audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories
This commit is contained in:
218
src/__Libraries/StellaOps.Spdx3/Model/Spdx3Document.cs
Normal file
218
src/__Libraries/StellaOps.Spdx3/Model/Spdx3Document.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
// <copyright file="Spdx3Document.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Spdx3.Model.Software;
|
||||
|
||||
namespace StellaOps.Spdx3.Model;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a parsed SPDX 3.0.1 document containing all elements.
|
||||
/// </summary>
|
||||
public sealed class Spdx3Document
|
||||
{
|
||||
private readonly Dictionary<string, Spdx3Element> _elementsById;
|
||||
private readonly Dictionary<string, Spdx3CreationInfo> _creationInfoById;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Spdx3Document"/> class.
|
||||
/// </summary>
|
||||
/// <param name="elements">All elements in the document.</param>
|
||||
/// <param name="creationInfos">All CreationInfo objects.</param>
|
||||
/// <param name="profiles">Detected profile conformance.</param>
|
||||
/// <param name="spdxDocument">The root SpdxDocument element if present.</param>
|
||||
public Spdx3Document(
|
||||
IEnumerable<Spdx3Element> elements,
|
||||
IEnumerable<Spdx3CreationInfo> creationInfos,
|
||||
IEnumerable<Spdx3ProfileIdentifier> profiles,
|
||||
Spdx3SpdxDocument? spdxDocument = null)
|
||||
{
|
||||
var elementList = elements.ToList();
|
||||
|
||||
// Use GroupBy to handle duplicates - last element wins for lookup but keeps all for validation
|
||||
_elementsById = elementList
|
||||
.GroupBy(e => e.SpdxId, StringComparer.Ordinal)
|
||||
.ToDictionary(g => g.Key, g => g.Last(), StringComparer.Ordinal);
|
||||
|
||||
_creationInfoById = creationInfos
|
||||
.Where(c => c.Id != null)
|
||||
.GroupBy(c => c.Id!, StringComparer.Ordinal)
|
||||
.ToDictionary(g => g.Key, g => g.Last(), StringComparer.Ordinal);
|
||||
|
||||
Profiles = profiles.ToImmutableHashSet();
|
||||
SpdxDocument = spdxDocument;
|
||||
|
||||
// Categorize elements by type - use original list to preserve duplicates for counting
|
||||
Packages = elementList.OfType<Spdx3Package>().ToImmutableArray();
|
||||
Files = elementList.OfType<Spdx3File>().ToImmutableArray();
|
||||
Snippets = elementList.OfType<Spdx3Snippet>().ToImmutableArray();
|
||||
Relationships = elementList.OfType<Spdx3Relationship>().ToImmutableArray();
|
||||
AllElements = elementList.ToImmutableArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all elements including duplicates (for validation).
|
||||
/// </summary>
|
||||
public ImmutableArray<Spdx3Element> AllElements { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root SpdxDocument element if present.
|
||||
/// </summary>
|
||||
public Spdx3SpdxDocument? SpdxDocument { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all elements in the document.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<Spdx3Element> Elements => _elementsById.Values;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all packages in the document.
|
||||
/// </summary>
|
||||
public ImmutableArray<Spdx3Package> Packages { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all files in the document.
|
||||
/// </summary>
|
||||
public ImmutableArray<Spdx3File> Files { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all snippets in the document.
|
||||
/// </summary>
|
||||
public ImmutableArray<Spdx3Snippet> Snippets { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all relationships in the document.
|
||||
/// </summary>
|
||||
public ImmutableArray<Spdx3Relationship> Relationships { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the detected profile conformance.
|
||||
/// </summary>
|
||||
public ImmutableHashSet<Spdx3ProfileIdentifier> Profiles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an element by its SPDX ID.
|
||||
/// </summary>
|
||||
/// <param name="spdxId">The SPDX ID.</param>
|
||||
/// <returns>The element, or null if not found.</returns>
|
||||
public Spdx3Element? GetById(string spdxId)
|
||||
{
|
||||
return _elementsById.TryGetValue(spdxId, out var element) ? element : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an element by its SPDX ID as a specific type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type.</typeparam>
|
||||
/// <param name="spdxId">The SPDX ID.</param>
|
||||
/// <returns>The element, or null if not found or wrong type.</returns>
|
||||
public T? GetById<T>(string spdxId) where T : Spdx3Element
|
||||
{
|
||||
return GetById(spdxId) as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a CreationInfo by its ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The CreationInfo ID.</param>
|
||||
/// <returns>The CreationInfo, or null if not found.</returns>
|
||||
public Spdx3CreationInfo? GetCreationInfo(string id)
|
||||
{
|
||||
return _creationInfoById.TryGetValue(id, out var info) ? info : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets relationships where the given element is the source.
|
||||
/// </summary>
|
||||
/// <param name="spdxId">The source element ID.</param>
|
||||
/// <returns>Matching relationships.</returns>
|
||||
public IEnumerable<Spdx3Relationship> GetRelationshipsFrom(string spdxId)
|
||||
{
|
||||
return Relationships.Where(r => r.From == spdxId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets relationships where the given element is a target.
|
||||
/// </summary>
|
||||
/// <param name="spdxId">The target element ID.</param>
|
||||
/// <returns>Matching relationships.</returns>
|
||||
public IEnumerable<Spdx3Relationship> GetRelationshipsTo(string spdxId)
|
||||
{
|
||||
return Relationships.Where(r => r.To.Contains(spdxId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets direct dependencies of a package.
|
||||
/// </summary>
|
||||
/// <param name="packageId">The package SPDX ID.</param>
|
||||
/// <returns>Dependent packages.</returns>
|
||||
public IEnumerable<Spdx3Package> GetDependencies(string packageId)
|
||||
{
|
||||
return GetRelationshipsFrom(packageId)
|
||||
.Where(r => r.RelationshipType == Spdx3RelationshipType.DependsOn)
|
||||
.SelectMany(r => r.To)
|
||||
.Select(GetById<Spdx3Package>)
|
||||
.Where(p => p != null)
|
||||
.Cast<Spdx3Package>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all files contained in a package.
|
||||
/// </summary>
|
||||
/// <param name="packageId">The package SPDX ID.</param>
|
||||
/// <returns>Contained files.</returns>
|
||||
public IEnumerable<Spdx3File> GetContainedFiles(string packageId)
|
||||
{
|
||||
return GetRelationshipsFrom(packageId)
|
||||
.Where(r => r.RelationshipType == Spdx3RelationshipType.Contains)
|
||||
.SelectMany(r => r.To)
|
||||
.Select(GetById<Spdx3File>)
|
||||
.Where(f => f != null)
|
||||
.Cast<Spdx3File>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the document conforms to a specific profile.
|
||||
/// </summary>
|
||||
/// <param name="profile">The profile to check.</param>
|
||||
/// <returns>True if the document conforms.</returns>
|
||||
public bool ConformsTo(Spdx3ProfileIdentifier profile)
|
||||
{
|
||||
return Profiles.Contains(profile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all PURLs from packages in the document.
|
||||
/// </summary>
|
||||
/// <returns>Package URLs.</returns>
|
||||
public IEnumerable<string> GetAllPurls()
|
||||
{
|
||||
return Packages
|
||||
.SelectMany(p => p.ExternalIdentifier)
|
||||
.Where(i => i.ExternalIdentifierType == Spdx3ExternalIdentifierType.PackageUrl)
|
||||
.Select(i => i.Identifier)
|
||||
.Distinct(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root package (if any).
|
||||
/// </summary>
|
||||
/// <returns>The root package, or null.</returns>
|
||||
public Spdx3Package? GetRootPackage()
|
||||
{
|
||||
if (SpdxDocument?.RootElement.Length > 0)
|
||||
{
|
||||
var rootId = SpdxDocument.RootElement[0];
|
||||
return GetById<Spdx3Package>(rootId);
|
||||
}
|
||||
|
||||
// Fallback: find package with no incoming Contains relationships
|
||||
var containedIds = Relationships
|
||||
.Where(r => r.RelationshipType == Spdx3RelationshipType.Contains)
|
||||
.SelectMany(r => r.To)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
return Packages.FirstOrDefault(p => !containedIds.Contains(p.SpdxId));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user