import re
from abc import ABCMeta, abstractmethod
from typing import List, Union
from owlapy.class_expression import OWLClass
from owlapy.iri import IRI
from owlapy.owl_datatype import OWLDatatype
from owlapy.owl_individual import OWLNamedIndividual
from owlapy.owl_literal import OWLLiteral
from owlapy.owl_property import OWLDataProperty, OWLObjectProperty
BUILTINS = ["add", "subtract", "multiply", "divide", "mod", "pow", "abs", "round", "floor", "ceiling",
"equal", "notEqual", "greaterThan", "lessThan", "greaterThanOrEqual", "lessThanOrEqual",
"stringConcat", "substring", "contains", "startsWith", "endsWith", "stringLength",
"matches", "normalizeSpace", "lowerCase", "upperCase", "dateTime", "addDayTimeDuration",
"subtractDateTimes", "year", "month", "day", "hour", "isInteger", "isString", "isDateTime",
"isBoolean", "isNumeric"]
DATATYPES = ["decimal", "integer", "nonNegativeInteger", "nonPositiveInteger", "positiveInteger", "negativeInteger",
"long", "double", "float", "boolean", "string", "date", "dateTime", "dateTimeStamp", "duration",
"time", "gYearMonth", "gMonthDay", "gYear", "gMonth", "gDay"]
SWRL = "http://www.w3.org/2003/11/swrl#"
SWRLB = "http://www.w3.org/2003/11/swrlb#"
[docs]
class Variable(metaclass=ABCMeta):
"""Represents a variable in SWRL syntax"""
iri: IRI # should have the correct namespace, e.g: http://www.w3.org/2003/11/swrl#x
def __init__(self, iri:Union[IRI, str]):
if isinstance(iri, str):
self.iri = IRI.create(iri)
elif isinstance(iri, IRI):
self.iri = iri
else:
raise TypeError(f"Expected 'iri' to be an instance of IRI or str, got {type(iri).__name__} instead ({iri!r}).")
[docs]
def is_i_variable(self):
if isinstance(self, IVariable):
return True
return False
[docs]
def is_d_variable(self):
if isinstance(self, DVariable):
return True
return False
[docs]
def __repr__(self):
return f"{type(self).__name__}({self.iri})"
[docs]
def __eq__(self, other):
if type(other) is type(self):
return self.iri == other.iri
return False
[docs]
def __str__(self):
return "?" + self.iri.remainder
[docs]
def __hash__(self):
return hash((type(self).__name__, self.iri))
[docs]
class DVariable(Variable):
"""Represents a data variable in SWRL syntax"""
[docs]
def __repr__(self):
return f"DVariable({self.iri})"
[docs]
class IVariable(Variable):
"""Represents a individual variable in SWRL syntax"""
[docs]
def __repr__(self):
return f"IVariable({self.iri})"
[docs]
class Atom(metaclass=ABCMeta):
"""Represents an Atom in SWRL syntax"""
[docs]
def __eq__(self, other):
if type(other) is type(self):
return self.__repr__() == other.__repr__()
return False
[docs]
@staticmethod
def from_string(atom_str: str, namespace: str, dp_predicates: List[str] = None):
"""
Parses a SWRL atom like 'parent(?x, ?y)' or 'person(?x)'.
Returns:
An Atom object.
"""
# Regular expression to extract predicate and variable list from a string atom
pattern = r'^\s*([a-zA-Z_][\w\-]*)\s*\(\s*([^\)]*)\s*\)\s*$'
match = re.match(pattern, atom_str)
if not match:
raise ValueError(f"Invalid SWRL atom: {atom_str}")
predicate = match.group(1)
raw_args = match.group(2)
args = [arg.strip() for arg in raw_args.split(',') if arg.strip()]
if len(args) == 1:
if predicate in DATATYPES:
if "?" in args[0]:
argument = DVariable(SWRL + args[0][1:])
else:
argument = OWLLiteral(args[0])
return DataRangeAtom(OWLDatatype("http://www.w3.org/2001/XMLSchema#" + predicate), argument)
else:
if "?" in args[0]:
argument = IVariable(SWRL + args[0][1:])
else:
argument = OWLNamedIndividual(namespace + args[0])
return ClassAtom(OWLClass(namespace + predicate), argument)
elif len(args) == 2 and predicate not in BUILTINS + ["sameAs", "differentFrom"]:
if dp_predicates is not None and predicate in dp_predicates:
if "?" in args[0]:
args[0] = IVariable(SWRL + args[0][1:])
else:
args[0] = OWLNamedIndividual(args[0])
if "?" in args[1]:
args[1] = DVariable(SWRL + args[1][1:])
else:
args[1] = OWLLiteral(args[1])
return DataPropertyAtom(OWLDataProperty(namespace + predicate), args[0], args[1])
else:
for i in range(0, 2):
if "?" in args[i]:
args[i] = IVariable(SWRL + args[i][1:])
else:
args[i] = OWLNamedIndividual(namespace + args[i])
return ObjectPropertyAtom(OWLObjectProperty(namespace + predicate), args[0], args[1])
elif len(args) == 2 and predicate == "sameAs":
for i in range(0, 2):
if "?" in args[i]:
args[i] = IVariable(SWRL + args[i][1:])
else:
args[i] = OWLNamedIndividual(namespace + args[i])
return SameAsAtom(args[0], args[1])
elif len(args) == 2 and predicate == "differentFrom":
for i in range(0, 2):
if "?" in args[i]:
args[i] = IVariable(namespace + args[i][1:])
else:
args[i] = OWLNamedIndividual(namespace + args[i])
return DifferentFromAtom(args[0], args[1])
elif predicate in BUILTINS:
for i in range(0, len(args)):
if "?" in args[i]:
args[i] = DVariable(SWRL + args[i][1:])
else:
args[i] = OWLLiteral(args[i])
return BuiltInAtom(IRI.create(SWRLB + predicate), args)
else:
raise ValueError(f"Invalid SWRL atom: {atom_str}")
[docs]
@abstractmethod
def is_class_assertion(self):
pass
[docs]
@abstractmethod
def is_property_assertion(self):
pass
[docs]
@abstractmethod
def is_same_as(self):
pass
[docs]
@abstractmethod
def is_different_from(self):
pass
[docs]
@abstractmethod
def is_builtin(self):
pass
[docs]
def r(argument):
"""Returns the right string format of a given argument depending on its type"""
if isinstance(argument, Variable):
return str(argument)
return argument.iri.remainder
[docs]
def t(argument):
"""Returns the representation for a given argument"""
return f"{type(argument).__name__}({argument.iri.str})"
[docs]
class ClassAtom(Atom):
"""Represents a class atom in SWRL syntax"""
argument1: Union[IVariable, OWLNamedIndividual]
cls: OWLClass
def __init__(self, cls: OWLClass, argument1: Union[IVariable, OWLNamedIndividual]):
if not isinstance(cls, OWLClass):
raise TypeError(f"Expected 'cls' to be an instance of OWLClass, got {type(cls).__name__} instead ({cls!r}).")
if not isinstance(argument1, (IVariable, OWLNamedIndividual)):
raise TypeError(f"Expected 'argument1' to be an instance of IVariable or OWLNamedIndividual, got {type(argument1).__name__} instead ({argument1!r}).")
self.cls = cls
self.argument1 = argument1
[docs]
def is_class_assertion(self):
return True
[docs]
def is_property_assertion(self):
return False
[docs]
def is_same_as(self):
return False
[docs]
def is_different_from(self):
return False
[docs]
def is_builtin(self):
return False
[docs]
def __str__(self):
return f"{r(self.cls)}({r(self.argument1)})"
[docs]
def __repr__(self):
return f"ClassAtom({t(self.cls)}, {t(self.argument1)})"
[docs]
def __hash__(self):
return hash(("ClassAtom",self.cls, self.argument1))
[docs]
def __eq__(self, other):
if type(other) is type(self):
return self.cls == other.cls and self.argument1 == other.argument1
return False
[docs]
class DataRangeAtom(Atom):
"""Represents a data range atom in SWRL syntax"""
argument1: Union[DVariable, OWLLiteral]
datatype: OWLDatatype
def __init__(self, datatype: OWLDatatype, argument1: Union[DVariable, OWLLiteral]):
if not isinstance(datatype, OWLDatatype):
raise TypeError(f"Expected 'datatype' to be an instance of OWLDatatype, got {type(datatype).__name__} instead ({datatype!r}).")
if not isinstance(argument1, (DVariable, OWLLiteral)):
raise TypeError(f"Expected 'argument1' to be an instance of DVariable or OWLLiteral, got {type(argument1).__name__} instead ({argument1!r}).")
self.datatype = datatype
self.argument1 = argument1
[docs]
def is_class_assertion(self):
return True
[docs]
def is_property_assertion(self):
return False
[docs]
def is_same_as(self):
return False
[docs]
def is_different_from(self):
return False
[docs]
def is_builtin(self):
return False
[docs]
def __str__(self):
return f"{r(self.datatype)}({r(self.argument1)})"
[docs]
def __repr__(self):
return f"DataRangeAtom({t(self.datatype)} {t(self.argument1)})"
[docs]
def __hash__(self):
return hash(("DataRangeAtom",self.datatype, self.argument1))
[docs]
def __eq__(self, other):
if type(other) is type(self):
return self.datatype == other.datatype and self.argument1 == other.argument1
return False
[docs]
class PropertyAtom(Atom, metaclass=ABCMeta):
"""Represents a property atom in SWRL syntax"""
def __init__(self, prop: Union[OWLObjectProperty, OWLDataProperty], argument1, argument2):
self.argument1 = argument1
self.argument2 = argument2
self.prop = prop
[docs]
def is_class_assertion(self):
return False
[docs]
def is_property_assertion(self):
return True
[docs]
def is_same_as(self):
return False
[docs]
def is_different_from(self):
return False
[docs]
def is_builtin(self):
return False
[docs]
def __str__(self):
return f"{r(self.prop)}({r(self.argument1)}, {r(self.argument2)})"
[docs]
class ObjectPropertyAtom(PropertyAtom):
"""Represents an object property atom in SWRL syntax"""
argument1: Union[OWLNamedIndividual, IVariable]
argument2: Union[OWLNamedIndividual, IVariable]
prop: OWLObjectProperty
def __init__(self, prop: OWLObjectProperty, argument1: Union[OWLNamedIndividual, IVariable],
argument2: Union[OWLNamedIndividual, IVariable]):
if not isinstance(prop, OWLObjectProperty):
raise TypeError(f"Expected 'prop' to be an instance of OWLObjectProperty, got {type(prop).__name__} instead ({prop!r}).")
if not isinstance(argument1, (OWLNamedIndividual, IVariable)):
raise TypeError(f"Expected 'argument1' to be an instance of OWLNamedIndividual or IVariable, got {type(argument1).__name__} instead ({argument1!r}).")
if not isinstance(argument2, (OWLNamedIndividual, IVariable)):
raise TypeError(f"Expected 'argument2' to be an instance of OWLNamedIndividual or IVariable, got {type(argument2).__name__} instead ({argument2!r}).")
super().__init__(prop, argument1, argument2)
[docs]
def __repr__(self):
return f"ObjectPropertyAtom({t(self.prop)}, {t(self.argument1)}, {t(self.argument2)})"
[docs]
def __hash__(self):
return hash(("ObjectPropertyAtom",self.prop, self.argument1, self.argument2))
[docs]
def __eq__(self, other):
if type(other) is type(self):
return self.prop == other.prop and self.argument1 == other.argument1 and self.argument2 == other.argument2
return False
[docs]
class DataPropertyAtom(PropertyAtom):
"""Represents a data property atom in SWRL syntax"""
argument1: Union[OWLNamedIndividual, IVariable]
argument2: Union[OWLLiteral, DVariable]
prop: OWLDataProperty
def __init__(self, prop: OWLDataProperty, argument1: Union[OWLNamedIndividual, IVariable],
argument2: Union[OWLLiteral, DVariable]):
if not isinstance(prop, OWLDataProperty):
raise TypeError(f"Expected 'prop' to be an instance of OWLDataProperty, got {type(prop).__name__} instead ({prop!r}).")
if not isinstance(argument1, (OWLNamedIndividual, IVariable)):
raise TypeError(f"Expected 'argument1' to be an instance of OWLNamedIndividual or IVariable, got {type(argument1).__name__} instead ({argument1!r}).")
if not isinstance(argument2, (OWLLiteral, DVariable)):
raise TypeError(f"Expected 'argument2' to be an instance of OWLLiteral or DVariable, got {type(argument2).__name__} instead ({argument2!r}).")
super().__init__(prop, argument1, argument2)
[docs]
def __repr__(self):
return f"DataPropertyAtom({t(self.prop)}, {t(self.argument1)}, {t(self.argument2)})"
[docs]
def __hash__(self):
return hash(("DataPropertyAtom",self.prop, self.argument1, self.argument2))
[docs]
def __eq__(self, other):
if type(other) is type(self):
return self.prop == other.prop and self.argument1 == other.argument1 and self.argument2 == other.argument2
return False
[docs]
class SameAsAtom(Atom):
"""Represents a 'same-as' atom in SWRL syntax"""
argument1: Union[IVariable, OWLNamedIndividual]
argument2: Union[IVariable, OWLNamedIndividual]
def __init__(self, argument1: Union[IVariable, OWLNamedIndividual], argument2: Union[IVariable, OWLNamedIndividual]):
if not isinstance(argument1, (IVariable, OWLNamedIndividual)):
raise TypeError(f"Expected 'argument1' to be an instance of IVariable or OWLNamedIndividual, got {type(argument1).__name__} instead ({argument1!r}).")
if not isinstance(argument2, (IVariable, OWLNamedIndividual)):
raise TypeError(f"Expected 'argument2' to be an instance of IVariable or OWLNamedIndividual, got {type(argument2).__name__} instead ({argument2!r}).")
self.argument1 = argument1
self.argument2 = argument2
[docs]
def is_class_assertion(self):
return False
[docs]
def is_property_assertion(self):
return False
[docs]
def is_same_as(self):
return True
[docs]
def is_different_from(self):
return False
[docs]
def is_builtin(self):
return False
[docs]
def __str__(self):
return f"sameAs({r(self.argument1)}, {r(self.argument2)})"
[docs]
def __repr__(self):
return f"SameAs({t(self.argument1)}, {t(self.argument2)})"
[docs]
def __hash__(self):
return hash(("SameAsAtom",self.argument1, self.argument2))
[docs]
def __eq__(self, other):
if type(other) is type(self):
return self.argument1 == other.argument1 and self.argument2 == other.argument2
return False
[docs]
class DifferentFromAtom(Atom):
"""Represents a 'different-from' atom in SWRL syntax"""
argument1: Union[IVariable, OWLNamedIndividual]
argument2: Union[IVariable, OWLNamedIndividual]
def __init__(self, argument1: Union[IVariable, OWLNamedIndividual], argument2: Union[IVariable, OWLNamedIndividual]):
if not isinstance(argument1, (IVariable, OWLNamedIndividual)):
raise TypeError(f"Expected 'argument1' to be an instance of IVariable or OWLNamedIndividual, got {type(argument1).__name__} instead ({argument1!r}).")
if not isinstance(argument2, (IVariable, OWLNamedIndividual)):
raise TypeError(f"Expected 'argument2' to be an instance of IVariable or OWLNamedIndividual, got {type(argument2).__name__} instead ({argument2!r}).")
self.argument1 = argument1
self.argument2 = argument2
[docs]
def is_class_assertion(self):
return False
[docs]
def is_property_assertion(self):
return False
[docs]
def is_same_as(self):
return False
[docs]
def is_different_from(self):
return True
[docs]
def is_builtin(self):
return False
[docs]
def __str__(self):
return f"differentFrom({r(self.argument1)}, {r(self.argument2)})"
[docs]
def __repr__(self):
return f"DifferentFrom({t(self.argument1)}, {t(self.argument2)})"
[docs]
def __hash__(self):
return hash(("DifferentFromAtom",self.argument1, self.argument2))
[docs]
def __eq__(self, other):
if type(other) is type(self):
return self.argument1 == other.argument1 and self.argument2 == other.argument2
return False
[docs]
class BuiltInAtom(Atom):
"""Represents a built-in atom in SWRL syntax"""
predicate: IRI # should have the correct prefix, e.g: http://www.w3.org/2003/11/swrlb#divide
arguments: List[Union[IVariable, DVariable, OWLLiteral]]
def __init__(self, predicate: IRI, arguments: List[Union[IVariable, DVariable, OWLLiteral]]):
if not isinstance(predicate, IRI):
raise TypeError(f"Expected 'predicate' to be an instance of IRI, got {type(predicate).__name__} instead ({predicate!r}).")
arguments = list(arguments)
for i, arg in enumerate(arguments):
if not isinstance(arg, (IVariable, DVariable, OWLLiteral)):
raise TypeError(f"Expected all arguments to be instances of IVariable, DVariable or OWLLiteral, got {type(arg).__name__} instead ({arg!r}) at index {i}.")
self.predicate = predicate
self.arguments = arguments
[docs]
def is_class_assertion(self):
return False
[docs]
def is_property_assertion(self):
return False
[docs]
def is_same_as(self):
return False
[docs]
def is_different_from(self):
return False
[docs]
def is_builtin(self):
return True
[docs]
def __str__(self):
args_to_print = ""
for arg in self.arguments:
if isinstance(arg, OWLLiteral):
args_to_print += str(arg._v) + ", "
else:
args_to_print += str(arg) + ", "
return f"{str(self.predicate.remainder)}({args_to_print[:-2]})"
[docs]
def __repr__(self):
args_list ='['
for arg in self.arguments:
args_list = arg.__repr__()
args_list += ']'
return f'BuiltInAtom(IRI.create({self.predicate.str}), {args_list})'
[docs]
def __hash__(self):
return hash(("BuiltInAtom", self.predicate, tuple(self.arguments)))
[docs]
def __eq__(self, other):
if type(other) is type(self):
return self.predicate == other.predicate and self.arguments == other.arguments
return False
[docs]
class Rule:
"""Represents a rule in SWRL syntax"""
body_atoms: Union[Atom, List[Atom]]
head_atoms: Union[Atom, List[Atom]]
def __init__(self, body_atoms: Union[Atom, List[Atom]], head_atoms: Union[Atom, List[Atom]]):
for name, atoms in (("body_atoms", body_atoms), ("head_atoms", head_atoms)):
if isinstance(atoms, Atom):
continue
if isinstance(atoms, list):
for i, a in enumerate(atoms):
if not isinstance(a, Atom):
raise TypeError(f"Expected all elements of '{name}' to be instances of Atom, got {type(a).__name__} instead ({a!r}) at index {i}.")
continue
raise TypeError(f"Expected '{name}' to be an instance of Atom or a list of Atom, got {type(atoms).__name__} instead ({atoms!r}).")
self.body = body_atoms
self.head = head_atoms
[docs]
@staticmethod
def from_string(rule: str, namespace: str, dp_predicates: List[str] = None):
"""
Parses a SWRL rule given as a string.
Use '^' for composition of atoms and '->' for consequent implication.
E.g. of a valid rule: 'parent(?x,?y) ^ brother(?y,?z) -> uncle(?x,?z)'
Args:
rule: The SWRL rule in string format that is to be parsed
namespace: The namespace of the ontology
dp_predicates: (optional) List of data property predicates that will help in the correct mapping of
property type (by default property atoms are considered as object property atoms except when
specifying data property predicates in this argument)
Returns:
A SWRL Rule object.
"""
body_str = rule.split("->")[0]
head_str = rule.split("->")[1]
body_atoms_str = body_str.split("^")
head_atoms_str = head_str.split("^")
body_atoms = [Atom.from_string(atom_str, namespace, dp_predicates) for atom_str in body_atoms_str]
head_atoms = [Atom.from_string(atom_str, namespace, dp_predicates) for atom_str in head_atoms_str]
return Rule(body_atoms, head_atoms)
[docs]
def __str__(self):
body = self.body
head = self.head
if isinstance(self.body, List):
body = " ^ ".join(map(str, self.body))
if isinstance(self.head, List):
head = " ^ ".join(map(str, self.head))
return f"{body} -> {head}"
[docs]
def __repr__(self):
return f"Rule({[a.__repr__() for a in self.body]}, {[a.__repr__() for a in self.head]})"
[docs]
def __eq__(self, other):
if type(other) is type(self):
return self.body == other.body and self.head == other.head
return False
[docs]
def __hash__(self):
return hash(("Rule", tuple(self.body) if isinstance(self.body, List) else self.body,
tuple(self.head) if isinstance(self.head, List) else self.head))