diff --git a/spdx/parsers/rdf.py b/spdx/parsers/rdf.py
index 774966f16..8e4b14d9b 100644
--- a/spdx/parsers/rdf.py
+++ b/spdx/parsers/rdf.py
@@ -1220,7 +1220,7 @@ def get_relationship(self, subject_term, relation_term):
for sub, pre, rel_ele in self.graph.triples(
(relation_term, self.spdx_namespace["relatedSpdxElement"], None)
):
- related_element = str(rel_ele.split("#")[1])
+ related_element = str(rel_ele.split("#")[1]) if '#' in rel_ele else rel_ele
except:
related_element = None
diff --git a/spdx/relationship.py b/spdx/relationship.py
index 980990ddc..742315b7a 100644
--- a/spdx/relationship.py
+++ b/spdx/relationship.py
@@ -10,18 +10,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from enum import Enum
-from spdx.parsers.loggers import ErrorMessages
-
-# Implement the auto feature that becomes available in 3.6
-autoinc = 0
-
+from enum import auto, Enum
-def auto():
- global autoinc
- autoval = autoinc
- autoinc += 1
- return autoval
+from spdx.parsers.loggers import ErrorMessages
class RelationshipType(Enum):
@@ -82,33 +73,37 @@ def __init__(self, relationship=None, relationship_comment=None):
self.relationship = relationship
self.relationship_comment = relationship_comment
+ def __eq__(self, other):
+ return (
+ isinstance(other, Relationship)
+ and self.relationship == other.relationship
+ )
+
@property
def has_comment(self):
return self.relationship_comment is not None
@property
- def spdxelementid(self):
+ def spdx_element_id(self):
return self.relationship.split(" ")[0]
@property
- def relationshiptype(self):
+ def relationship_type(self):
return self.relationship.split(" ")[1]
@property
- def relatedspdxelement(self):
+ def related_spdx_element(self):
return self.relationship.split(" ")[2]
- def validate(self, messages):
+ def validate(self, messages: ErrorMessages) -> ErrorMessages:
"""
Check that all the fields are valid.
Appends any error messages to messages parameter shall be a ErrorMessages.
"""
- self.validate_relationship(messages)
-
- def validate_relationship(self, messages):
- r_type = self.relationship.split(" ")[1]
+ r_type = self.relationship_type
if r_type not in [name for name, _ in RelationshipType.__members__.items()]:
messages.append(
"Relationship type must be one of the constants defined in "
"class spdx.relationship.Relationship"
)
+ return messages
diff --git a/spdx/utils.py b/spdx/utils.py
index d40113ce2..fc8b9d13e 100644
--- a/spdx/utils.py
+++ b/spdx/utils.py
@@ -73,6 +73,9 @@ def to_value(self):
def __str__(self):
return self.to_value()
+ def __repr__(self):
+ return self.to_value()
+
class UnKnown(object):
"""
@@ -91,6 +94,7 @@ def __repr__(self):
def __eq__(self, other):
return self.to_value() == other.to_value()
+
class SPDXNone(object):
"""
Represent SPDX None value.
diff --git a/spdx/writers/json.py b/spdx/writers/json.py
index 0177cd104..a2b226fdf 100644
--- a/spdx/writers/json.py
+++ b/spdx/writers/json.py
@@ -14,6 +14,14 @@
from spdx.writers.tagvalue import InvalidDocumentError
from spdx.writers.jsonyamlxml import Writer
from spdx.parsers.loggers import ErrorMessages
+import datetime
+
+
+def json_converter(obj):
+ if isinstance(obj, datetime.datetime):
+ return str(obj)
+ else:
+ raise TypeError("No implementation available to serialize objects of type " + type(obj).__name__)
def write_document(document, out, validate=True):
@@ -26,4 +34,4 @@ def write_document(document, out, validate=True):
writer = Writer(document)
document_object = writer.create_document()
- json.dump(document_object, out, indent=4)
+ json.dump(document_object, out, indent=4, default=json_converter)
diff --git a/spdx/writers/jsonyamlxml.py b/spdx/writers/jsonyamlxml.py
index 2abf31a93..9bb78c731 100644
--- a/spdx/writers/jsonyamlxml.py
+++ b/spdx/writers/jsonyamlxml.py
@@ -331,16 +331,16 @@ def create_relationship_info(self):
relationship_objects = []
for relationship_term in self.document.relationships:
- if relationship_term.relationshiptype == "DESCRIBES":
+ if relationship_term.relationship_type == "DESCRIBES":
continue
- if relationship_term.relationshiptype == "CONTAINS":
+ if relationship_term.relationship_type == "CONTAINS":
continue
relationship_object = dict()
- relationship_object["spdxElementId"] = relationship_term.spdxelementid
+ relationship_object["spdxElementId"] = relationship_term.spdx_element_id
relationship_object[
"relatedSpdxElement"
- ] = relationship_term.relatedspdxelement
- relationship_object["relationshipType"] = relationship_term.relationshiptype
+ ] = relationship_term.related_spdx_element
+ relationship_object["relationshipType"] = relationship_term.relationship_type
if relationship_term.has_comment:
relationship_object["comment"] = relationship_term.relationship_comment
@@ -398,9 +398,12 @@ def __init__(self, document):
def create_extracted_license(self):
extracted_license_objects = []
- extracted_licenses = self.document.extracted_licenses
+ unique_extracted_licenses = {}
+ for lic in self.document.extracted_licenses:
+ if lic.identifier not in unique_extracted_licenses.keys():
+ unique_extracted_licenses[lic.identifier] = lic
- for extracted_license in extracted_licenses:
+ for extracted_license in unique_extracted_licenses.values():
extracted_license_object = dict()
if isinstance(extracted_license.identifier, Literal):
@@ -495,8 +498,8 @@ def create_document_describes(self):
document_describes = []
remove_rel = []
for relationship in self.document.relationships:
- if relationship.relationshiptype == "DESCRIBES":
- document_describes.append(relationship.relatedspdxelement)
+ if relationship.relationship_type == "DESCRIBES":
+ document_describes.append(relationship.related_spdx_element)
if not relationship.has_comment:
remove_rel.append(relationship)
for relationship in remove_rel:
@@ -516,9 +519,14 @@ def create_document(self):
document_describes = self.create_document_describes()
self.document_object["documentDescribes"] = document_describes
+ unique_doc_packages = {}
+ for doc_package in self.document.packages:
+ if doc_package.spdx_id not in unique_doc_packages.keys():
+ unique_doc_packages[doc_package.spdx_id] = doc_package
+
package_objects = []
file_objects = []
- for package in self.document.packages:
+ for package in unique_doc_packages.values():
package_info_object, files_in_package = self.create_package_info(package)
package_objects.append(package_info_object)
file_objects.extend(file for file in files_in_package if file not in file_objects)
diff --git a/spdx/writers/rdf.py b/spdx/writers/rdf.py
index 004e030dd..e700fadc7 100644
--- a/spdx/writers/rdf.py
+++ b/spdx/writers/rdf.py
@@ -24,6 +24,7 @@
from spdx import utils
from spdx.package import Package
from spdx.parsers.loggers import ErrorMessages
+from spdx.relationship import Relationship
from spdx.writers.tagvalue import InvalidDocumentError
import warnings
@@ -536,15 +537,31 @@ class RelationshipInfoWriter(BaseWriter):
def __init__(self, document, out):
super(RelationshipInfoWriter, self).__init__(document, out)
- def create_relationship_node(self, relationship):
+ def transform_relationship_type_to_rdf_model(self, relationship_type: str) -> str:
"""
- Return an relationship node.
+ Transform relationship type from upper snake case to camel case to match rdf-specific output e.g.
+ COPY_OF -> relationshipType_copyOf.
"""
- relationship_node = URIRef(str(relationship.spdxelementid))
+ return "relationshipType_" + relationship_type[0].lower() + relationship_type.title().replace('_', '')[1:]
+
+ def create_relationship_node(self, relationship: Relationship) -> BNode:
+ """
+ Return a relationship node.
+ """
+ relationship_node = BNode()
type_triple = (relationship_node, RDF.type, self.spdx_namespace.Relationship)
self.graph.add(type_triple)
- relationship_type_node = Literal(relationship.relationshiptype)
+ relationship_spdx_element_id = Literal(relationship.spdx_element_id)
+ self.graph.add(
+ (
+ relationship_node,
+ self.spdx_namespace.spdxElementId,
+ relationship_spdx_element_id,
+ )
+ )
+ relationship_type = self.transform_relationship_type_to_rdf_model(relationship.relationship_type)
+ relationship_type_node = self.spdx_namespace[relationship_type]
self.graph.add(
(
relationship_node,
@@ -552,7 +569,7 @@ def create_relationship_node(self, relationship):
relationship_type_node,
)
)
- related_spdx_node = Literal(relationship.relatedspdxelement)
+ related_spdx_node = Literal(relationship.related_spdx_element)
related_spdx_triple = (
relationship_node,
self.spdx_namespace.relatedSpdxElement,
@@ -1019,6 +1036,7 @@ def write(self, doc_node=None):
for package_node in self.packages():
package_triple = (doc_node, self.spdx_namespace.describesPackage, package_node)
self.graph.add(package_triple)
+
# Add external package reference
for pkg_ext_ref_node in self.pkg_ext_refs():
pkg_ext_ref_triple = (
@@ -1027,10 +1045,12 @@ def write(self, doc_node=None):
pkg_ext_ref_node,
)
self.graph.add(pkg_ext_ref_triple)
- """# Add relationship
- relate_node = self.relationships()
- relate_triple = (doc_node, self.spdx_namespace.relationship, relate_node)
- self.graph.add(relate_triple)"""
+
+ # Add relationship
+ for relate_node in self.relationships():
+ relate_triple = (doc_node, self.spdx_namespace.relationship, relate_node)
+ self.graph.add(relate_triple)
+
# Add snippet
snippet_nodes = self.snippets()
for snippet in snippet_nodes:
diff --git a/spdx/writers/yaml.py b/spdx/writers/yaml.py
index 24600d8a7..2ccc6884d 100644
--- a/spdx/writers/yaml.py
+++ b/spdx/writers/yaml.py
@@ -27,4 +27,4 @@ def write_document(document, out, validate=True):
writer = Writer(document)
document_object = writer.create_document()
- yaml.safe_dump(document_object, out, indent=2, explicit_start=True)
+ yaml.safe_dump(document_object, out, indent=2, explicit_start=True, encoding='utf-8')
diff --git a/tests/data/doc_write/rdf-simple-plus.json b/tests/data/doc_write/rdf-simple-plus.json
index 5f84b380f..f7711a568 100644
--- a/tests/data/doc_write/rdf-simple-plus.json
+++ b/tests/data/doc_write/rdf-simple-plus.json
@@ -61,8 +61,16 @@
"@rdf:resource": "http://spdx.org/licenses/LGPL-2.1-or-later"
}
}
- },
+ },
+ "ns1:relationship": {
+ "ns1:Relationship": {
+ "ns1:spdxElementId": "SPDXRef-DOCUMENT",
+ "ns1:relatedSpdxElement": "SPDXRef-Package",
+ "ns1:relationshipType": {
+ "@rdf:resource": "http://spdx.org/rdf/terms#relationshipType_describes"
+ }
+ }},
"@rdf:about": "http://www.spdx.org/tools#SPDXRef-DOCUMENT"
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/data/doc_write/rdf-simple.json b/tests/data/doc_write/rdf-simple.json
index 7327c703f..a2717510f 100644
--- a/tests/data/doc_write/rdf-simple.json
+++ b/tests/data/doc_write/rdf-simple.json
@@ -61,8 +61,17 @@
},
"ns1:fileName": "./some/path/tofile"
}
- },
+ },
+ "ns1:relationship": {
+ "ns1:Relationship": {
+ "ns1:spdxElementId": "SPDXRef-DOCUMENT",
+ "ns1:relatedSpdxElement": "SPDXRef-Package",
+ "ns1:relationshipType": {
+ "@rdf:resource": "http://spdx.org/rdf/terms#relationshipType_describes"
+ }
+ }
+ },
"@rdf:about": "http://www.spdx.org/tools#SPDXRef-DOCUMENT"
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/data/doc_write/xml-simple-plus.xml b/tests/data/doc_write/xml-simple-plus.xml
index f3b2bec73..6de34d2a8 100644
--- a/tests/data/doc_write/xml-simple-plus.xml
+++ b/tests/data/doc_write/xml-simple-plus.xml
@@ -1,43 +1,37 @@
-
-
- SPDX-2.1
- CC0-1.0
- Sample_Document-V2.1
- SPDXRef-DOCUMENT
- https://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301
- SPDXRef-Package
-
- SPDXRef-Package
- some/path
- NOASSERTION
- Some copyrught
-
- SOME code
-
-
- SOME-SHA1
- SHA1
-
- NOASSERTION
- NOASSERTION
- LGPL-2.1-or-later
-
- SPDXRef-File
-
-
-
- ./some/path/tofile
- SPDXRef-File
-
- SOME-SHA1
- SHA1
-
- NOASSERTION
- NOASSERTION
- LGPL-2.1-or-later
-
-
-
-
-
\ No newline at end of file
+
+ SPDX-2.1
+ CC0-1.0
+ Sample_Document-V2.1
+ SPDXRef-DOCUMENT
+ https://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301
+ SPDXRef-Package
+
+ SPDXRef-Package
+ some/path
+ NOASSERTION
+ Some copyrught
+
+ SOME code
+
+
+ SOME-SHA1
+ SHA1
+
+ NOASSERTION
+ NOASSERTION
+ LGPL-2.1-or-later
+ SPDXRef-File
+
+
+ ./some/path/tofile
+ SPDXRef-File
+
+ SOME-SHA1
+ SHA1
+
+ NOASSERTION
+ NOASSERTION
+ LGPL-2.1-or-later
+
+
diff --git a/tests/data/doc_write/xml-simple.xml b/tests/data/doc_write/xml-simple.xml
index 9eaca485b..f45ca9707 100644
--- a/tests/data/doc_write/xml-simple.xml
+++ b/tests/data/doc_write/xml-simple.xml
@@ -20,7 +20,6 @@
NOASSERTION
NOASSERTION
-
LGPL-2.1-only
SPDXRef-File
@@ -35,4 +34,4 @@
NOASSERTION
LGPL-2.1-only
-
\ No newline at end of file
+
diff --git a/tests/utils_test.py b/tests/utils_test.py
index 3d2c9630a..c1482aee5 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -138,7 +138,7 @@ def check_rdf_scan(expected_file, result_file, regen=False):
else:
with io.open(expected_file, 'r', encoding='utf-8') as i:
expected = sort_nested(json.load(i))
- assert expected == result
+ assert result == expected
def load_and_clean_tv(location):
@@ -165,7 +165,7 @@ def check_tv_scan(expected_file, result_file, regen=False):
o.write(result)
expected = load_and_clean_tv(expected_file)
- assert expected == result
+ assert result == expected
def load_and_clean_json(location):
@@ -195,7 +195,7 @@ def check_json_scan(expected_file, result_file, regen=False):
o.write(result)
expected = load_and_clean_json(expected_file)
- assert expected == result
+ assert result == expected
def load_and_clean_yaml(location):
@@ -225,7 +225,7 @@ def check_yaml_scan(expected_file, result_file, regen=False):
o.write(result)
expected = load_and_clean_yaml(expected_file)
- assert expected == result
+ assert result == expected
def load_and_clean_xml(location):
@@ -255,7 +255,7 @@ def check_xml_scan(expected_file, result_file, regen=False):
o.write(result)
expected = load_and_clean_xml(expected_file)
- assert expected == result
+ assert result == expected
class TestParserUtils(object):