From ccd24f671723f65ee5ea1b8c314d2a4bb1090a2b Mon Sep 17 00:00:00 2001 From: jdsika Date: Sat, 26 Sep 2026 13:53:26 +0200 Subject: [PATCH] feat(owl): encode ISO 19150-2 enumerations as OWL 2 datatype definitions rule-owl-cls-iso191502Enumeration adds owl:oneOf directly to the named rdfs:Datatype of an enumeration. The OWL 2 mapping to RDF does not read that pattern as a data range, so OWL 2 tools do not see the literals of the enumeration. The OWL API reads it as EquivalentClasses(DT ObjectOneOf()), and its OWL 2 DL profile check reports EmptyOneOfAxiom, DatatypeIRIAlsoUsedAsClassIRI and UseOfUndeclaredClass. The new target parameter iso191502EnumerationAsDatatypeDefinition (default false, so the output is unchanged) encodes the enumeration as the datatype definition DatatypeDefinition( DT DataOneOf( lt1 ... ltn ) ) of the OWL 2 Structural Specification, Sec. 9.4: the named rdfs:Datatype is owl:equivalentClass to an anonymous rdfs:Datatype that carries the owl:oneOf list (OWL 2 Mapping to RDF Graphs, Sec. 2.1, Table 1). OWL 2 DL requires such a definition for every datatype that is not built in (Sec. 11.2). The documentation of the rule and of the new parameter describes both forms. It also names the two cases that stay outside OWL 2 DL with the parameter set: an enumeration without literals, which DataOneOf cannot express, and an enumeration that a stereotype or type conversion parameter makes a subclass, since OWL 2 has no subclass relation between datatypes. The new integration test testOwl_iso191502EnumerationAsDatatypeDefinition runs the model of testOwl_enumerationDatatypeProperty with the parameter set. That existing test pins the default form and the new one the datatype definition, so one of them fails if the parameter is ignored or always applied. Under the OWL API 5.5.0 OWL2DLProfile, the five violations that the enumeration causes in the default reference are gone from the new one, which the OWL API reads as DatatypeDefinition(ex:ExampleEnumeration DataOneOf("alpha" "beta" "gamma")). Signed-off-by: jdsika --- ...ML_to_RDF_OWL_based_on_ISO_IS_19150_2.adoc | 54 ++++++ .../reference/owl/INPUT/test.ttl | 47 ++++++ .../reference/owl/INPUT/test_codes.ttl | 23 +++ ...o191502EnumerationAsDatatypeDefinition.xml | 157 ++++++++++++++++++ ...ationAsDatatypeDefinition_runWithSCXML.xml | 157 ++++++++++++++++++ .../shapechange/app/OntologyTest.java | 21 +++ .../core/target/ontology/OWLISO19150.java | 25 +++ .../OWLISO19150ConfigurationValidator.java | 3 +- .../core/target/ontology/OntologyModel.java | 15 +- 9 files changed, 500 insertions(+), 2 deletions(-) create mode 100644 shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/reference/owl/INPUT/test.ttl create mode 100644 shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/reference/owl/INPUT/test_codes.ttl create mode 100644 shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/testEA_owl_iso191502EnumerationAsDatatypeDefinition.xml create mode 100644 shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/testEA_owl_iso191502EnumerationAsDatatypeDefinition_runWithSCXML.xml diff --git a/shapechange-app/src/docs/asciidoc/targets/ontology/UML_to_RDF_OWL_based_on_ISO_IS_19150_2.adoc b/shapechange-app/src/docs/asciidoc/targets/ontology/UML_to_RDF_OWL_based_on_ISO_IS_19150_2.adoc index de42b0cad..bae30ec67 100644 --- a/shapechange-app/src/docs/asciidoc/targets/ontology/UML_to_RDF_OWL_based_on_ISO_IS_19150_2.adoc +++ b/shapechange-app/src/docs/asciidoc/targets/ontology/UML_to_RDF_OWL_based_on_ISO_IS_19150_2.adoc @@ -377,6 +377,22 @@ This rule implements the conversion of enumerations as defined by ISO The enumeration corresponds to an RDFS datatype that specifies the restricted list of literals using an owl:oneOf declaration. +By default, the owl:oneOf declaration is added to the named RDFS +datatype itself. The OWL 2 mapping to RDF does not read this as an +enumeration of literals: an OWL 2 datatype is given a value space by a +datatype definition, which in RDF is an owl:equivalentClass from the +named datatype to an anonymous data range (see the +https://www.w3.org/TR/owl2-mapping-to-rdf/#Translation_of_Axioms_without_Annotations[OWL 2 Mapping to RDF Graphs, Section 2.1, Table 1]). +With target parameter +xref:./UML_to_RDF_OWL_based_on_ISO_IS_19150_2.adoc#iso191502EnumerationAsDatatypeDefinition[_iso191502EnumerationAsDatatypeDefinition_] +set to _true_, the enumeration is encoded in this form: + +[source,turtle,linenumbers] +---------- +ex:Colour a rdfs:Datatype ; + owl:equivalentClass [ a rdfs:Datatype ; owl:oneOf ( "red" "green" ) ] . +---------- + [[rule-owl-cls-iso191502IsAbstract]] ==== rule-owl-cls-iso191502IsAbstract @@ -1119,6 +1135,44 @@ Applies to Rule(s): _none_ - this is a common target parameter +[[iso191502EnumerationAsDatatypeDefinition]] +==== iso191502EnumerationAsDatatypeDefinition + +(since v4.1.0) + +Type: Boolean + +Default Value: _false_ + +Explanation: + +If _true_, an enumeration is encoded as an OWL 2 datatype definition +(https://www.w3.org/TR/owl2-syntax/#Datatype_Definitions[OWL 2 +Structural Specification, Section 9.4]): the named RDFS datatype is +owl:equivalentClass to an anonymous RDFS datatype, which carries the +owl:oneOf list of the enumeration's literals. OWL 2 tools then see the +enumeration as `DatatypeDefinition( DT DataOneOf( ... ) )`, as OWL 2 DL +requires of a datatype that is not built in +(https://www.w3.org/TR/owl2-syntax/#The_Restrictions_on_the_Axiom_Closure[OWL +2 Structural Specification, Section 11.2]). By default, the owl:oneOf +list is added to the named datatype itself, which the OWL API, for +example, reads as an empty enumeration of individuals. + +This does not hold if a stereotype or type conversion parameter adds +rdfs:subClassOf to the enumeration. OWL 2 has no subclass relation +between datatypes, so the datatype is then also used as a class, and +the OWL API, for example, again reads an empty enumeration of +individuals. + +An enumeration without any literals is encoded as a named RDFS datatype +without a definition, whether or not this parameter is set, because +DataOneOf needs at least one literal. Such a datatype is not valid in +OWL 2 DL. + +Applies to Rule(s): + +* xref:./UML_to_RDF_OWL_based_on_ISO_IS_19150_2.adoc#rule-owl-cls-iso191502Enumeration[rule-owl-cls-iso191502Enumeration] + [[language]] ==== language diff --git a/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/reference/owl/INPUT/test.ttl b/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/reference/owl/INPUT/test.ttl new file mode 100644 index 000000000..fa1e0e187 --- /dev/null +++ b/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/reference/owl/INPUT/test.ttl @@ -0,0 +1,47 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix skos: . +@prefix xsd: . + +ex:Road.name rdf:type owl:DatatypeProperty; + rdfs:domain ex:Road; + rdfs:range xsd:string . + +ex:Road rdf:type owl:Class; + rdfs:subClassOf [ rdf:type owl:Restriction; + owl:onDataRange ex:ExampleEnumeration; + owl:onProperty ex:Road.surface; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger + ]; + rdfs:subClassOf [ rdf:type owl:Restriction; + owl:onDataRange xsd:string; + owl:onProperty ex:Road.name; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger + ]; + rdfs:subClassOf [ rdf:type owl:Restriction; + owl:onClass ex:ExampleCodeList; + owl:onProperty ex:Road.category; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger + ] . + +ex:Road.category rdf:type owl:ObjectProperty; + rdfs:domain ex:Road; + rdfs:range ex:ExampleCodeList . + +ex:ExampleCodeList rdf:type owl:Class; + rdfs:subClassOf skos:Concept . + +ex:ExampleEnumeration + rdf:type rdfs:Datatype; + owl:equivalentClass [ rdf:type rdfs:Datatype; + owl:oneOf ( "alpha" "beta" "gamma" ) + ] . + +ex:Road.surface rdf:type owl:DatatypeProperty; + rdfs:domain ex:Road; + rdfs:range ex:ExampleEnumeration . + + + rdf:type owl:Ontology . diff --git a/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/reference/owl/INPUT/test_codes.ttl b/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/reference/owl/INPUT/test_codes.ttl new file mode 100644 index 000000000..00c226026 --- /dev/null +++ b/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/reference/owl/INPUT/test_codes.ttl @@ -0,0 +1,23 @@ +@prefix dct: . +@prefix ex: . +@prefix excode: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix skos: . +@prefix xsd: . + + + rdf:type owl:Ontology . + +excode:ExampleCodeList + rdf:type skos:ConceptScheme; + dct:isFormatOf ex:ExampleCodeList . + + + rdf:type ex:ExampleCodeList; + skos:inScheme excode:ExampleCodeList . + + + rdf:type ex:ExampleCodeList; + skos:inScheme excode:ExampleCodeList . diff --git a/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/testEA_owl_iso191502EnumerationAsDatatypeDefinition.xml b/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/testEA_owl_iso191502EnumerationAsDatatypeDefinition.xml new file mode 100644 index 000000000..d55302243 --- /dev/null +++ b/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/testEA_owl_iso191502EnumerationAsDatatypeDefinition.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/testEA_owl_iso191502EnumerationAsDatatypeDefinition_runWithSCXML.xml b/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/testEA_owl_iso191502EnumerationAsDatatypeDefinition_runWithSCXML.xml new file mode 100644 index 000000000..8b7918842 --- /dev/null +++ b/shapechange-app/src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/testEA_owl_iso191502EnumerationAsDatatypeDefinition_runWithSCXML.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/OntologyTest.java b/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/OntologyTest.java index 4c3df59bb..819b8d792 100644 --- a/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/OntologyTest.java +++ b/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/OntologyTest.java @@ -146,4 +146,25 @@ public void testOwl_enumerationDatatypeProperty() { new String[] { "ttl" }, "testResults/owl/enumerationDatatypeProperty", "src/integrationtests/owl/enumerationDatatypeProperty/reference"); } + + @Test + public void testOwl_iso191502EnumerationAsDatatypeDefinition() { + + /* + * With target parameter iso191502EnumerationAsDatatypeDefinition, + * rule-owl-cls-iso191502Enumeration encodes an enumeration as the OWL 2 datatype + * definition DatatypeDefinition( DT DataOneOf( lt1 ... ltn ) ), which maps to + * the named DT being owl:equivalentClass to an anonymous rdfs:Datatype with the + * owl:oneOf list (W3C OWL 2 Mapping to RDF Graphs, Sec. 2.1, Table 1). + * + * The model is the one of testOwl_enumerationDatatypeProperty, which pins the + * default encoding, with owl:oneOf on the named datatype. The rest of the output + * is unchanged: Road.surface stays a data property whose qualified cardinality + * restriction uses owl:onDataRange with the named datatype. + */ + multiTest( + "src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/testEA_owl_iso191502EnumerationAsDatatypeDefinition.xml", + new String[] { "ttl" }, "testResults/owl/iso191502EnumerationAsDatatypeDefinition", + "src/integrationtests/owl/iso191502EnumerationAsDatatypeDefinition/reference"); + } } diff --git a/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OWLISO19150.java b/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OWLISO19150.java index 553d5fa20..d57725338 100644 --- a/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OWLISO19150.java +++ b/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OWLISO19150.java @@ -317,6 +317,17 @@ public class OWLISO19150 implements SingleTarget, MessageSource { public static final String PARAM_SUPPRESS_MESSAGES_FOR_UNSUPPORTED_CLASS_CATEGORY = "suppressMessagesForUnsupportedCategoryOfClasses"; + /** + * If set to true, {@value #RULE_OWL_CLS_ISO191502_ENUMERATION} + * encodes an enumeration as an OWL 2 datatype definition: the named + * rdfs:Datatype is owl:equivalentClass to an anonymous rdfs:Datatype that + * carries the owl:oneOf list of literals (W3C OWL 2 Structural Specification, + * Sec. 9.4; OWL 2 Mapping to RDF Graphs, Sec. 2.1, Table 1). By + * default, the owl:oneOf list is added directly to the named datatype, which + * the OWL 2 mapping to RDF does not read as an enumeration of literals. + */ + public static final String PARAM_ISO191502_ENUMERATION_AS_DATATYPE_DEFINITION = "iso191502EnumerationAsDatatypeDefinition"; + /** * key: a package, value: the according ontology object */ @@ -379,6 +390,7 @@ public class OWLISO19150 implements SingleTarget, MessageSource { private static String ontologyNameCode = null; private static String defaultTypeImplementation = null; private static boolean suppressMessagesForUnsupportedCategoryOfClasses = false; + private static boolean iso191502EnumerationAsDatatypeDefinition = false; private static String generalPropertyNamespaceAbbreviation = null; /** @@ -552,6 +564,9 @@ public void initialise(PackageInfo p, Model m, Options o, ShapeChangeResult r, b suppressMessagesForUnsupportedCategoryOfClasses = options.parameterAsBoolean(this.getClass().getName(), PARAM_SUPPRESS_MESSAGES_FOR_UNSUPPORTED_CLASS_CATEGORY, false); + iso191502EnumerationAsDatatypeDefinition = options.parameterAsBoolean(this.getClass().getName(), + PARAM_ISO191502_ENUMERATION_AS_DATATYPE_DEFINITION, false); + /* * Initialize an ontology for the package and - unless stated otherwise via a * rule - for the sub-packages in the same target namespace. Also create @@ -1273,6 +1288,7 @@ public void reset() { OWLISO19150.ontologyNameCode = null; OWLISO19150.defaultTypeImplementation = null; OWLISO19150.suppressMessagesForUnsupportedCategoryOfClasses = false; + OWLISO19150.iso191502EnumerationAsDatatypeDefinition = false; OWLISO19150.ontologyByPropertyConversionTargetReference = null; @@ -1372,6 +1388,15 @@ public boolean isSuppressMessagesForUnsupportedCategoryOfClasses() { return suppressMessagesForUnsupportedCategoryOfClasses; } + /** + * @return true if enumerations shall be encoded as datatype + * definitions, see + * {@value #PARAM_ISO191502_ENUMERATION_AS_DATATYPE_DEFINITION} + */ + public boolean isIso191502EnumerationAsDatatypeDefinition() { + return iso191502EnumerationAsDatatypeDefinition; + } + /** * @return the rdfNamespaceSeparator */ diff --git a/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OWLISO19150ConfigurationValidator.java b/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OWLISO19150ConfigurationValidator.java index 4af9dd315..9c2df4073 100644 --- a/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OWLISO19150ConfigurationValidator.java +++ b/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OWLISO19150ConfigurationValidator.java @@ -72,7 +72,8 @@ public class OWLISO19150ConfigurationValidator extends AbstractConfigurationVali .of(OWLISO19150.PARAM_CODE_LIST_OWL_CLASS_NAMESPACE, OWLISO19150.PARAM_CODE_LIST_OWL_CLASS_NAMESPACE_FOR_ENUMERATIONS, OWLISO19150.PARAM_CODE_NAMESPACE, OWLISO19150.PARAM_CODE_NAMESPACE_FOR_ENUMERATIONS, OWLISO19150.PARAM_DEFAULT_TYPE_IMPL, - OWLISO19150.PARAM_GENERAL_PROPERTY_NSABR, OWLISO19150.PARAM_LANGUAGE, + OWLISO19150.PARAM_GENERAL_PROPERTY_NSABR, + OWLISO19150.PARAM_ISO191502_ENUMERATION_AS_DATATYPE_DEFINITION, OWLISO19150.PARAM_LANGUAGE, OWLISO19150.PARAM_ONTOLOGYNAME_CODE_NAME, OWLISO19150.PARAM_ONTOLOGYNAME_TAGGED_VALUE_NAME, OWLISO19150.PARAM_OUTPUTFORMAT, OWLISO19150.PARAM_PROP_EXTERNAL_REFERENCE_TARGET_PROPERTY, OWLISO19150.PARAM_RDF_NAMESPACE_SEPARATOR, OWLISO19150.PARAM_RDFXMLWRITER_BLOCKRULES, diff --git a/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OntologyModel.java b/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OntologyModel.java index da646d282..7325e34d4 100644 --- a/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OntologyModel.java +++ b/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OntologyModel.java @@ -3223,7 +3223,20 @@ public Resource addEnumeration(ClassInfo ci) { Literal en = ontmodel.createLiteral(pi.name()); enums.add(en); } - e.addProperty(OWL2.oneOf, ontmodel.createList(enums.iterator())); + + if (owliso19150.isIso191502EnumerationAsDatatypeDefinition()) { + /* + * DatatypeDefinition( DT DataOneOf( lt1 ... ltn ) ): the enumeration of + * literals is an anonymous data range, to which the named datatype is + * equivalent (W3C OWL 2 Mapping to RDF Graphs, Sec. 2.1, Table 1). + */ + Resource dataRange = ontmodel + .createResource(ontmodel.createResource(OWLISO19150.RDF_NS_W3C_RDFS + "Datatype")); + dataRange.addProperty(OWL2.oneOf, ontmodel.createList(enums.iterator())); + e.addProperty(OWL2.equivalentClass, dataRange); + } else { + e.addProperty(OWL2.oneOf, ontmodel.createList(enums.iterator())); + } } return e; }