Skip to content

Commit 0e22204

Browse files
committed
[issue-561] fix rdf parser: add logic to parse implicit "hasFiles" and "describesPackage" relationships
Signed-off-by: Meret Behrens <[email protected]>
1 parent ac9e62f commit 0e22204

File tree

5 files changed

+102
-27
lines changed

5 files changed

+102
-27
lines changed

src/spdx/parser/rdf/rdf_parser.py

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# SPDX-FileCopyrightText: 2023 spdx contributors
22
#
33
# SPDX-License-Identifier: Apache-2.0
4+
from typing import Any, Dict
5+
46
from rdflib import RDF, Graph
57

68
from spdx.model.document import Document
9+
from spdx.model.relationship import RelationshipType
710
from spdx.parser.error import SPDXParsingError
811
from spdx.parser.logger import Logger
912
from spdx.parser.parsing_functions import construct_or_raise_parsing_error, raise_parsing_error_if_logger_has_messages
@@ -12,7 +15,7 @@
1215
from spdx.parser.rdf.extracted_licensing_info_parser import parse_extracted_licensing_info
1316
from spdx.parser.rdf.file_parser import parse_file
1417
from spdx.parser.rdf.package_parser import parse_package
15-
from spdx.parser.rdf.relationship_parser import parse_relationship
18+
from spdx.parser.rdf.relationship_parser import parse_implicit_relationship, parse_relationship
1619
from spdx.parser.rdf.snippet_parser import parse_snippet
1720
from spdx.rdfschema.namespace import SPDX_NAMESPACE
1821

@@ -27,7 +30,7 @@ def parse_from_file(file_name: str) -> Document:
2730

2831

2932
def translate_graph_to_document(graph: Graph) -> Document:
30-
parsed_fields = dict()
33+
parsed_fields: Dict[str, Any] = dict()
3134
logger = Logger()
3235
try:
3336
creation_info, doc_node = parse_creation_info(graph)
@@ -62,6 +65,21 @@ def translate_graph_to_document(graph: Graph) -> Document:
6265
logger.extend(err.get_messages())
6366
parsed_fields[element] = elements
6467

68+
for triple, relationship_type in [
69+
((None, SPDX_NAMESPACE.hasFile, None), RelationshipType.CONTAINS),
70+
((None, SPDX_NAMESPACE.describesPackage, None), RelationshipType.DESCRIBES),
71+
]:
72+
for parent_node, _, element_node in graph.triples(triple):
73+
try:
74+
relationship = parse_implicit_relationship(
75+
parent_node, relationship_type, element_node, graph, creation_info.document_namespace
76+
)
77+
if relationship not in parsed_fields["relationships"]:
78+
parsed_fields["relationships"].append(relationship)
79+
80+
except SPDXParsingError as err:
81+
logger.extend(err.get_messages())
82+
6583
extracted_licensing_infos = []
6684
for _, _, extracted_licensing_info_node in graph.triples((None, SPDX_NAMESPACE.hasExtractedLicensingInfo, None)):
6785
try:

src/spdx/parser/rdf/relationship_parser.py

+20
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,23 @@ def parse_relationship(
4949
)
5050

5151
return relationship
52+
53+
54+
def parse_implicit_relationship(
55+
spdx_element_node: URIRef,
56+
relationship_type: RelationshipType,
57+
related_spdx_element_node: URIRef,
58+
graph: Graph,
59+
doc_namespace: str,
60+
) -> Relationship:
61+
spdx_element_id = parse_spdx_id(spdx_element_node, doc_namespace, graph)
62+
related_spdx_element_id = parse_spdx_id(related_spdx_element_node, doc_namespace, graph)
63+
relationship = construct_or_raise_parsing_error(
64+
Relationship,
65+
dict(
66+
spdx_element_id=spdx_element_id,
67+
relationship_type=relationship_type,
68+
related_spdx_element_id=related_spdx_element_id,
69+
),
70+
)
71+
return relationship

tests/spdx/parser/rdf/data/file_to_test_rdf_parser.rdf.xml

