Skip to content

Commit 257fbd5

Browse files
authored
Merge pull request #56 from dice-group/owl_to_sentence
Owl to sentence
2 parents 085bfb7 + 24a28d7 commit 257fbd5

File tree

5 files changed

+128
-22
lines changed

5 files changed

+128
-22
lines changed

owlapy/iri.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def __init__(self, namespace: Union[str, Namespaces], remainder: str):
4343
if isinstance(namespace, Namespaces):
4444
namespace = namespace.ns
4545
else:
46-
assert namespace[-1] in ("/", ":", "#")
46+
assert namespace[-1] in ("/", ":", "#"), "It should be a valid IRI based on /, :, and #"
4747
import sys
4848
self._namespace = sys.intern(namespace)
4949
self._remainder = remainder

owlapy/owl_reasoner.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from owlapy.owl_axiom import OWLAxiom, OWLSubClassOfAxiom
2222
from owlapy.owl_data_ranges import OWLDataRange, OWLDataComplementOf, OWLDataUnionOf, OWLDataIntersectionOf
2323
from owlapy.owl_datatype import OWLDatatype
24+
from owlapy.owl_object import OWLEntity
2425
from owlapy.owl_ontology import OWLOntology, Ontology, _parse_concept_to_owlapy, ToOwlready2
2526
from owlapy.owl_ontology_manager import OntologyManager
2627
from owlapy.owl_property import OWLObjectPropertyExpression, OWLDataProperty, OWLObjectProperty, OWLObjectInverseOf, \
@@ -187,16 +188,19 @@ def equivalent_data_properties(self, dp: OWLDataProperty) -> Iterable[OWLDataPro
187188
pass
188189

189190
@abstractmethod
190-
def data_property_values(self, ind: OWLNamedIndividual, pe: OWLDataProperty, direct: bool = True) \
191+
def data_property_values(self, e: OWLEntity, pe: OWLDataProperty, direct: bool = True) \
191192
-> Iterable['OWLLiteral']:
192-
"""Gets the data property values for the specified individual and data property expression.
193+
"""Gets the data property values for the specified entity and data property expression.
193194
194195
Args:
195-
ind: The individual that is the subject of the data property values.
196-
pe: The data property expression whose values are to be retrieved for the specified individual.
196+
e: The owl entity (usually an individual) that is the subject of the data property values.
197+
pe: The data property expression whose values are to be retrieved for the specified entity.
197198
direct: Specifies if the direct values should be retrieved (True), or if all values should be retrieved
198199
(False), so that sub properties are taken into account.
199200
201+
Note: Can be used to get values, for example, of 'label' property of owl entities such as classes and properties
202+
too (not only individuals).
203+
200204
Returns:
201205
A set of OWLLiterals containing literals such that for each literal l in the set, the set of reasoner
202206
axioms entails DataPropertyAssertion(pe ind l).
@@ -610,9 +614,9 @@ def same_individuals(self, ind: OWLNamedIndividual) -> Iterable[OWLNamedIndividu
610614
yield from (OWLNamedIndividual(IRI.create(d_i.iri)) for d_i in i.equivalent_to
611615
if isinstance(d_i, owlready2.Thing))
612616

613-
def data_property_values(self, ind: OWLNamedIndividual, pe: OWLDataProperty, direct: bool = True) \
617+
def data_property_values(self, e: OWLEntity, pe: OWLDataProperty, direct: bool = True) \
614618
-> Iterable[OWLLiteral]:
615-
i: owlready2.Thing = self._world[ind.str]
619+
i: owlready2.Thing = self._world[e.str]
616620
p: owlready2.DataPropertyClass = self._world[pe.str]
617621
retrieval_func = p._get_values_for_individual if direct else p._get_indirect_values_for_individual
618622
for val in retrieval_func(i):
@@ -1140,9 +1144,9 @@ def different_individuals(self, ce: OWLNamedIndividual) -> Iterable[OWLNamedIndi
11401144
def same_individuals(self, ce: OWLNamedIndividual) -> Iterable[OWLNamedIndividual]:
11411145
yield from self._base_reasoner.same_individuals(ce)
11421146

1143-
def data_property_values(self, ind: OWLNamedIndividual, pe: OWLDataProperty, direct: bool = True) \
1147+
def data_property_values(self, e: OWLEntity, pe: OWLDataProperty, direct: bool = True) \
11441148
-> Iterable[OWLLiteral]:
1145-
yield from self._base_reasoner.data_property_values(ind, pe, direct)
1149+
yield from self._base_reasoner.data_property_values(e, pe, direct)
11461150

11471151
def all_data_property_values(self, pe: OWLDataProperty, direct: bool = True) -> Iterable[OWLLiteral]:
11481152
yield from self._base_reasoner.all_data_property_values(pe, direct)
@@ -1645,9 +1649,8 @@ def different_individuals(self, ind: OWLNamedIndividual) -> Iterable[OWLNamedInd
16451649
def same_individuals(self, ind: OWLNamedIndividual) -> Iterable[OWLNamedIndividual]:
16461650
yield from self.adaptor.same_individuals(ind)
16471651

1648-
def data_property_values(self, ind: OWLNamedIndividual, pe: OWLDataProperty, direct: bool = True) -> Iterable[
1649-
OWLLiteral]:
1650-
yield from self.adaptor.data_property_values(ind, pe)
1652+
def data_property_values(self, e: OWLEntity, pe: OWLDataProperty, direct: bool = True) -> Iterable[OWLLiteral]:
1653+
yield from self.adaptor.data_property_values(e, pe)
16511654

16521655
def object_property_values(self, ind: OWLNamedIndividual, pe: OWLObjectPropertyExpression, direct: bool = False) -> \
16531656
Iterable[OWLNamedIndividual]:

owlapy/owlapi_adaptor.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from owlapy.class_expression import OWLClassExpression
1010
from owlapy.owl_individual import OWLNamedIndividual
11+
from owlapy.owl_object import OWLEntity
1112
from owlapy.owl_property import OWLDataProperty, OWLObjectProperty
1213
from typing import List
1314

@@ -389,19 +390,19 @@ def object_property_values(self, i: OWLNamedIndividual, p: OWLObjectProperty):
389390
yield from [self.mapper.map_(ind) for ind in
390391
self.reasoner.getObjectPropertyValues(self.mapper.map_(i), self.mapper.map_(p)).getFlattened()]
391392

392-
def data_property_values(self, i: OWLNamedIndividual, p: OWLDataProperty):
393-
"""Gets the data property values for the specified individual and data property expression.
393+
def data_property_values(self, e: OWLEntity, p: OWLDataProperty):
394+
"""Gets the data property values for the specified entity and data property expression.
394395
395396
Args:
396-
i: The individual that is the subject of the data property values.
397+
e: The entity (usually an individual) that is the subject of the data property values.
397398
p: The data property expression whose values are to be retrieved for the specified individual.
398399
399400
Returns:
400401
A set of OWLLiterals containing literals such that for each literal l in the set, the set of reasoner
401402
axioms entails DataPropertyAssertion(pe ind l).
402403
"""
403404
yield from [self.mapper.map_(literal) for literal in
404-
to_list(self.reasoner.dataPropertyValues(self.mapper.map_(i), self.mapper.map_(p)))]
405+
to_list(self.reasoner.dataPropertyValues(self.mapper.map_(e), self.mapper.map_(p)))]
405406

406407
def disjoint_object_properties(self, p: OWLObjectProperty):
407408
"""Gets the simplified object properties that are disjoint with the specified object property with respect

owlapy/render.py

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
from owlapy import namespaces
99
from .iri import IRI
10-
from .owl_individual import OWLNamedIndividual
10+
from .owl_individual import OWLNamedIndividual, OWLIndividual
1111
from .owl_literal import OWLLiteral
1212
from .owl_object import OWLObjectRenderer, OWLEntity, OWLObject
13-
from .owl_property import OWLObjectInverseOf, OWLPropertyExpression
13+
from .owl_property import OWLObjectInverseOf, OWLPropertyExpression, OWLDataProperty, OWLObjectProperty
1414
from .class_expression import OWLClassExpression, OWLBooleanClassExpression, OWLClass, OWLObjectSomeValuesFrom, \
1515
OWLObjectAllValuesFrom, OWLObjectUnionOf, OWLObjectIntersectionOf, OWLObjectComplementOf, OWLObjectMinCardinality, \
1616
OWLObjectExactCardinality, OWLObjectMaxCardinality, OWLObjectHasSelf, OWLDataSomeValuesFrom, OWLDataAllValuesFrom, \
@@ -20,8 +20,11 @@
2020
from .owl_data_ranges import OWLNaryDataRange, OWLDataComplementOf, OWLDataUnionOf, OWLDataIntersectionOf
2121
from .class_expression import OWLObjectHasValue, OWLFacetRestriction, OWLDatatypeRestriction, OWLObjectOneOf
2222
from .owl_datatype import OWLDatatype
23-
24-
23+
from .owl_reasoner import OWLReasoner
24+
from typing import Union, Tuple
25+
import requests
26+
import warnings
27+
import abc
2528
_DL_SYNTAX = types.SimpleNamespace(
2629
SUBCLASS="⊑",
2730
EQUIVALENT_TO="≡",
@@ -57,6 +60,104 @@ def _simple_short_form_provider(e: OWLEntity) -> str:
5760
return sf
5861

5962

63+
mapper = {
64+
'OWLNamedIndividual': "http://www.w3.org/2002/07/owl#NamedIndividual",
65+
'OWLObjectProperty': "http://www.w3.org/2002/07/owl#ObjectProperty",
66+
'OWLDataProperty': "http://www.w3.org/2002/07/owl#DatatypeProperty",
67+
'OWLClass': "http://www.w3.org/2002/07/owl#Class"
68+
}
69+
70+
71+
def translating_short_form_provider(e: OWLEntity, reasoner, rules: dict[str:str] = None) -> str:
72+
"""
73+
e: entity.
74+
reasoner: OWLReasoner or Triplestore(from Ontolearn)
75+
rules: A mapping from OWLEntity to predicates,
76+
Keys in rules can be general or specific iris, e.g.,
77+
IRI to IRI s.t. the second IRI must be a predicate leading to literal
78+
"""
79+
label_iri = "http://www.w3.org/2000/01/rdf-schema#label"
80+
81+
def get_label(entity, r, predicate=label_iri):
82+
if isinstance(r, OWLReasoner):
83+
values = list(r.data_property_values(entity, OWLDataProperty(predicate)))
84+
if values:
85+
return str(values[0].get_literal())
86+
else:
87+
return _simple_short_form_provider(entity)
88+
else:
89+
# else we have a TripleStore
90+
sparql = f"""select ?o where {{ <{entity.str}> <{predicate}> ?o}}"""
91+
if results := list(r.query(sparql)):
92+
return str(results[0])
93+
else:
94+
return _simple_short_form_provider(entity)
95+
96+
if rules is None:
97+
return get_label(e, reasoner)
98+
else:
99+
# Check if a rule exist for a specific IRI:
100+
# (e.g "http://www.example.org/SomeSpecificClass":"http://www.example.org/SomePredicate")
101+
# WARNING: If the entity is an OWLClass, the rule specified for that class will only be used to replace the
102+
# class itself not individuals belonging to that class. The reason for that is that the entity can also be a
103+
# property and properties does not classify individuals. So to avoid confusion, the specified predicate in the
104+
# rules will only be used to 'label' the specified entity.
105+
if specific_predicate := rules.get(e.str, None):
106+
return get_label(e, reasoner, specific_predicate)
107+
# Check if a rule exist for a general IRI:
108+
# (e.g "http://www.w3.org/2002/07/owl#NamedIndividual":"http://www.example.org/SomePredicate")
109+
# then it will label any entity of that type using the value retrieved from the given predicate.
110+
elif general_predicate := rules.get(mapper[e.__class__.__name__], None):
111+
return get_label(e, reasoner, general_predicate)
112+
# No specific rule set, use http://www.w3.org/2000/01/rdf-schema#label (by default)
113+
else:
114+
return get_label(e, reasoner)
115+
116+
117+
def translating_short_form_endpoint(e: OWLEntity, endpoint: str,
118+
rules: dict[abc.ABCMeta:str] = None) -> str:
119+
"""
120+
Translates an OWLEntity to a short form string using provided rules and an endpoint.
121+
122+
Parameters:
123+
e (OWLEntity): The OWL entity to be translated.
124+
endpoint (str): The endpoint of a triple store to query against.
125+
rules (dict[abc.ABCMeta:str], optional): A dictionary mapping OWL classes to string IRIs leading to a literal.
126+
127+
Returns:
128+
str: The translated short form of the OWL entity. If no matching rules are found, a simple short form is returned.
129+
130+
This function iterates over the provided rules to check if the given OWL entity is an instance of any specified class.
131+
If a match is found, it constructs a SPARQL query to retrieve the literal value associated with the entity and predicate.
132+
If a literal is found, it is returned as the short form. If no literals are found, the SPARQL query and entity information
133+
are printed for debugging purposes. If no matching rules are found, a warning is issued and a simple short form is returned.
134+
135+
136+
Example:
137+
>>> e = OWLEntity("http://example.org/entity")
138+
>>> endpoint = "http://example.org/sparql"
139+
>>> rules = {SomeOWLClass: "http://example.org/predicate"}
140+
>>> translating_short_form_endpoint(e, endpoint, rules)
141+
"""
142+
# () Iterate over rules
143+
for owlapy_class, str_predicate in rules.items():
144+
# () Check whether an OWL entity is an instance of specified class
145+
if isinstance(e, owlapy_class):
146+
sparql = f"""select ?o where {{ <{e.str}> <{str_predicate}> ?o}}"""
147+
response = requests.post(url=endpoint, data={"query": sparql})
148+
results = response.json()["results"]["bindings"]
149+
if len(results) > 0:
150+
return results[0]["o"]["value"]
151+
else:
152+
print(sparql)
153+
print(f"No literal found\n{sparql}\n{e}")
154+
continue
155+
156+
warnings.warn(f"No matching rules for OWL Entity:{e}!")
157+
# No mathing rule found
158+
return _simple_short_form_provider(e)
159+
160+
60161
class DLSyntaxObjectRenderer(OWLObjectRenderer):
61162
"""DL Syntax renderer for OWL Objects."""
62163
__slots__ = '_sfp'
@@ -226,7 +327,7 @@ def _render_operands(self, c: OWLNaryBooleanClassExpression) -> List[str]:
226327

227328
def _render_nested(self, c: OWLClassExpression) -> str:
228329
if isinstance(c, OWLBooleanClassExpression) or isinstance(c, OWLRestriction) \
229-
or isinstance(c, OWLNaryDataRange):
330+
or isinstance(c, OWLNaryDataRange):
230331
return "(%s)" % self.render(c)
231332
else:
232333
return self.render(c)
@@ -420,7 +521,7 @@ def _render_operands(self, c: OWLNaryBooleanClassExpression) -> List[str]:
420521

421522
def _render_nested(self, c: OWLClassExpression) -> str:
422523
if isinstance(c, OWLBooleanClassExpression) or isinstance(c, OWLRestriction) \
423-
or isinstance(c, OWLNaryDataRange):
524+
or isinstance(c, OWLNaryDataRange):
424525
return "(%s)" % self.render(c)
425526
else:
426527
return self.render(c)

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
},
1414
install_requires=[
1515
"pandas>=1.5.0",
16+
"requests>=2.32.3",
1617
"rdflib>=6.0.2",
1718
"parsimonious>=0.8.1",
1819
"pytest>=8.1.1",

0 commit comments

Comments
 (0)