From dd7f58696531395c3ea1816c4c3d5d189bb931df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20Dom=C3=ADnguez?= <43052541+kysrpex@users.noreply.github.com> Date: Wed, 16 Jun 2021 14:07:49 +0200 Subject: [PATCH] 554 Parse hasValue statements (#585) * Parse hasValue statements on DatatypeProperty restrictions. The object of the hasValue statement becomes the default value for the DatatypeProperty if no default has been defined using the cuba ontology. * Update tests to include hasValue restrictions. As the city ontology does not include such an example, the example from the GitLab issue https://gitlab.cc-asp.fraunhofer.de/ontology/applications/reaxpro/co2-activation/-/issues/9 has been used. Co-authored-by: Matthias Urban <42069939+urbanmatthias@users.noreply.github.com> --- osp/core/ontology/oclass.py | 10 ++++-- osp/core/ontology/oclass_restriction.py | 4 ++- tests/test_ontology_entity.py | 46 +++++++++++++++++++++++++ tests/test_restrictions_emmo.py | 3 +- 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/osp/core/ontology/oclass.py b/osp/core/ontology/oclass.py index 0c564c8a..d810e4bd 100644 --- a/osp/core/ontology/oclass.py +++ b/osp/core/ontology/oclass.py @@ -43,7 +43,8 @@ def attributes(self): for superclass in self.superclasses: for attr, v in self._get_attributes(superclass.iri).items(): x = attributes.get(attr, (None, None, None)) - x = (x[0] or v[0], x[1] or v[1], x[2] or v[2]) + x = (x[0] or v[0], False if x[0] or v[0] else x[1] or v[1], + x[2] or v[2]) attributes[attr] = x return attributes @@ -128,10 +129,12 @@ def _get_attributes(self, iri): if triple not in graph or isinstance(a_iri, BNode): continue a = self.namespace._namespace_registry.from_iri(a_iri) - default = self._get_default(a_iri, iri) + cuba_default = self._get_default(a_iri, iri) + restriction_default = graph.value(o, OWL.hasValue) + default = cuba_default or restriction_default dt, obligatory = self._get_datatype_for_restriction(o) obligatory = default is None and obligatory - attributes[a] = (self._get_default(a_iri, iri), obligatory, dt) + attributes[a] = (default, obligatory, dt) # TODO more cases return attributes @@ -144,6 +147,7 @@ def _get_datatype_for_restriction(self, r): dt = g.value(r, OWL.someValuesFrom) obligatory = dt is not None dt = dt or g.value(r, OWL.allValuesFrom) + dt = dt or g.value(r, OWL.hasValue) obligatory = obligatory or (r, OWL.cardinality) != 0 obligatory = obligatory or (r, OWL.minCardinality) != 0 return dt, obligatory diff --git a/osp/core/ontology/oclass_restriction.py b/osp/core/ontology/oclass_restriction.py index c172e0f0..738759cf 100644 --- a/osp/core/ontology/oclass_restriction.py +++ b/osp/core/ontology/oclass_restriction.py @@ -17,6 +17,7 @@ class QUANTIFIER(Enum): EXACTLY = 3 MIN = 4 MAX = 5 + VALUE = 6 class RTYPE(Enum): @@ -151,7 +152,8 @@ def _compute_target(self): (rdflib.OWL.allValuesFrom, QUANTIFIER.ONLY), (rdflib.OWL.cardinality, QUANTIFIER.EXACTLY), (rdflib.OWL.minCardinality, QUANTIFIER.MIN), - (rdflib.OWL.maxCardinality, QUANTIFIER.MAX) + (rdflib.OWL.maxCardinality, QUANTIFIER.MAX), + (rdflib.OWL.hasValue, QUANTIFIER.VALUE) ]: if self._check_quantifier(rdflib_predicate, quantifier): return True diff --git a/tests/test_ontology_entity.py b/tests/test_ontology_entity.py index a6085fab..9da475c1 100644 --- a/tests/test_ontology_entity.py +++ b/tests/test_ontology_entity.py @@ -249,6 +249,52 @@ def test_attribute_datatype(self): [1, 2] ) + def test_hasValue_statement(self): + """Test hasValue statement from the OWL ontology for data properties. + + The hasValue statement is an OWL restriction, and forces the individual + to be connected at least once to a specific literal through a specific + datatype restriction. + """ + graph = city._graph + namespace_registry = city._namespace_registry + + triples_hassymboldata = ((rdflib.URIRef("http://www.osp-core.com/city#" + "hasSymbolData"), + rdflib.RDF.type, + rdflib.OWL.DatatypeProperty), + ) + bnode = rdflib.BNode() + triples_restriction = ((bnode, rdflib.RDF.type, + rdflib.OWL.Restriction), + (bnode, rdflib.OWL.onProperty, + rdflib.URIRef("http://www.osp-core.com/city#" + "hasSymbolData")), + (bnode, rdflib.OWL.hasValue, + rdflib.Literal('C', + datatype=rdflib.XSD.string)), + ) + triples_carbonsymbol = ((rdflib.URIRef("http://www.osp-core.com/city#" + "CarbonSymbol"), + rdflib.RDF.type, rdflib.OWL.Class), + (rdflib.URIRef("http://www.osp-core.com/city#" + "CarbonSymbol"), + rdflib.RDFS.subClassOf, bnode), + ) + triples = (*triples_hassymboldata, *triples_carbonsymbol, + *triples_restriction) + + for triple in triples: + graph.add(triple) + namespace_registry.update_namespaces() + try: + carbon_symbol = city.CarbonSymbol() + self.assertEqual(carbon_symbol.hasSymbolData, 'C') + finally: + for triple in triples: + graph.remove(triple) + namespace_registry.update_namespaces() + if __name__ == "__main__": unittest.main() diff --git a/tests/test_restrictions_emmo.py b/tests/test_restrictions_emmo.py index 5c44e2f2..28224a46 100644 --- a/tests/test_restrictions_emmo.py +++ b/tests/test_restrictions_emmo.py @@ -27,7 +27,8 @@ rdflib.OWL.allValuesFrom: QUANTIFIER.ONLY, rdflib.OWL.cardinality: QUANTIFIER.EXACTLY, rdflib.OWL.minCardinality: QUANTIFIER.MIN, - rdflib.OWL.maxCardinality: QUANTIFIER.MAX} + rdflib.OWL.maxCardinality: QUANTIFIER.MAX, + rdflib.OWL.hasValue: QUANTIFIER.VALUE} rtypes = {OntologyRelationship: RTYPE.RELATIONSHIP_RESTRICTION, OntologyAttribute: RTYPE.ATTRIBUTE_RESTRICTION}