Repository navigation
POC: JSON-LD parsing without rdflib #696
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| # SPDX-FileCopyrightText: 2022 spdx contributors | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| import json | ||
| from typing import Union | ||
|
|
||
| from beartype.typing import Dict, List | ||
| from semantic_version import Version | ||
|
|
||
| from spdx_tools.spdx3.model import HashAlgorithm | ||
| from spdx_tools.spdx3.parser.model_definition_dicts import ( | ||
| CLASS_DICT, | ||
| DATETIME_PROPERTIES, | ||
| ENUM_DICT, | ||
| KEYS_WITH_URI_VALUES, | ||
| SPECIALLY_TREATED_KEYS, | ||
| VERSION_PROPERTIES, | ||
| ) | ||
| from spdx_tools.spdx3.payload import Payload | ||
| from spdx_tools.spdx.casing_tools import camel_case_to_snake_case | ||
| from spdx_tools.spdx.datetime_conversions import datetime_from_str | ||
|
|
||
|
|
||
| def parse_from_file(file_name: str) -> Payload: | ||
| with open(file_name) as file: | ||
| input_as_dict: Dict = json.load(file) | ||
|
|
||
| context = input_as_dict["@context"] | ||
| custom_context = {} | ||
| if isinstance(context, list): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if |
||
| for entry in context: | ||
| if isinstance(entry, dict): | ||
| custom_context.update(entry) | ||
|
|
||
| if "@graph" in input_as_dict: | ||
| payload = parse_list_of_elements(input_as_dict["@graph"], custom_context) | ||
| else: # in case of only a single serialized element | ||
| input_as_dict.pop("@context") | ||
| payload = Payload() | ||
| payload.add_element(parse_element_object(input_as_dict, custom_context)) | ||
|
|
||
| return payload | ||
|
|
||
|
|
||
| def parse_list_of_elements(list_of_elements: List[Dict], custom_context: Dict): | ||
| payload = Payload() | ||
| for element_object in list_of_elements: | ||
| payload.add_element(parse_element_object(element_object, custom_context)) | ||
| return payload | ||
|
|
||
|
|
||
| def parse_element_object(element_object: Union[Dict, str], custom_context: Dict): | ||
| if isinstance(element_object, str): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, JSON also supports numbers, booleans and null directly. Don't we need to handle them in similar fashion? |
||
| return element_object | ||
|
|
||
| element_properties = {} | ||
| if "@id" in element_object: | ||
| element_properties["spdx_id"] = expand_uri(element_object.pop("@id"), custom_context) | ||
|
|
||
| element_type = element_object.pop("type") | ||
|
|
||
| for key, value in element_object.items(): | ||
| if key in ENUM_DICT: | ||
| value = parse_enum_property(key, value) | ||
| elif isinstance(value, dict): | ||
| value = parse_element_object(value, custom_context) | ||
| elif isinstance(value, list): | ||
| value = [parse_element_object(entry, custom_context) for entry in value] | ||
| elif key in VERSION_PROPERTIES: | ||
| value = Version(value) | ||
| elif key in DATETIME_PROPERTIES: | ||
| value = datetime_from_str(value) | ||
|
|
||
| if key in KEYS_WITH_URI_VALUES: | ||
| if isinstance(value, list): | ||
| value = [expand_uri(entry, custom_context) for entry in value] | ||
| else: | ||
| value = expand_uri(value, custom_context) | ||
|
|
||
| if key in SPECIALLY_TREATED_KEYS: | ||
| element_properties[SPECIALLY_TREATED_KEYS[key]] = value | ||
| else: | ||
| element_properties[camel_case_to_snake_case(key)] = value | ||
|
|
||
| if element_type not in CLASS_DICT: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be moved before the |
||
| raise ValueError(f"element type {element_type} not found in CLASS_DICT. Maybe it was forgotten?") | ||
| return CLASS_DICT[element_type](**element_properties) | ||
|
|
||
|
|
||
| def expand_uri(spdx_id, custom_context): | ||
| if ":" in spdx_id: | ||
| prefix, suffix = spdx_id.split(":", 1) | ||
| if prefix in custom_context and not suffix.startswith("//"): | ||
| spdx_id = custom_context[prefix] + suffix | ||
| return spdx_id | ||
|
|
||
|
|
||
| def parse_enum_property(key: str, value: str): | ||
| enum_class = ENUM_DICT[key] | ||
| if enum_class == HashAlgorithm: | ||
| return parse_hash_algorithm(value) | ||
| if isinstance(value, str): | ||
| return enum_class[camel_case_to_snake_case(value).upper()] | ||
| elif isinstance(value, list): | ||
| return [enum_class[camel_case_to_snake_case(entry).upper()] for entry in value] | ||
| else: | ||
| raise ValueError(f"unrecognized Enum property: {key}: {value}") | ||
|
|
||
|
|
||
| def parse_hash_algorithm(value: str): | ||
| if value.startswith("blake"): | ||
| return HashAlgorithm[value.upper()] | ||
|
|
||
| return HashAlgorithm[camel_case_to_snake_case(value).upper()] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| # SPDX-FileCopyrightText: 2023 spdx contributors | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| from spdx_tools.spdx3.model import ( | ||
| Agent, | ||
| Annotation, | ||
| AnnotationType, | ||
| Bom, | ||
| Bundle, | ||
| CreationInfo, | ||
| ExternalIdentifier, | ||
| ExternalIdentifierType, | ||
| ExternalMap, | ||
| ExternalReference, | ||
| ExternalReferenceType, | ||
| Hash, | ||
| HashAlgorithm, | ||
| LifecycleScopedRelationship, | ||
| LifecycleScopeType, | ||
| NamespaceMap, | ||
| Organization, | ||
| Person, | ||
| ProfileIdentifier, | ||
| Relationship, | ||
| RelationshipCompleteness, | ||
| RelationshipType, | ||
| SoftwareAgent, | ||
| SpdxDocument, | ||
| Tool, | ||
| ) | ||
| from spdx_tools.spdx3.model.ai import AIPackage | ||
| from spdx_tools.spdx3.model.build import Build | ||
| from spdx_tools.spdx3.model.dataset import Dataset | ||
| from spdx_tools.spdx3.model.positive_integer_range import PositiveIntegerRange | ||
| from spdx_tools.spdx3.model.software import ( | ||
| DependencyConditionalityType, | ||
| File, | ||
| Package, | ||
| Sbom, | ||
| SBOMType, | ||
| Snippet, | ||
| SoftwareDependencyLinkType, | ||
| SoftwareDependencyRelationship, | ||
| SoftwarePurpose, | ||
| ) | ||
|
|
||
| CLASS_DICT = { | ||
| "Agent": Agent, | ||
| "AIPackage": AIPackage, | ||
| "Annotation": Annotation, | ||
| "Bom": Bom, | ||
| "Build": Build, | ||
| "Bundle": Bundle, | ||
| "CreationInfo": CreationInfo, | ||
| "Dataset": Dataset, | ||
| "ExternalIdentifier": ExternalIdentifier, | ||
| "ExternalMap": ExternalMap, | ||
| "ExternalReference": ExternalReference, | ||
| "File": File, | ||
| "Hash": Hash, | ||
| "LifecycleScopedRelationship": LifecycleScopedRelationship, | ||
| "NamespaceMap": NamespaceMap, | ||
| "Organization": Organization, | ||
| "Package": Package, | ||
| "Person": Person, | ||
| "PositiveIntegerRange": PositiveIntegerRange, | ||
| "Relationship": Relationship, | ||
| "Sbom": Sbom, | ||
| "Snippet": Snippet, | ||
| "SoftwareAgent": SoftwareAgent, | ||
| "SoftwareDependencyRelationship": SoftwareDependencyRelationship, | ||
| "SpdxDocument": SpdxDocument, | ||
| "Tool": Tool, | ||
| } | ||
| ENUM_DICT = { | ||
| "algorithm": HashAlgorithm, | ||
| "annotationType": AnnotationType, | ||
| "completeness": RelationshipCompleteness, | ||
| "conditionality": DependencyConditionalityType, | ||
| "externalIdentifierType": ExternalIdentifierType, | ||
| "externalReferenceType": ExternalReferenceType, | ||
| "profile": ProfileIdentifier, | ||
| "primaryPurpose": SoftwarePurpose, | ||
| "additionalPurpose": SoftwarePurpose, | ||
| "relationshipType": RelationshipType, | ||
| "sbomType": SBOMType, | ||
| "scope": LifecycleScopeType, | ||
| "softwareLinkage": SoftwareDependencyLinkType, | ||
| } | ||
| SPECIALLY_TREATED_KEYS = {"from": "from_element"} | ||
| KEYS_WITH_URI_VALUES = [ | ||
| "createdBy", | ||
| "createdUsing", | ||
| "dataLicense", | ||
| "from", | ||
| "originatedBy", | ||
| "subject", | ||
| "suppliedBy", | ||
| "to", | ||
| ] | ||
| DATETIME_PROPERTIES = ["created", "builtTime", "releaseTime", "validUntilTime"] | ||
| VERSION_PROPERTIES = ["specVersion"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably the wrong import, should just be
typing?