From 8bdabeb5cd846ea2311881e246389d0958cfd62e Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sat, 24 Jan 2026 09:22:20 +0000 Subject: [PATCH 1/3] Read base URI from actual model Use the Core namespace in the model as source Signed-off-by: Arthit Suriyawongkul --- spec_parser/rdf.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/spec_parser/rdf.py b/spec_parser/rdf.py index cc9b030..488f6df 100644 --- a/spec_parser/rdf.py +++ b/spec_parser/rdf.py @@ -10,19 +10,31 @@ from rdflib.namespace import DCTERMS, OWL, RDF, RDFS, SH, SKOS, XSD from rdflib.tools.rdf2dot import rdf2dot -URI_BASE = "https://spdx.org/rdf/3.1/terms/" logger = logging.getLogger(__name__) def gen_rdf(model, outpath, cfg): p = outpath - ret = gen_rdf_ontology(model) + base_uri = "https://spdx.org/rdf/3.1/terms/" + # Determine actual base URI from Core namespace: + # https://spdx.org/rdf//terms/Core/ + # -> https://spdx.org/rdf//terms/ + for ns in model.namespaces: + if ns.name == "Core": + iri = ns.iri + if iri.endswith("/Core/"): + base_uri = iri[: -len("Core/")] + elif iri.endswith("/Core"): + base_uri = iri[: -len("Core")] + break + + ret = gen_rdf_ontology(model, base_uri) for ext in ["hext", "json-ld", "longturtle", "n3", "nt", "pretty-xml", "trig", "ttl", "xml"]: f = p / ("spdx-model." + ext) ret.serialize(f, format=ext, encoding="utf-8") - ctx = jsonld_context(ret) + ctx = jsonld_context(ret, base_uri) fn = p / "spdx-context.jsonld" with fn.open("w") as f: json.dump(ctx, f, sort_keys=True, indent=2) @@ -40,13 +52,13 @@ def xsd_range(rng, propname): return None -def gen_rdf_ontology(model): +def gen_rdf_ontology(model, base_uri: str): g = Graph() - g.bind("spdx", Namespace(URI_BASE)) + g.bind("spdx", Namespace(base_uri)) OMG_ANN = Namespace("https://www.omg.org/spec/Commons/AnnotationVocabulary/") g.bind("omg-ann", OMG_ANN) - node = URIRef(URI_BASE) + node = URIRef(base_uri) g.add((node, RDF.type, OWL.Ontology)) g.add((node, OWL.versionIRI, node)) g.add((node, RDFS.label, Literal("System Package Data Exchange™ (SPDX®) Ontology", lang="en"))) @@ -71,7 +83,7 @@ def gen_rdf_ontology(model): gen_rdf_properties(model, g) # gen_rdf_datatypes(model, g) gen_rdf_vocabularies(model, g) - gen_rdf_individuals(model, g) + gen_rdf_individuals(model, g, base_uri) return g @@ -233,9 +245,9 @@ def gen_rdf_vocabularies(model, g): g.add((enode, RDFS.comment, Literal(d, lang="en"))) -def gen_rdf_individuals(model, g): +def gen_rdf_individuals(model, g, base_uri: str): def ci_ref(s): - return URIRef(URI_BASE + "Core/" + s) + return URIRef(base_uri + "Core/" + s) for i in model.individuals.values(): ci_node = URIRef("https://spdx.org/rdf/3.1/creationInfo_" + i.name) @@ -259,7 +271,7 @@ def ci_ref(s): g.add((node, OWL.sameAs, URIRef(custom_iri))) -def jsonld_context(g): +def jsonld_context(g, base_uri: str): terms = dict() def get_subject_term(subject): @@ -308,7 +320,7 @@ def get_subject_term(subject): except ValueError: continue - if base != URI_BASE.rstrip("/"): + if base != base_uri.rstrip("/"): continue key = name if ns == "Core" else ns.lower() + "_" + name @@ -320,7 +332,7 @@ def get_subject_term(subject): terms[key] = get_subject_term(subject) - terms["spdx"] = URI_BASE + terms["spdx"] = base_uri terms["spdxId"] = "@id" terms["type"] = "@type" From b21312b53a5f9aa0b914d96d5e47415fc30015e9 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Wed, 20 May 2026 13:48:13 +0100 Subject: [PATCH 2/3] Use "3" version in term IRI Signed-off-by: Arthit Suriyawongkul --- spec_parser/rdf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec_parser/rdf.py b/spec_parser/rdf.py index 488f6df..ce7c693 100644 --- a/spec_parser/rdf.py +++ b/spec_parser/rdf.py @@ -16,7 +16,7 @@ def gen_rdf(model, outpath, cfg): p = outpath - base_uri = "https://spdx.org/rdf/3.1/terms/" + base_uri = "https://spdx.org/rdf/3/terms/" # Determine actual base URI from Core namespace: # https://spdx.org/rdf//terms/Core/ # -> https://spdx.org/rdf//terms/ @@ -250,7 +250,7 @@ def ci_ref(s): return URIRef(base_uri + "Core/" + s) for i in model.individuals.values(): - ci_node = URIRef("https://spdx.org/rdf/3.1/creationInfo_" + i.name) + ci_node = URIRef("https://spdx.org/rdf/3/creationInfo_" + i.name) g.add((ci_node, RDF.type, ci_ref("CreationInfo"))) g.add((ci_node, RDFS.comment, Literal("This individual element was defined by the spec.", lang="en"))) g.add((ci_node, ci_ref("created"), Literal("2026-01-23T03:01:00Z", datatype=XSD.dateTimeStamp))) From 9aab759a31d1576d0bc2e2f3bd2d8ad88a281a06 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Wed, 20 May 2026 14:02:25 +0100 Subject: [PATCH 3/3] Add owl:versionInfo Signed-off-by: Arthit Suriyawongkul --- spec_parser/rdf.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/spec_parser/rdf.py b/spec_parser/rdf.py index ce7c693..a8e92c1 100644 --- a/spec_parser/rdf.py +++ b/spec_parser/rdf.py @@ -60,7 +60,22 @@ def gen_rdf_ontology(model, base_uri: str): node = URIRef(base_uri) g.add((node, RDF.type, OWL.Ontology)) - g.add((node, OWL.versionIRI, node)) + + # owl:versionInfo / owl:versionIRI from namespace metadata. + # Ontology IRI is stable across minor releases (major ver. only in path). + # versionIRI identifies the specific release by replacing the major version + # segment with the full version: https://spdx.org/rdf/3/terms/ (ontology) + # -> https://spdx.org/rdf/3.1/terms/ (versionIRI for 3.1 release). + # W3C OWL 2 Section 3.1: + # https://www.w3.org/TR/owl2-syntax/#Ontology_IRI_and_Version_IRI + for ns in model.namespaces: + v = ns.metadata.get("version") or ns.metadata.get("Version") + if v: + g.add((node, OWL.versionInfo, Literal(v))) + major = v.split(".")[0] + versioned_iri = URIRef(base_uri.replace(f"/{major}/", f"/{v}/", 1)) + g.add((node, OWL.versionIRI, versioned_iri)) + break g.add((node, RDFS.label, Literal("System Package Data Exchange™ (SPDX®) Ontology", lang="en"))) g.add( (