+27-22
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@
8080
<spdx:spdxDocument rdf:resource="https://namespace.com"/>
8181
</spdx:ExternalDocumentRef>
8282
</spdx:externalDocumentRef>
83-
</spdx:SpdxDocument>
84-
<spdx:Package rdf:about="https://some.namespace#SPDXRef-Package">
83+
<spdx:describesPackage>
84+
<spdx:Package rdf:about="https://some.namespace#SPDXRef-Package">
8585
<spdx:licenseConcluded>
8686
<spdx:ConjunctiveLicenseSet rdf:nodeID="N7cbb6cbf0d9d4b77bed24cab18272c1f">
8787
<spdx:member rdf:resource="http://spdx.org/licenses/MIT"/>
@@ -137,27 +137,32 @@
137137
<rdfs:comment>externalPackageRefComment</rdfs:comment>
138138
</spdx:ExternalRef>
139139
</spdx:externalRef>
140+
<spdx:hasFile rdf:resource="https://some.namespace#SPDXRef-File"/>
140141
</spdx:Package>
141-
<spdx:Package rdf:about="https://some.namespace#SPDXRef-Package2">
142-
<spdx:name>packageName</spdx:name>
143-
<spdx:downloadLocation>http://differentdownload.com</spdx:downloadLocation>
144-
<spdx:externalRef>
145-
<spdx:ExternalRef rdf:nodeID="Nda50d273cd134f45b1ca926a378b8db4">
146-
<spdx:referenceCategory rdf:resource="http://spdx.org/rdf/terms#referenceCategory_other"/>
147-
<rdfs:comment>This is the external ref for Acme</rdfs:comment>
148-
<spdx:referenceLocator>acmecorp/acmenator/4.1.3-alpha</spdx:referenceLocator>
149-
<spdx:referenceType rdf:resource="https://some.namespace#LocationRef-acmeforge"/>
150-
</spdx:ExternalRef>
151-
</spdx:externalRef>
152-
<spdx:licenseConcluded>
153-
<spdx:ExtractedLicensingInfo rdf:about="https://some.namespace#LicenseRef-2">
154-
<rdfs:seeAlso>https://see.also</rdfs:seeAlso>
155-
<spdx:extractedText>extractedText</spdx:extractedText>
156-
<rdfs:comment>licenseComment</rdfs:comment>
157-
<spdx:name>another license</spdx:name>
158-
</spdx:ExtractedLicensingInfo>
159-
</spdx:licenseConcluded>
160-
</spdx:Package>
142+
</spdx:describesPackage>
143+
<spdx:describesPackage>
144+
<spdx:Package rdf:about="https://some.namespace#SPDXRef-Package2">
145+
<spdx:name>packageName</spdx:name>
146+
<spdx:downloadLocation>http://differentdownload.com</spdx:downloadLocation>
147+
<spdx:externalRef>
148+
<spdx:ExternalRef rdf:nodeID="Nda50d273cd134f45b1ca926a378b8db4">
149+
<spdx:referenceCategory rdf:resource="http://spdx.org/rdf/terms#referenceCategory_other"/>
150+
<rdfs:comment>This is the external ref for Acme</rdfs:comment>
151+
<spdx:referenceLocator>acmecorp/acmenator/4.1.3-alpha</spdx:referenceLocator>
152+
<spdx:referenceType rdf:resource="https://some.namespace#LocationRef-acmeforge"/>
153+
</spdx:ExternalRef>
154+
</spdx:externalRef>
155+
<spdx:licenseConcluded>
156+
<spdx:ExtractedLicensingInfo rdf:about="https://some.namespace#LicenseRef-2">
157+
<rdfs:seeAlso>https://see.also</rdfs:seeAlso>
158+
<spdx:extractedText>extractedText</spdx:extractedText>
159+
<rdfs:comment>licenseComment</rdfs:comment>
160+
<spdx:name>another license</spdx:name>
161+
</spdx:ExtractedLicensingInfo>
162+
</spdx:licenseConcluded>
163+
</spdx:Package>
164+
</spdx:describesPackage>
165+
</spdx:SpdxDocument>
161166
<spdx:Snippet rdf:about="https://some.namespace#SPDXRef-Snippet">
162167
<spdx:range>
163168
<ptr:StartEndPointer rdf:nodeID="N079962c320a64e15bc1b4f2c01ea04b8">

tests/spdx/parser/rdf/test_rdf_parser.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ def test_rdf_parser_with_2_2_example():
4444
assert len(doc.files) == 4
4545
assert len(doc.annotations) == 5
4646
assert len(doc.packages) == 4
47-
assert len(doc.relationships) == 9
47+
assert len(doc.relationships) == 11
4848
assert len(doc.extracted_licensing_info) == 5

tests/spdx/parser/rdf/test_relationship_parser.py

+34-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
# SPDX-License-Identifier: Apache-2.0
44
import os
55

6-
from rdflib import RDF, Graph
6+
import pytest
7+
from rdflib import RDF, Graph, URIRef
78

89
from spdx.model.relationship import RelationshipType
9-
from spdx.parser.rdf.relationship_parser import parse_relationship
10+
from spdx.parser.rdf.relationship_parser import parse_implicit_relationship, parse_relationship
1011
from spdx.rdfschema.namespace import SPDX_NAMESPACE
1112

1213

@@ -22,3 +23,34 @@ def test_relationship_parser():
2223
assert relationship.relationship_type == RelationshipType.DESCRIBES
2324
assert relationship.related_spdx_element_id == "SPDXRef-File"
2425
assert relationship.comment == "relationshipComment"
26+
27+
28+
@pytest.mark.parametrize(
29+
"parent_node, predicate, spdx_element_id, relationship_type, related_spdx_element_id",
30+
[
31+
(
32+
SPDX_NAMESPACE.SpdxDocument,
33+
SPDX_NAMESPACE.describesPackage,
34+
"SPDXRef-DOCUMENT",
35+
RelationshipType.DESCRIBES,
36+
"SPDXRef-Package",
37+
),
38+
(SPDX_NAMESPACE.Package, SPDX_NAMESPACE.hasFile, "SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File"),
39+
],
40+
)
41+
def test_parse_implicit_relationship(
42+
parent_node, predicate, spdx_element_id, relationship_type, related_spdx_element_id
43+
):
44+
graph = Graph().parse(os.path.join(os.path.dirname(__file__), "data/file_to_test_rdf_parser.rdf.xml"))
45+
parent_node = graph.value(predicate=RDF.type, object=parent_node)
46+
relationship_node = graph.value(subject=parent_node, predicate=predicate)
47+
assert isinstance(relationship_node, URIRef)
48+
assert isinstance(parent_node, URIRef)
49+
50+
doc_namespace = "https://some.namespace"
51+
52+
relationship = parse_implicit_relationship(parent_node, relationship_type, relationship_node, graph, doc_namespace)
53+
54+
assert relationship.spdx_element_id == spdx_element_id
55+
assert relationship.relationship_type == relationship_type
56+
assert relationship.related_spdx_element_id == related_spdx_element_id

0 commit comments

Comments
 (0)