Skip to content
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

Owl to sentence #56

Merged
merged 10 commits into from
Aug 6, 2024
2 changes: 1 addition & 1 deletion owlapy/iri.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def __init__(self, namespace: Union[str, Namespaces], remainder: str):
if isinstance(namespace, Namespaces):
namespace = namespace.ns
else:
assert namespace[-1] in ("/", ":", "#")
assert namespace[-1] in ("/", ":", "#"), "It should be a valid IRI based on /, :, and #"
import sys
self._namespace = sys.intern(namespace)
self._remainder = remainder
Expand Down
25 changes: 14 additions & 11 deletions owlapy/owl_reasoner.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from owlapy.owl_axiom import OWLAxiom, OWLSubClassOfAxiom
from owlapy.owl_data_ranges import OWLDataRange, OWLDataComplementOf, OWLDataUnionOf, OWLDataIntersectionOf
from owlapy.owl_datatype import OWLDatatype
from owlapy.owl_object import OWLEntity
from owlapy.owl_ontology import OWLOntology, Ontology, _parse_concept_to_owlapy, ToOwlready2
from owlapy.owl_ontology_manager import OntologyManager
from owlapy.owl_property import OWLObjectPropertyExpression, OWLDataProperty, OWLObjectProperty, OWLObjectInverseOf, \
Expand Down Expand Up @@ -187,16 +188,19 @@ def equivalent_data_properties(self, dp: OWLDataProperty) -> Iterable[OWLDataPro
pass

@abstractmethod
def data_property_values(self, ind: OWLNamedIndividual, pe: OWLDataProperty, direct: bool = True) \
def data_property_values(self, e: OWLEntity, pe: OWLDataProperty, direct: bool = True) \
-> Iterable['OWLLiteral']:
"""Gets the data property values for the specified individual and data property expression.
"""Gets the data property values for the specified entity and data property expression.

Args:
ind: The individual that is the subject of the data property values.
pe: The data property expression whose values are to be retrieved for the specified individual.
e: The owl entity (usually an individual) that is the subject of the data property values.
pe: The data property expression whose values are to be retrieved for the specified entity.
direct: Specifies if the direct values should be retrieved (True), or if all values should be retrieved
(False), so that sub properties are taken into account.

Note: Can be used to get values, for example, of 'label' property of owl entities such as classes and properties
too (not only individuals).

Returns:
A set of OWLLiterals containing literals such that for each literal l in the set, the set of reasoner
axioms entails DataPropertyAssertion(pe ind l).
Expand Down Expand Up @@ -610,9 +614,9 @@ def same_individuals(self, ind: OWLNamedIndividual) -> Iterable[OWLNamedIndividu
yield from (OWLNamedIndividual(IRI.create(d_i.iri)) for d_i in i.equivalent_to
if isinstance(d_i, owlready2.Thing))

def data_property_values(self, ind: OWLNamedIndividual, pe: OWLDataProperty, direct: bool = True) \
def data_property_values(self, e: OWLEntity, pe: OWLDataProperty, direct: bool = True) \
-> Iterable[OWLLiteral]:
i: owlready2.Thing = self._world[ind.str]
i: owlready2.Thing = self._world[e.str]
p: owlready2.DataPropertyClass = self._world[pe.str]
retrieval_func = p._get_values_for_individual if direct else p._get_indirect_values_for_individual
for val in retrieval_func(i):
Expand Down Expand Up @@ -1140,9 +1144,9 @@ def different_individuals(self, ce: OWLNamedIndividual) -> Iterable[OWLNamedIndi
def same_individuals(self, ce: OWLNamedIndividual) -> Iterable[OWLNamedIndividual]:
yield from self._base_reasoner.same_individuals(ce)

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

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

def data_property_values(self, ind: OWLNamedIndividual, pe: OWLDataProperty, direct: bool = True) -> Iterable[
OWLLiteral]:
yield from self.adaptor.data_property_values(ind, pe)
def data_property_values(self, e: OWLEntity, pe: OWLDataProperty, direct: bool = True) -> Iterable[OWLLiteral]:
yield from self.adaptor.data_property_values(e, pe)

def object_property_values(self, ind: OWLNamedIndividual, pe: OWLObjectPropertyExpression, direct: bool = False) -> \
Iterable[OWLNamedIndividual]:
Expand Down
9 changes: 5 additions & 4 deletions owlapy/owlapi_adaptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from owlapy.class_expression import OWLClassExpression
from owlapy.owl_individual import OWLNamedIndividual
from owlapy.owl_object import OWLEntity
from owlapy.owl_property import OWLDataProperty, OWLObjectProperty
from typing import List

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

def data_property_values(self, i: OWLNamedIndividual, p: OWLDataProperty):
"""Gets the data property values for the specified individual and data property expression.
def data_property_values(self, e: OWLEntity, p: OWLDataProperty):
"""Gets the data property values for the specified entity and data property expression.

Args:
i: The individual that is the subject of the data property values.
e: The entity (usually an individual) that is the subject of the data property values.
p: The data property expression whose values are to be retrieved for the specified individual.

Returns:
A set of OWLLiterals containing literals such that for each literal l in the set, the set of reasoner
axioms entails DataPropertyAssertion(pe ind l).
"""
yield from [self.mapper.map_(literal) for literal in
to_list(self.reasoner.dataPropertyValues(self.mapper.map_(i), self.mapper.map_(p)))]
to_list(self.reasoner.dataPropertyValues(self.mapper.map_(e), self.mapper.map_(p)))]

def disjoint_object_properties(self, p: OWLObjectProperty):
"""Gets the simplified object properties that are disjoint with the specified object property with respect
Expand Down
113 changes: 107 additions & 6 deletions owlapy/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

from owlapy import namespaces
from .iri import IRI
from .owl_individual import OWLNamedIndividual
from .owl_individual import OWLNamedIndividual, OWLIndividual
from .owl_literal import OWLLiteral
from .owl_object import OWLObjectRenderer, OWLEntity, OWLObject
from .owl_property import OWLObjectInverseOf, OWLPropertyExpression
from .owl_property import OWLObjectInverseOf, OWLPropertyExpression, OWLDataProperty, OWLObjectProperty
from .class_expression import OWLClassExpression, OWLBooleanClassExpression, OWLClass, OWLObjectSomeValuesFrom, \
OWLObjectAllValuesFrom, OWLObjectUnionOf, OWLObjectIntersectionOf, OWLObjectComplementOf, OWLObjectMinCardinality, \
OWLObjectExactCardinality, OWLObjectMaxCardinality, OWLObjectHasSelf, OWLDataSomeValuesFrom, OWLDataAllValuesFrom, \
Expand All @@ -20,8 +20,11 @@
from .owl_data_ranges import OWLNaryDataRange, OWLDataComplementOf, OWLDataUnionOf, OWLDataIntersectionOf
from .class_expression import OWLObjectHasValue, OWLFacetRestriction, OWLDatatypeRestriction, OWLObjectOneOf
from .owl_datatype import OWLDatatype


from .owl_reasoner import OWLReasoner
from typing import Union, Tuple
import requests
import warnings
import abc
_DL_SYNTAX = types.SimpleNamespace(
SUBCLASS="⊑",
EQUIVALENT_TO="≡",
Expand Down Expand Up @@ -57,6 +60,104 @@ def _simple_short_form_provider(e: OWLEntity) -> str:
return sf


mapper = {
'OWLNamedIndividual': "http://www.w3.org/2002/07/owl#NamedIndividual",
'OWLObjectProperty': "http://www.w3.org/2002/07/owl#ObjectProperty",
'OWLDataProperty': "http://www.w3.org/2002/07/owl#DatatypeProperty",
'OWLClass': "http://www.w3.org/2002/07/owl#Class"
}


def translating_short_form_provider(e: OWLEntity, reasoner, rules: dict[str:str] = None) -> str:
"""
e: entity.
reasoner: OWLReasoner or Triplestore(from Ontolearn)
rules: A mapping from OWLEntity to predicates,
Keys in rules can be general or specific iris, e.g.,
IRI to IRI s.t. the second IRI must be a predicate leading to literal
"""
label_iri = "http://www.w3.org/2000/01/rdf-schema#label"

def get_label(entity, r, predicate=label_iri):
if isinstance(r, OWLReasoner):
values = list(r.data_property_values(entity, OWLDataProperty(predicate)))
if values:
return str(values[0].get_literal())
else:
return _simple_short_form_provider(entity)
else:
# else we have a TripleStore
sparql = f"""select ?o where {{ <{entity.str}> <{predicate}> ?o}}"""
if results := list(r.query(sparql)):
return str(results[0])
else:
return _simple_short_form_provider(entity)

if rules is None:
return get_label(e, reasoner)
else:
# Check if a rule exist for a specific IRI:
# (e.g "http://www.example.org/SomeSpecificClass":"http://www.example.org/SomePredicate")
# WARNING: If the entity is an OWLClass, the rule specified for that class will only be used to replace the
# class itself not individuals belonging to that class. The reason for that is that the entity can also be a
# property and properties does not classify individuals. So to avoid confusion, the specified predicate in the
# rules will only be used to 'label' the specified entity.
if specific_predicate := rules.get(e.str, None):
return get_label(e, reasoner, specific_predicate)
# Check if a rule exist for a general IRI:
# (e.g "http://www.w3.org/2002/07/owl#NamedIndividual":"http://www.example.org/SomePredicate")
# then it will label any entity of that type using the value retrieved from the given predicate.
elif general_predicate := rules.get(mapper[e.__class__.__name__], None):
return get_label(e, reasoner, general_predicate)
# No specific rule set, use http://www.w3.org/2000/01/rdf-schema#label (by default)
else:
return get_label(e, reasoner)


def translating_short_form_endpoint(e: OWLEntity, endpoint: str,
rules: dict[abc.ABCMeta:str] = None) -> str:
"""
Translates an OWLEntity to a short form string using provided rules and an endpoint.

Parameters:
e (OWLEntity): The OWL entity to be translated.
endpoint (str): The endpoint of a triple store to query against.
rules (dict[abc.ABCMeta:str], optional): A dictionary mapping OWL classes to string IRIs leading to a literal.

Returns:
str: The translated short form of the OWL entity. If no matching rules are found, a simple short form is returned.

This function iterates over the provided rules to check if the given OWL entity is an instance of any specified class.
If a match is found, it constructs a SPARQL query to retrieve the literal value associated with the entity and predicate.
If a literal is found, it is returned as the short form. If no literals are found, the SPARQL query and entity information
are printed for debugging purposes. If no matching rules are found, a warning is issued and a simple short form is returned.


Example:
>>> e = OWLEntity("http://example.org/entity")
>>> endpoint = "http://example.org/sparql"
>>> rules = {SomeOWLClass: "http://example.org/predicate"}
>>> translating_short_form_endpoint(e, endpoint, rules)
"""
# () Iterate over rules
for owlapy_class, str_predicate in rules.items():
# () Check whether an OWL entity is an instance of specified class
if isinstance(e, owlapy_class):
sparql = f"""select ?o where {{ <{e.str}> <{str_predicate}> ?o}}"""
response = requests.post(url=endpoint, data={"query": sparql})
results = response.json()["results"]["bindings"]
if len(results) > 0:
return results[0]["o"]["value"]
else:
print(sparql)
print(f"No literal found\n{sparql}\n{e}")
continue

warnings.warn(f"No matching rules for OWL Entity:{e}!")
# No mathing rule found
return _simple_short_form_provider(e)


class DLSyntaxObjectRenderer(OWLObjectRenderer):
"""DL Syntax renderer for OWL Objects."""
__slots__ = '_sfp'
Expand Down Expand Up @@ -226,7 +327,7 @@ def _render_operands(self, c: OWLNaryBooleanClassExpression) -> List[str]:

def _render_nested(self, c: OWLClassExpression) -> str:
if isinstance(c, OWLBooleanClassExpression) or isinstance(c, OWLRestriction) \
or isinstance(c, OWLNaryDataRange):
or isinstance(c, OWLNaryDataRange):
return "(%s)" % self.render(c)
else:
return self.render(c)
Expand Down Expand Up @@ -420,7 +521,7 @@ def _render_operands(self, c: OWLNaryBooleanClassExpression) -> List[str]:

def _render_nested(self, c: OWLClassExpression) -> str:
if isinstance(c, OWLBooleanClassExpression) or isinstance(c, OWLRestriction) \
or isinstance(c, OWLNaryDataRange):
or isinstance(c, OWLNaryDataRange):
return "(%s)" % self.render(c)
else:
return self.render(c)
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
install_requires=[
"pandas>=1.5.0",
"requests>=2.32.3",
"rdflib>=6.0.2",
"parsimonious>=0.8.1",
"pytest>=8.1.1",
Expand Down
Loading