Files
git.stella-ops.org/src/__Libraries/StellaOps.Spdx3/Spdx3Parser.cs

898 lines
33 KiB
C#

// <copyright file="Spdx3Parser.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Collections.Immutable;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Spdx3.JsonLd;
using StellaOps.Spdx3.Model;
using StellaOps.Spdx3.Model.Build;
using StellaOps.Spdx3.Model.Security;
using StellaOps.Spdx3.Model.Software;
namespace StellaOps.Spdx3;
/// <summary>
/// SPDX 3.0.1 JSON-LD parser implementation.
/// </summary>
public sealed class Spdx3Parser : ISpdx3Parser
{
private readonly ISpdx3ContextResolver _contextResolver;
private readonly ILogger<Spdx3Parser> _logger;
private readonly TimeProvider _timeProvider;
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true
};
/// <summary>
/// Initializes a new instance of the <see cref="Spdx3Parser"/> class.
/// </summary>
public Spdx3Parser(
ISpdx3ContextResolver contextResolver,
ILogger<Spdx3Parser> logger,
TimeProvider? timeProvider = null)
{
_contextResolver = contextResolver;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <inheritdoc />
public async Task<Spdx3ParseResult> ParseAsync(
Stream stream,
CancellationToken cancellationToken = default)
{
try
{
using var document = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false);
return await ParseDocumentAsync(document, cancellationToken)
.ConfigureAwait(false);
}
catch (JsonException ex)
{
_logger.LogError(ex, "Failed to parse SPDX 3.0.1 JSON");
return Spdx3ParseResult.Failed("JSON_PARSE_ERROR", ex.Message, ex.Path);
}
}
/// <inheritdoc />
public async Task<Spdx3ParseResult> ParseAsync(
string filePath,
CancellationToken cancellationToken = default)
{
if (!File.Exists(filePath))
{
return Spdx3ParseResult.Failed("FILE_NOT_FOUND", $"File not found: {filePath}");
}
await using var stream = File.OpenRead(filePath);
return await ParseAsync(stream, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task<Spdx3ParseResult> ParseFromJsonAsync(
string json,
CancellationToken cancellationToken = default)
{
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json));
return await ParseAsync(stream, cancellationToken).ConfigureAwait(false);
}
private async Task<Spdx3ParseResult> ParseDocumentAsync(
JsonDocument document,
CancellationToken cancellationToken)
{
var root = document.RootElement;
var errors = new List<Spdx3ParseError>();
var warnings = new List<Spdx3ParseWarning>();
// Check for @context (JSON-LD indicator)
if (!root.TryGetProperty("@context", out var contextElement))
{
return Spdx3ParseResult.Failed("MISSING_CONTEXT", "No @context found. Is this an SPDX 3.0.1 JSON-LD document?");
}
// Resolve context (for future expansion support)
var contextRef = GetContextReference(contextElement);
if (!string.IsNullOrEmpty(contextRef))
{
await _contextResolver.ResolveAsync(contextRef, cancellationToken).ConfigureAwait(false);
}
// Parse @graph (main element array)
var elements = new List<Spdx3Element>();
var creationInfos = new List<Spdx3CreationInfo>();
var profiles = new HashSet<Spdx3ProfileIdentifier>();
Spdx3SpdxDocument? spdxDocument = null;
if (root.TryGetProperty("@graph", out var graphElement) && graphElement.ValueKind == JsonValueKind.Array)
{
foreach (var element in graphElement.EnumerateArray())
{
var parsed = ParseElement(element, errors, warnings);
if (parsed != null)
{
elements.Add(parsed);
if (parsed is Spdx3SpdxDocument doc)
{
spdxDocument = doc;
}
}
// Also extract CreationInfo
var creationInfo = ExtractCreationInfo(element, errors);
if (creationInfo != null)
{
creationInfos.Add(creationInfo);
foreach (var profile in creationInfo.Profile)
{
profiles.Add(profile);
}
}
}
}
else
{
// Single document format (no @graph)
var parsed = ParseElement(root, errors, warnings);
if (parsed != null)
{
elements.Add(parsed);
if (parsed is Spdx3SpdxDocument doc)
{
spdxDocument = doc;
}
}
}
if (errors.Count > 0)
{
return Spdx3ParseResult.Failed(errors, warnings);
}
var result = new Spdx3Document(elements, creationInfos, profiles, spdxDocument);
return Spdx3ParseResult.Succeeded(result, warnings);
}
private Spdx3Element? ParseElement(
JsonElement element,
List<Spdx3ParseError> errors,
List<Spdx3ParseWarning> warnings)
{
if (element.ValueKind != JsonValueKind.Object)
{
return null;
}
// Get @type to determine element type
var type = GetStringProperty(element, "@type") ?? GetStringProperty(element, "type");
if (string.IsNullOrEmpty(type))
{
return null;
}
// Get spdxId (required)
var spdxId = GetStringProperty(element, "spdxId") ?? GetStringProperty(element, "@id");
if (string.IsNullOrEmpty(spdxId))
{
warnings.Add(new Spdx3ParseWarning("MISSING_SPDX_ID", $"Element of type {type} has no spdxId"));
return null;
}
return type switch
{
"software_Package" or "Package" or "spdx:software_Package" =>
ParsePackage(element, spdxId),
"software_File" or "File" or "spdx:software_File" =>
ParseFile(element, spdxId),
"software_Snippet" or "Snippet" or "spdx:software_Snippet" =>
ParseSnippet(element, spdxId),
"software_SpdxDocument" or "SpdxDocument" or "spdx:software_SpdxDocument" =>
ParseSpdxDocument(element, spdxId),
"Relationship" or "spdx:Relationship" =>
ParseRelationship(element, spdxId),
"Person" or "spdx:Person" =>
ParseAgent<Spdx3Person>(element, spdxId),
"Organization" or "spdx:Organization" =>
ParseAgent<Spdx3Organization>(element, spdxId),
"Tool" or "spdx:Tool" =>
ParseAgent<Spdx3Tool>(element, spdxId),
"Build" or "build_Build" or "spdx:Build" =>
ParseBuild(element, spdxId),
"security_Vulnerability" or "Vulnerability" or "spdx:security_Vulnerability" =>
ParseVulnerability(element, spdxId),
"security_VexAffectedVulnAssessmentRelationship" or
"security_VexNotAffectedVulnAssessmentRelationship" or
"security_VexFixedVulnAssessmentRelationship" or
"security_VexUnderInvestigationVulnAssessmentRelationship" =>
ParseVexAssessment(element, spdxId, type),
"security_CvssV3VulnAssessmentRelationship" =>
ParseCvssAssessment(element, spdxId),
"security_EpssVulnAssessmentRelationship" =>
ParseEpssAssessment(element, spdxId),
_ => ParseGenericElement(element, spdxId, type, warnings)
};
}
private Spdx3Package ParsePackage(JsonElement element, string spdxId)
{
return new Spdx3Package
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
Name = GetStringProperty(element, "name"),
Summary = GetStringProperty(element, "summary"),
Description = GetStringProperty(element, "description"),
Comment = GetStringProperty(element, "comment"),
PackageVersion = GetStringProperty(element, "packageVersion"),
DownloadLocation = GetStringProperty(element, "downloadLocation"),
PackageUrl = GetStringProperty(element, "packageUrl"),
HomePage = GetStringProperty(element, "homePage"),
SourceInfo = GetStringProperty(element, "sourceInfo"),
CopyrightText = GetStringProperty(element, "copyrightText"),
SuppliedBy = GetStringProperty(element, "suppliedBy"),
VerifiedUsing = ParseIntegrityMethods(element),
ExternalRef = ParseExternalRefs(element),
ExternalIdentifier = ParseExternalIdentifiers(element),
OriginatedBy = GetStringArrayProperty(element, "originatedBy"),
AttributionText = GetStringArrayProperty(element, "attributionText")
};
}
private Spdx3File ParseFile(JsonElement element, string spdxId)
{
return new Spdx3File
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
Name = GetStringProperty(element, "name") ?? string.Empty,
Summary = GetStringProperty(element, "summary"),
Description = GetStringProperty(element, "description"),
Comment = GetStringProperty(element, "comment"),
ContentType = GetStringProperty(element, "contentType"),
CopyrightText = GetStringProperty(element, "copyrightText"),
VerifiedUsing = ParseIntegrityMethods(element),
ExternalRef = ParseExternalRefs(element),
ExternalIdentifier = ParseExternalIdentifiers(element)
};
}
private Spdx3Snippet ParseSnippet(JsonElement element, string spdxId)
{
return new Spdx3Snippet
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
Name = GetStringProperty(element, "name"),
Summary = GetStringProperty(element, "summary"),
Description = GetStringProperty(element, "description"),
Comment = GetStringProperty(element, "comment"),
SnippetFromFile = GetStringProperty(element, "snippetFromFile") ?? string.Empty,
CopyrightText = GetStringProperty(element, "copyrightText"),
VerifiedUsing = ParseIntegrityMethods(element),
ExternalRef = ParseExternalRefs(element),
ExternalIdentifier = ParseExternalIdentifiers(element)
};
}
private Spdx3SpdxDocument ParseSpdxDocument(JsonElement element, string spdxId)
{
return new Spdx3SpdxDocument
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
Name = GetStringProperty(element, "name"),
Summary = GetStringProperty(element, "summary"),
Description = GetStringProperty(element, "description"),
Comment = GetStringProperty(element, "comment"),
Element = GetStringArrayProperty(element, "element"),
RootElement = GetStringArrayProperty(element, "rootElement"),
VerifiedUsing = ParseIntegrityMethods(element),
ExternalRef = ParseExternalRefs(element),
ExternalIdentifier = ParseExternalIdentifiers(element)
};
}
private Spdx3Relationship ParseRelationship(JsonElement element, string spdxId)
{
var toValue = GetStringProperty(element, "to");
var toArray = toValue != null
? [toValue]
: GetStringArrayProperty(element, "to");
var relationshipType = GetStringProperty(element, "relationshipType") ?? "Other";
if (!Enum.TryParse<Spdx3RelationshipType>(relationshipType, ignoreCase: true, out var relType))
{
relType = Spdx3RelationshipType.Other;
}
return new Spdx3Relationship
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
From = GetStringProperty(element, "from") ?? string.Empty,
To = toArray,
RelationshipType = relType,
Comment = GetStringProperty(element, "comment"),
VerifiedUsing = ParseIntegrityMethods(element),
ExternalRef = ParseExternalRefs(element),
ExternalIdentifier = ParseExternalIdentifiers(element)
};
}
private Spdx3Build ParseBuild(JsonElement element, string spdxId)
{
// Parse timestamps
DateTimeOffset? buildStartTime = null;
DateTimeOffset? buildEndTime = null;
var startTimeStr = GetStringProperty(element, "build_buildStartTime");
if (!string.IsNullOrEmpty(startTimeStr) && DateTimeOffset.TryParse(startTimeStr, out var parsedStart))
{
buildStartTime = parsedStart;
}
var endTimeStr = GetStringProperty(element, "build_buildEndTime");
if (!string.IsNullOrEmpty(endTimeStr) && DateTimeOffset.TryParse(endTimeStr, out var parsedEnd))
{
buildEndTime = parsedEnd;
}
// Parse config source digests
var configSourceDigests = ImmutableArray<Spdx3BuildHash>.Empty;
if (element.TryGetProperty("build_configSourceDigest", out var digestsElement) &&
digestsElement.ValueKind == JsonValueKind.Array)
{
var digests = new List<Spdx3BuildHash>();
foreach (var digestEl in digestsElement.EnumerateArray())
{
if (digestEl.ValueKind == JsonValueKind.Object)
{
var algorithm = GetStringProperty(digestEl, "algorithm") ?? "sha256";
var hashValue = GetStringProperty(digestEl, "hashValue") ?? string.Empty;
digests.Add(new Spdx3BuildHash { Algorithm = algorithm, HashValue = hashValue });
}
}
configSourceDigests = digests.ToImmutableArray();
}
// Parse environment and parameters as dictionaries
var environment = ParseDictionary(element, "build_environment");
var parameters = ParseDictionary(element, "build_parameter");
return new Spdx3Build
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
Name = GetStringProperty(element, "name"),
Summary = GetStringProperty(element, "summary"),
Description = GetStringProperty(element, "description"),
BuildType = GetStringProperty(element, "build_buildType") ?? string.Empty,
BuildId = GetStringProperty(element, "build_buildId"),
BuildStartTime = buildStartTime,
BuildEndTime = buildEndTime,
ConfigSourceUri = GetStringArrayProperty(element, "build_configSourceUri"),
ConfigSourceDigest = configSourceDigests,
ConfigSourceEntrypoint = GetStringArrayProperty(element, "build_configSourceEntrypoint"),
Environment = environment,
Parameter = parameters,
VerifiedUsing = ParseIntegrityMethods(element),
ExternalRef = ParseExternalRefs(element),
ExternalIdentifier = ParseExternalIdentifiers(element)
};
}
private static ImmutableDictionary<string, string> ParseDictionary(JsonElement element, string propertyName)
{
if (!element.TryGetProperty(propertyName, out var dictElement) ||
dictElement.ValueKind != JsonValueKind.Object)
{
return ImmutableDictionary<string, string>.Empty;
}
var dict = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var property in dictElement.EnumerateObject())
{
if (property.Value.ValueKind == JsonValueKind.String)
{
dict[property.Name] = property.Value.GetString() ?? string.Empty;
}
}
return dict.ToImmutableDictionary();
}
private Spdx3Vulnerability ParseVulnerability(JsonElement element, string spdxId)
{
DateTimeOffset? publishedTime = null;
DateTimeOffset? modifiedTime = null;
DateTimeOffset? withdrawnTime = null;
var publishedStr = GetStringProperty(element, "security_publishedTime");
if (!string.IsNullOrEmpty(publishedStr) && DateTimeOffset.TryParse(publishedStr, out var parsedPublished))
{
publishedTime = parsedPublished;
}
var modifiedStr = GetStringProperty(element, "security_modifiedTime");
if (!string.IsNullOrEmpty(modifiedStr) && DateTimeOffset.TryParse(modifiedStr, out var parsedModified))
{
modifiedTime = parsedModified;
}
var withdrawnStr = GetStringProperty(element, "security_withdrawnTime");
if (!string.IsNullOrEmpty(withdrawnStr) && DateTimeOffset.TryParse(withdrawnStr, out var parsedWithdrawn))
{
withdrawnTime = parsedWithdrawn;
}
return new Spdx3Vulnerability
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
Name = GetStringProperty(element, "name"),
Summary = GetStringProperty(element, "summary"),
Description = GetStringProperty(element, "description"),
PublishedTime = publishedTime,
ModifiedTime = modifiedTime,
WithdrawnTime = withdrawnTime,
ExternalRefs = ParseExternalRefs(element),
ExternalIdentifiers = ParseExternalIdentifiers(element)
};
}
private Spdx3VulnAssessmentRelationship ParseVexAssessment(
JsonElement element,
string spdxId,
string type)
{
var assessedElement = GetStringProperty(element, "security_assessedElement") ?? string.Empty;
var from = GetStringProperty(element, "from") ?? string.Empty;
var toValue = GetStringProperty(element, "to");
var toArray = toValue != null ? ImmutableArray.Create(toValue) : GetStringArrayProperty(element, "to");
DateTimeOffset? publishedTime = null;
var publishedStr = GetStringProperty(element, "security_publishedTime");
if (!string.IsNullOrEmpty(publishedStr) && DateTimeOffset.TryParse(publishedStr, out var parsedPublished))
{
publishedTime = parsedPublished;
}
DateTimeOffset? actionStatementTime = null;
var actionTimeStr = GetStringProperty(element, "security_actionStatementTime");
if (!string.IsNullOrEmpty(actionTimeStr) && DateTimeOffset.TryParse(actionTimeStr, out var parsedActionTime))
{
actionStatementTime = parsedActionTime;
}
var vexVersion = GetStringProperty(element, "security_vexVersion");
var statusNotes = GetStringProperty(element, "security_statusNotes");
var actionStatement = GetStringProperty(element, "security_actionStatement");
var impactStatement = GetStringProperty(element, "security_impactStatement");
var suppliedBy = GetStringProperty(element, "security_suppliedBy");
// Parse justification for not_affected
Spdx3VexJustificationType? justificationType = null;
var justificationStr = GetStringProperty(element, "security_justificationType");
if (!string.IsNullOrEmpty(justificationStr) &&
Enum.TryParse<Spdx3VexJustificationType>(justificationStr, ignoreCase: true, out var parsed))
{
justificationType = parsed;
}
return type switch
{
"security_VexAffectedVulnAssessmentRelationship" => new Spdx3VexAffectedVulnAssessmentRelationship
{
SpdxId = spdxId,
Type = type,
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.Affects,
VexVersion = vexVersion,
StatusNotes = statusNotes,
ActionStatement = actionStatement,
ActionStatementTime = actionStatementTime,
PublishedTime = publishedTime,
SuppliedBy = suppliedBy
},
"security_VexNotAffectedVulnAssessmentRelationship" => new Spdx3VexNotAffectedVulnAssessmentRelationship
{
SpdxId = spdxId,
Type = type,
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.DoesNotAffect,
VexVersion = vexVersion,
StatusNotes = statusNotes,
ImpactStatement = impactStatement,
JustificationType = justificationType,
PublishedTime = publishedTime,
SuppliedBy = suppliedBy
},
"security_VexFixedVulnAssessmentRelationship" => new Spdx3VexFixedVulnAssessmentRelationship
{
SpdxId = spdxId,
Type = type,
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.FixedIn,
VexVersion = vexVersion,
StatusNotes = statusNotes,
PublishedTime = publishedTime,
SuppliedBy = suppliedBy
},
"security_VexUnderInvestigationVulnAssessmentRelationship" => new Spdx3VexUnderInvestigationVulnAssessmentRelationship
{
SpdxId = spdxId,
Type = type,
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.UnderInvestigationFor,
VexVersion = vexVersion,
StatusNotes = statusNotes,
PublishedTime = publishedTime,
SuppliedBy = suppliedBy
},
_ => throw new ArgumentException($"Unknown VEX assessment type: {type}")
};
}
private Spdx3CvssV3VulnAssessmentRelationship ParseCvssAssessment(JsonElement element, string spdxId)
{
var assessedElement = GetStringProperty(element, "security_assessedElement") ?? string.Empty;
var from = GetStringProperty(element, "from") ?? string.Empty;
var toValue = GetStringProperty(element, "to");
var toArray = toValue != null ? ImmutableArray.Create(toValue) : GetStringArrayProperty(element, "to");
decimal? baseScore = null;
if (element.TryGetProperty("security_score", out var scoreEl) && scoreEl.TryGetDecimal(out var score))
{
baseScore = score;
}
var vectorString = GetStringProperty(element, "security_vectorString");
// Parse severity enum
Spdx3CvssSeverity? severityEnum = null;
var severityStr = GetStringProperty(element, "security_severity");
if (!string.IsNullOrEmpty(severityStr) &&
Enum.TryParse<Spdx3CvssSeverity>(severityStr, ignoreCase: true, out var parsedSeverity))
{
severityEnum = parsedSeverity;
}
return new Spdx3CvssV3VulnAssessmentRelationship
{
SpdxId = spdxId,
Type = "security_CvssV3VulnAssessmentRelationship",
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.HasAssessmentFor,
Score = baseScore,
VectorString = vectorString,
Severity = severityEnum
};
}
private Spdx3EpssVulnAssessmentRelationship ParseEpssAssessment(JsonElement element, string spdxId)
{
var assessedElement = GetStringProperty(element, "security_assessedElement") ?? string.Empty;
var from = GetStringProperty(element, "from") ?? string.Empty;
var toValue = GetStringProperty(element, "to");
var toArray = toValue != null ? ImmutableArray.Create(toValue) : GetStringArrayProperty(element, "to");
decimal? probability = null;
if (element.TryGetProperty("security_probability", out var probEl) && probEl.TryGetDecimal(out var prob))
{
probability = prob;
}
decimal? percentile = null;
if (element.TryGetProperty("security_percentile", out var percEl) && percEl.TryGetDecimal(out var perc))
{
percentile = perc;
}
return new Spdx3EpssVulnAssessmentRelationship
{
SpdxId = spdxId,
Type = "security_EpssVulnAssessmentRelationship",
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.HasAssessmentFor,
Probability = probability,
Percentile = percentile
};
}
private T ParseAgent<T>(JsonElement element, string spdxId) where T : Spdx3Element
{
var name = GetStringProperty(element, "name") ?? string.Empty;
if (typeof(T) == typeof(Spdx3Person))
{
return (T)(object)new Spdx3Person
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
Name = name
};
}
if (typeof(T) == typeof(Spdx3Organization))
{
return (T)(object)new Spdx3Organization
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
Name = name
};
}
if (typeof(T) == typeof(Spdx3Tool))
{
return (T)(object)new Spdx3Tool
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
Name = name
};
}
throw new InvalidOperationException($"Unsupported agent type: {typeof(T)}");
}
private Spdx3Element? ParseGenericElement(
JsonElement element,
string spdxId,
string type,
List<Spdx3ParseWarning> warnings)
{
// For unknown types, we could create a generic element
// For now, log a warning and skip
warnings.Add(new Spdx3ParseWarning("UNKNOWN_TYPE", $"Unknown element type: {type}", spdxId));
return null;
}
private Spdx3CreationInfo? ExtractCreationInfo(
JsonElement element,
List<Spdx3ParseError> errors)
{
if (!element.TryGetProperty("creationInfo", out var ciElement))
{
return null;
}
if (ciElement.ValueKind == JsonValueKind.String)
{
// Reference to CreationInfo - will be resolved later
return null;
}
if (ciElement.ValueKind != JsonValueKind.Object)
{
return null;
}
var specVersion = GetStringProperty(ciElement, "specVersion");
if (string.IsNullOrEmpty(specVersion))
{
return null;
}
var createdStr = GetStringProperty(ciElement, "created");
if (!DateTimeOffset.TryParse(createdStr, out var created))
{
created = _timeProvider.GetUtcNow();
}
var profileStrings = GetStringArrayProperty(ciElement, "profile");
var profiles = profileStrings
.Select(p => Spdx3ProfileUris.Parse(p))
.Where(p => p.HasValue)
.Select(p => p!.Value)
.ToImmutableArray();
return new Spdx3CreationInfo
{
Id = GetStringProperty(ciElement, "@id"),
Type = GetStringProperty(ciElement, "@type"),
SpecVersion = specVersion,
Created = created,
CreatedBy = GetStringArrayProperty(ciElement, "createdBy"),
CreatedUsing = GetStringArrayProperty(ciElement, "createdUsing"),
Profile = profiles,
DataLicense = GetStringProperty(ciElement, "dataLicense"),
Comment = GetStringProperty(ciElement, "comment")
};
}
private ImmutableArray<Spdx3IntegrityMethod> ParseIntegrityMethods(JsonElement element)
{
if (!element.TryGetProperty("verifiedUsing", out var verifiedUsing))
{
return [];
}
if (verifiedUsing.ValueKind != JsonValueKind.Array)
{
return [];
}
var methods = new List<Spdx3IntegrityMethod>();
foreach (var item in verifiedUsing.EnumerateArray())
{
var algorithm = GetStringProperty(item, "algorithm");
var hashValue = GetStringProperty(item, "hashValue");
if (!string.IsNullOrEmpty(algorithm) && !string.IsNullOrEmpty(hashValue))
{
if (Enum.TryParse<Spdx3HashAlgorithm>(algorithm.Replace("-", "_"), ignoreCase: true, out var algo))
{
methods.Add(new Spdx3Hash
{
Type = GetStringProperty(item, "@type"),
Algorithm = algo,
HashValue = hashValue.ToLowerInvariant(),
Comment = GetStringProperty(item, "comment")
});
}
}
}
return [.. methods];
}
private ImmutableArray<Spdx3ExternalRef> ParseExternalRefs(JsonElement element)
{
if (!element.TryGetProperty("externalRef", out var externalRef))
{
return [];
}
if (externalRef.ValueKind != JsonValueKind.Array)
{
return [];
}
var refs = new List<Spdx3ExternalRef>();
foreach (var item in externalRef.EnumerateArray())
{
refs.Add(new Spdx3ExternalRef
{
Type = GetStringProperty(item, "@type"),
Locator = GetStringArrayProperty(item, "locator"),
ContentType = GetStringProperty(item, "contentType"),
Comment = GetStringProperty(item, "comment")
});
}
return [.. refs];
}
private ImmutableArray<Spdx3ExternalIdentifier> ParseExternalIdentifiers(JsonElement element)
{
if (!element.TryGetProperty("externalIdentifier", out var externalId))
{
return [];
}
if (externalId.ValueKind != JsonValueKind.Array)
{
return [];
}
var identifiers = new List<Spdx3ExternalIdentifier>();
foreach (var item in externalId.EnumerateArray())
{
var identifier = GetStringProperty(item, "identifier");
if (string.IsNullOrEmpty(identifier))
{
continue;
}
var typeStr = GetStringProperty(item, "externalIdentifierType");
Spdx3ExternalIdentifierType? idType = null;
if (!string.IsNullOrEmpty(typeStr) &&
Enum.TryParse<Spdx3ExternalIdentifierType>(typeStr, ignoreCase: true, out var parsed))
{
idType = parsed;
}
identifiers.Add(new Spdx3ExternalIdentifier
{
Type = GetStringProperty(item, "@type"),
ExternalIdentifierType = idType,
Identifier = identifier,
Comment = GetStringProperty(item, "comment"),
IssuingAuthority = GetStringProperty(item, "issuingAuthority")
});
}
return [.. identifiers];
}
private static string? GetStringProperty(JsonElement element, string propertyName)
{
if (element.TryGetProperty(propertyName, out var prop) &&
prop.ValueKind == JsonValueKind.String)
{
return prop.GetString();
}
return null;
}
private static ImmutableArray<string> GetStringArrayProperty(JsonElement element, string propertyName)
{
if (!element.TryGetProperty(propertyName, out var prop))
{
return [];
}
if (prop.ValueKind == JsonValueKind.String)
{
var value = prop.GetString();
return value != null ? [value] : [];
}
if (prop.ValueKind == JsonValueKind.Array)
{
var list = new List<string>();
foreach (var item in prop.EnumerateArray())
{
if (item.ValueKind == JsonValueKind.String)
{
var value = item.GetString();
if (value != null)
{
list.Add(value);
}
}
}
return [.. list];
}
return [];
}
private static string? GetContextReference(JsonElement contextElement)
{
if (contextElement.ValueKind == JsonValueKind.String)
{
return contextElement.GetString();
}
if (contextElement.ValueKind == JsonValueKind.Array)
{
foreach (var item in contextElement.EnumerateArray())
{
if (item.ValueKind == JsonValueKind.String)
{
return item.GetString();
}
}
}
return null;
}
}