pyobo 0.11.2__py3-none-any.whl → 0.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyobo/.DS_Store +0 -0
- pyobo/__init__.py +95 -20
- pyobo/__main__.py +0 -0
- pyobo/api/__init__.py +81 -10
- pyobo/api/alts.py +52 -42
- pyobo/api/combine.py +39 -0
- pyobo/api/edges.py +68 -0
- pyobo/api/hierarchy.py +231 -203
- pyobo/api/metadata.py +14 -19
- pyobo/api/names.py +207 -127
- pyobo/api/properties.py +117 -113
- pyobo/api/relations.py +68 -94
- pyobo/api/species.py +24 -21
- pyobo/api/typedefs.py +11 -11
- pyobo/api/utils.py +66 -13
- pyobo/api/xrefs.py +108 -114
- pyobo/cli/__init__.py +0 -0
- pyobo/cli/cli.py +35 -50
- pyobo/cli/database.py +183 -161
- pyobo/{xrefdb/xrefs_pipeline.py → cli/database_utils.py} +54 -73
- pyobo/cli/lookup.py +163 -195
- pyobo/cli/utils.py +19 -6
- pyobo/constants.py +102 -3
- pyobo/getters.py +196 -118
- pyobo/gilda_utils.py +79 -200
- pyobo/identifier_utils/__init__.py +41 -0
- pyobo/identifier_utils/api.py +296 -0
- pyobo/identifier_utils/model.py +130 -0
- pyobo/identifier_utils/preprocessing.json +812 -0
- pyobo/identifier_utils/preprocessing.py +61 -0
- pyobo/identifier_utils/relations/__init__.py +8 -0
- pyobo/identifier_utils/relations/api.py +162 -0
- pyobo/identifier_utils/relations/data.json +5824 -0
- pyobo/identifier_utils/relations/data_owl.json +57 -0
- pyobo/identifier_utils/relations/data_rdf.json +1 -0
- pyobo/identifier_utils/relations/data_rdfs.json +7 -0
- pyobo/mocks.py +9 -6
- pyobo/ner/__init__.py +9 -0
- pyobo/ner/api.py +72 -0
- pyobo/ner/normalizer.py +33 -0
- pyobo/obographs.py +43 -39
- pyobo/plugins.py +5 -4
- pyobo/py.typed +0 -0
- pyobo/reader.py +1358 -395
- pyobo/reader_utils.py +155 -0
- pyobo/resource_utils.py +42 -22
- pyobo/resources/__init__.py +0 -0
- pyobo/resources/goc.py +75 -0
- pyobo/resources/goc.tsv +188 -0
- pyobo/resources/ncbitaxon.py +4 -5
- pyobo/resources/ncbitaxon.tsv.gz +0 -0
- pyobo/resources/ro.py +3 -2
- pyobo/resources/ro.tsv +0 -0
- pyobo/resources/so.py +0 -0
- pyobo/resources/so.tsv +0 -0
- pyobo/sources/README.md +12 -8
- pyobo/sources/__init__.py +52 -29
- pyobo/sources/agrovoc.py +0 -0
- pyobo/sources/antibodyregistry.py +11 -12
- pyobo/sources/bigg/__init__.py +13 -0
- pyobo/sources/bigg/bigg_compartment.py +81 -0
- pyobo/sources/bigg/bigg_metabolite.py +229 -0
- pyobo/sources/bigg/bigg_model.py +46 -0
- pyobo/sources/bigg/bigg_reaction.py +77 -0
- pyobo/sources/biogrid.py +1 -2
- pyobo/sources/ccle.py +7 -12
- pyobo/sources/cgnc.py +0 -5
- pyobo/sources/chebi.py +1 -1
- pyobo/sources/chembl/__init__.py +9 -0
- pyobo/sources/{chembl.py → chembl/chembl_compound.py} +13 -25
- pyobo/sources/chembl/chembl_target.py +160 -0
- pyobo/sources/civic_gene.py +55 -15
- pyobo/sources/clinicaltrials.py +160 -0
- pyobo/sources/complexportal.py +24 -24
- pyobo/sources/conso.py +14 -22
- pyobo/sources/cpt.py +0 -0
- pyobo/sources/credit.py +1 -9
- pyobo/sources/cvx.py +27 -5
- pyobo/sources/depmap.py +9 -12
- pyobo/sources/dictybase_gene.py +2 -7
- pyobo/sources/drugbank/__init__.py +9 -0
- pyobo/sources/{drugbank.py → drugbank/drugbank.py} +11 -16
- pyobo/sources/{drugbank_salt.py → drugbank/drugbank_salt.py} +3 -8
- pyobo/sources/drugcentral.py +17 -13
- pyobo/sources/expasy.py +31 -34
- pyobo/sources/famplex.py +13 -18
- pyobo/sources/flybase.py +3 -8
- pyobo/sources/gard.py +62 -0
- pyobo/sources/geonames/__init__.py +9 -0
- pyobo/sources/geonames/features.py +28 -0
- pyobo/sources/{geonames.py → geonames/geonames.py} +87 -26
- pyobo/sources/geonames/utils.py +115 -0
- pyobo/sources/gmt_utils.py +6 -7
- pyobo/sources/go.py +20 -13
- pyobo/sources/gtdb.py +154 -0
- pyobo/sources/gwascentral/__init__.py +9 -0
- pyobo/sources/{gwascentral_phenotype.py → gwascentral/gwascentral_phenotype.py} +5 -7
- pyobo/sources/{gwascentral_study.py → gwascentral/gwascentral_study.py} +1 -7
- pyobo/sources/hgnc/__init__.py +9 -0
- pyobo/sources/{hgnc.py → hgnc/hgnc.py} +56 -70
- pyobo/sources/{hgncgenefamily.py → hgnc/hgncgenefamily.py} +8 -18
- pyobo/sources/icd/__init__.py +9 -0
- pyobo/sources/{icd10.py → icd/icd10.py} +35 -37
- pyobo/sources/icd/icd11.py +148 -0
- pyobo/sources/{icd_utils.py → icd/icd_utils.py} +66 -20
- pyobo/sources/interpro.py +4 -9
- pyobo/sources/itis.py +0 -5
- pyobo/sources/kegg/__init__.py +0 -0
- pyobo/sources/kegg/api.py +16 -38
- pyobo/sources/kegg/genes.py +9 -20
- pyobo/sources/kegg/genome.py +1 -7
- pyobo/sources/kegg/pathway.py +9 -21
- pyobo/sources/mesh.py +58 -24
- pyobo/sources/mgi.py +3 -10
- pyobo/sources/mirbase/__init__.py +11 -0
- pyobo/sources/{mirbase.py → mirbase/mirbase.py} +8 -11
- pyobo/sources/{mirbase_constants.py → mirbase/mirbase_constants.py} +0 -0
- pyobo/sources/{mirbase_family.py → mirbase/mirbase_family.py} +4 -8
- pyobo/sources/{mirbase_mature.py → mirbase/mirbase_mature.py} +3 -7
- pyobo/sources/msigdb.py +74 -39
- pyobo/sources/ncbi/__init__.py +9 -0
- pyobo/sources/ncbi/ncbi_gc.py +162 -0
- pyobo/sources/{ncbigene.py → ncbi/ncbigene.py} +18 -19
- pyobo/sources/nih_reporter.py +60 -0
- pyobo/sources/nlm/__init__.py +9 -0
- pyobo/sources/nlm/nlm_catalog.py +48 -0
- pyobo/sources/nlm/nlm_publisher.py +36 -0
- pyobo/sources/nlm/utils.py +116 -0
- pyobo/sources/npass.py +6 -8
- pyobo/sources/omim_ps.py +10 -3
- pyobo/sources/pathbank.py +4 -8
- pyobo/sources/pfam/__init__.py +9 -0
- pyobo/sources/{pfam.py → pfam/pfam.py} +3 -8
- pyobo/sources/{pfam_clan.py → pfam/pfam_clan.py} +2 -7
- pyobo/sources/pharmgkb/__init__.py +15 -0
- pyobo/sources/pharmgkb/pharmgkb_chemical.py +89 -0
- pyobo/sources/pharmgkb/pharmgkb_disease.py +77 -0
- pyobo/sources/pharmgkb/pharmgkb_gene.py +108 -0
- pyobo/sources/pharmgkb/pharmgkb_pathway.py +63 -0
- pyobo/sources/pharmgkb/pharmgkb_variant.py +84 -0
- pyobo/sources/pharmgkb/utils.py +86 -0
- pyobo/sources/pid.py +1 -6
- pyobo/sources/pombase.py +6 -10
- pyobo/sources/pubchem.py +4 -9
- pyobo/sources/reactome.py +5 -11
- pyobo/sources/rgd.py +11 -16
- pyobo/sources/rhea.py +37 -36
- pyobo/sources/ror.py +69 -42
- pyobo/sources/selventa/__init__.py +0 -0
- pyobo/sources/selventa/schem.py +4 -7
- pyobo/sources/selventa/scomp.py +1 -6
- pyobo/sources/selventa/sdis.py +4 -7
- pyobo/sources/selventa/sfam.py +1 -6
- pyobo/sources/sgd.py +6 -11
- pyobo/sources/signor/__init__.py +7 -0
- pyobo/sources/signor/download.py +41 -0
- pyobo/sources/signor/signor_complexes.py +105 -0
- pyobo/sources/slm.py +12 -15
- pyobo/sources/umls/__init__.py +7 -1
- pyobo/sources/umls/__main__.py +0 -0
- pyobo/sources/umls/get_synonym_types.py +20 -4
- pyobo/sources/umls/sty.py +57 -0
- pyobo/sources/umls/synonym_types.tsv +1 -1
- pyobo/sources/umls/umls.py +18 -22
- pyobo/sources/unimod.py +46 -0
- pyobo/sources/uniprot/__init__.py +1 -1
- pyobo/sources/uniprot/uniprot.py +40 -32
- pyobo/sources/uniprot/uniprot_ptm.py +4 -34
- pyobo/sources/utils.py +3 -2
- pyobo/sources/wikipathways.py +7 -10
- pyobo/sources/zfin.py +5 -10
- pyobo/ssg/__init__.py +12 -16
- pyobo/ssg/base.html +0 -0
- pyobo/ssg/index.html +26 -13
- pyobo/ssg/term.html +12 -2
- pyobo/ssg/typedef.html +0 -0
- pyobo/struct/__init__.py +54 -8
- pyobo/struct/functional/__init__.py +1 -0
- pyobo/struct/functional/dsl.py +2572 -0
- pyobo/struct/functional/macros.py +423 -0
- pyobo/struct/functional/obo_to_functional.py +385 -0
- pyobo/struct/functional/ontology.py +270 -0
- pyobo/struct/functional/utils.py +112 -0
- pyobo/struct/reference.py +331 -136
- pyobo/struct/struct.py +1413 -643
- pyobo/struct/struct_utils.py +1078 -0
- pyobo/struct/typedef.py +162 -210
- pyobo/struct/utils.py +12 -5
- pyobo/struct/vocabulary.py +138 -0
- pyobo/utils/__init__.py +0 -0
- pyobo/utils/cache.py +13 -11
- pyobo/utils/io.py +17 -31
- pyobo/utils/iter.py +5 -5
- pyobo/utils/misc.py +41 -53
- pyobo/utils/ndex_utils.py +0 -0
- pyobo/utils/path.py +76 -70
- pyobo/version.py +3 -3
- {pyobo-0.11.2.dist-info → pyobo-0.12.0.dist-info}/METADATA +228 -229
- pyobo-0.12.0.dist-info/RECORD +202 -0
- pyobo-0.12.0.dist-info/WHEEL +4 -0
- {pyobo-0.11.2.dist-info → pyobo-0.12.0.dist-info}/entry_points.txt +1 -0
- pyobo-0.12.0.dist-info/licenses/LICENSE +21 -0
- pyobo/aws.py +0 -162
- pyobo/cli/aws.py +0 -47
- pyobo/identifier_utils.py +0 -142
- pyobo/normalizer.py +0 -232
- pyobo/registries/__init__.py +0 -16
- pyobo/registries/metaregistry.json +0 -507
- pyobo/registries/metaregistry.py +0 -135
- pyobo/sources/icd11.py +0 -105
- pyobo/xrefdb/__init__.py +0 -1
- pyobo/xrefdb/canonicalizer.py +0 -214
- pyobo/xrefdb/priority.py +0 -59
- pyobo/xrefdb/sources/__init__.py +0 -60
- pyobo/xrefdb/sources/biomappings.py +0 -36
- pyobo/xrefdb/sources/cbms2019.py +0 -91
- pyobo/xrefdb/sources/chembl.py +0 -83
- pyobo/xrefdb/sources/compath.py +0 -82
- pyobo/xrefdb/sources/famplex.py +0 -64
- pyobo/xrefdb/sources/gilda.py +0 -50
- pyobo/xrefdb/sources/intact.py +0 -113
- pyobo/xrefdb/sources/ncit.py +0 -133
- pyobo/xrefdb/sources/pubchem.py +0 -27
- pyobo/xrefdb/sources/wikidata.py +0 -116
- pyobo-0.11.2.dist-info/RECORD +0 -157
- pyobo-0.11.2.dist-info/WHEEL +0 -5
- pyobo-0.11.2.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,2572 @@
|
|
|
1
|
+
"""A DSL for functional OWL."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import itertools as itt
|
|
7
|
+
import typing
|
|
8
|
+
from abc import abstractmethod
|
|
9
|
+
from collections.abc import Iterable, Sequence
|
|
10
|
+
from typing import ClassVar, TypeAlias
|
|
11
|
+
|
|
12
|
+
import rdflib.namespace
|
|
13
|
+
from curies import Converter, Reference
|
|
14
|
+
from rdflib import OWL, RDF, RDFS, XSD, Graph, collection, term
|
|
15
|
+
|
|
16
|
+
from pyobo.struct.functional.utils import list_to_funowl
|
|
17
|
+
from pyobo.struct.reference import Reference as PyOBOReference
|
|
18
|
+
from pyobo.struct.reference import Referenced, get_preferred_prefix
|
|
19
|
+
from pyobo.struct.struct_utils import OBOLiteral
|
|
20
|
+
|
|
21
|
+
from .utils import FunctionalOWLSerializable, RDFNodeSerializable
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"Annotation",
|
|
25
|
+
"AnnotationAssertion",
|
|
26
|
+
"AnnotationAxiom",
|
|
27
|
+
"AnnotationProperty",
|
|
28
|
+
"AnnotationPropertyDomain",
|
|
29
|
+
"AnnotationPropertyRange",
|
|
30
|
+
"Assertion",
|
|
31
|
+
"AsymmetricObjectProperty",
|
|
32
|
+
"Axiom",
|
|
33
|
+
"Box",
|
|
34
|
+
"ClassAssertion",
|
|
35
|
+
"ClassAxiom",
|
|
36
|
+
"ClassExpression",
|
|
37
|
+
"DataAllValuesFrom",
|
|
38
|
+
"DataComplementOf",
|
|
39
|
+
"DataExactCardinality",
|
|
40
|
+
"DataHasValue",
|
|
41
|
+
"DataIntersectionOf",
|
|
42
|
+
"DataMaxCardinality",
|
|
43
|
+
"DataMinCardinality",
|
|
44
|
+
"DataOneOf",
|
|
45
|
+
"DataPropertyAssertion",
|
|
46
|
+
"DataPropertyAxiom",
|
|
47
|
+
"DataPropertyDomain",
|
|
48
|
+
"DataPropertyExpression",
|
|
49
|
+
"DataPropertyRange",
|
|
50
|
+
"DataRange",
|
|
51
|
+
"DataSomeValuesFrom",
|
|
52
|
+
"DataUnionOf",
|
|
53
|
+
"DatatypeDefinition",
|
|
54
|
+
"DatatypeRestriction",
|
|
55
|
+
"Declaration",
|
|
56
|
+
"DeclarationType",
|
|
57
|
+
"DifferentIndividuals",
|
|
58
|
+
"DisjointClasses",
|
|
59
|
+
"DisjointDataProperties",
|
|
60
|
+
"DisjointObjectProperties",
|
|
61
|
+
"DisjointUnion",
|
|
62
|
+
"EquivalentClasses",
|
|
63
|
+
"EquivalentDataProperties",
|
|
64
|
+
"EquivalentObjectProperties",
|
|
65
|
+
"FunctionalDataProperty",
|
|
66
|
+
"FunctionalObjectProperty",
|
|
67
|
+
"HasKey",
|
|
68
|
+
"InverseFunctionalObjectProperty",
|
|
69
|
+
"InverseObjectProperties",
|
|
70
|
+
"IrreflexiveObjectProperty",
|
|
71
|
+
"NegativeDataPropertyAssertion",
|
|
72
|
+
"NegativeObjectPropertyAssertion",
|
|
73
|
+
"ObjectAllValuesFrom",
|
|
74
|
+
"ObjectComplementOf",
|
|
75
|
+
"ObjectExactCardinality",
|
|
76
|
+
"ObjectHasSelf",
|
|
77
|
+
"ObjectHasValue",
|
|
78
|
+
"ObjectIntersectionOf",
|
|
79
|
+
"ObjectInverseOf",
|
|
80
|
+
"ObjectMaxCardinality",
|
|
81
|
+
"ObjectMinCardinality",
|
|
82
|
+
"ObjectOneOf",
|
|
83
|
+
"ObjectPropertyAssertion",
|
|
84
|
+
"ObjectPropertyAxiom",
|
|
85
|
+
"ObjectPropertyChain",
|
|
86
|
+
"ObjectPropertyDomain",
|
|
87
|
+
"ObjectPropertyExpression",
|
|
88
|
+
"ObjectPropertyRange",
|
|
89
|
+
"ObjectSomeValuesFrom",
|
|
90
|
+
"ObjectUnionOf",
|
|
91
|
+
"ReflexiveObjectProperty",
|
|
92
|
+
"SameIndividual",
|
|
93
|
+
"SubAnnotationPropertyOf",
|
|
94
|
+
"SubClassOf",
|
|
95
|
+
"SubDataPropertyOf",
|
|
96
|
+
"SubObjectPropertyExpression",
|
|
97
|
+
"SubObjectPropertyOf",
|
|
98
|
+
"SymmetricObjectProperty",
|
|
99
|
+
"TransitiveObjectProperty",
|
|
100
|
+
"l",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def l(value) -> term.Literal: # noqa:E743
|
|
105
|
+
"""Get a literal."""
|
|
106
|
+
return term.Literal(value)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
#: These are the literals that can be automatically converted to and from RDFLib
|
|
110
|
+
SupportedLiterals: TypeAlias = int | float | bool | str | datetime.date | datetime.datetime
|
|
111
|
+
|
|
112
|
+
#: A partial hint for something that can be turned into an :class:`IdentifierBox`.
|
|
113
|
+
#: Here, a string gets interpreted into a CURIE using :meth:`curies.Reference.from_curie`
|
|
114
|
+
IdentifierHint = term.URIRef | Reference | Referenced | str
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class Box(FunctionalOWLSerializable, RDFNodeSerializable):
|
|
118
|
+
"""A model for objects that can be represented as nodes in RDF and Functional OWL."""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def obo_literal_to_rdflib(obo_literal: OBOLiteral, converter: Converter) -> rdflib.Literal:
|
|
122
|
+
"""Expand the OBO literal."""
|
|
123
|
+
iri = converter.expand(obo_literal.datatype.curie, strict=True)
|
|
124
|
+
return rdflib.Literal(obo_literal.value, datatype=rdflib.URIRef(iri))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class IdentifierBox(Box):
|
|
128
|
+
"""A simple wrapper around CURIEs and IRIs."""
|
|
129
|
+
|
|
130
|
+
identifier: term.URIRef | Reference
|
|
131
|
+
|
|
132
|
+
def __init__(self, identifier: IdentifierBoxOrHint) -> None:
|
|
133
|
+
"""Initialize the identifier box with a URIRef, Reference, or string representing a CURIE."""
|
|
134
|
+
if isinstance(identifier, Referenced):
|
|
135
|
+
identifier = identifier.reference
|
|
136
|
+
if isinstance(identifier, IdentifierBox):
|
|
137
|
+
self.identifier = identifier.identifier
|
|
138
|
+
# make sure to check for URIRef first,
|
|
139
|
+
# since it's also a subclass of str
|
|
140
|
+
elif isinstance(identifier, term.URIRef):
|
|
141
|
+
self.identifier = identifier
|
|
142
|
+
elif isinstance(identifier, str):
|
|
143
|
+
self.identifier = Reference.from_curie(identifier)
|
|
144
|
+
elif isinstance(identifier, PyOBOReference):
|
|
145
|
+
# it doesn't matter we're potentially throwing away the name,
|
|
146
|
+
# since this annotation gets put in OFN in a different place
|
|
147
|
+
self.identifier = Reference(
|
|
148
|
+
prefix=get_preferred_prefix(identifier), identifier=identifier.identifier
|
|
149
|
+
)
|
|
150
|
+
elif isinstance(identifier, Reference):
|
|
151
|
+
self.identifier = identifier
|
|
152
|
+
else:
|
|
153
|
+
raise TypeError(f"can not make an identifier box from: {identifier}")
|
|
154
|
+
|
|
155
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
156
|
+
"""Represent this identifier for RDF, using the converter to convert a CURIE if appropriate."""
|
|
157
|
+
if isinstance(self.identifier, term.URIRef):
|
|
158
|
+
return self.identifier
|
|
159
|
+
# TODO make more efficient
|
|
160
|
+
iri = converter.expand(self.identifier.curie, strict=True)
|
|
161
|
+
return term.URIRef(iri)
|
|
162
|
+
|
|
163
|
+
def to_funowl(self) -> str:
|
|
164
|
+
"""Represent this identifier for functional OWL."""
|
|
165
|
+
if isinstance(self.identifier, term.URIRef):
|
|
166
|
+
return f"<{self.identifier}>"
|
|
167
|
+
if any(c in self.identifier.identifier for c in "()"):
|
|
168
|
+
raise ValueError(f"Can't encode CURIE with parentheses to OFN: {self.identifier}")
|
|
169
|
+
return self.identifier.curie
|
|
170
|
+
|
|
171
|
+
def to_funowl_args(self) -> str: # pragma: no cover
|
|
172
|
+
"""Get the inside of the functional OWL tag representing the identifier (unused)."""
|
|
173
|
+
raise RuntimeError
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class LiteralBox(Box):
|
|
177
|
+
"""A simple wrapper around a literal."""
|
|
178
|
+
|
|
179
|
+
literal: term.Literal
|
|
180
|
+
_namespace_manager: ClassVar[rdflib.namespace.NamespaceManager] = Graph().namespace_manager
|
|
181
|
+
_converter: ClassVar[Converter] = Converter.from_rdflib(_namespace_manager)
|
|
182
|
+
|
|
183
|
+
def __init__(self, literal: LiteralBoxOrHint, language: str | None = None) -> None:
|
|
184
|
+
"""Initialize the literal box with a RDFlib literal or Python primitive.."""
|
|
185
|
+
if literal is None:
|
|
186
|
+
raise ValueError
|
|
187
|
+
if isinstance(literal, LiteralBox):
|
|
188
|
+
self.literal = literal.literal
|
|
189
|
+
elif isinstance(literal, term.Literal):
|
|
190
|
+
self.literal = literal
|
|
191
|
+
elif isinstance(literal, bool):
|
|
192
|
+
self.literal = term.Literal(str(literal).lower(), datatype=XSD.boolean)
|
|
193
|
+
elif isinstance(literal, int):
|
|
194
|
+
self.literal = term.Literal(literal, datatype=XSD.integer)
|
|
195
|
+
elif isinstance(literal, float):
|
|
196
|
+
self.literal = term.Literal(literal, datatype=XSD.decimal)
|
|
197
|
+
elif isinstance(literal, str):
|
|
198
|
+
self.literal = term.Literal(literal, lang=language)
|
|
199
|
+
elif isinstance(literal, datetime.date):
|
|
200
|
+
self.literal = term.Literal(literal, datatype=XSD.date)
|
|
201
|
+
elif isinstance(literal, datetime.datetime):
|
|
202
|
+
self.literal = term.Literal(literal, datatype=XSD.dateTime)
|
|
203
|
+
elif isinstance(literal, OBOLiteral):
|
|
204
|
+
self.literal = obo_literal_to_rdflib(literal, self._converter)
|
|
205
|
+
else:
|
|
206
|
+
raise TypeError(f"Unhandled type for literal: {literal}")
|
|
207
|
+
|
|
208
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.Literal:
|
|
209
|
+
"""Represent this literal for RDF."""
|
|
210
|
+
return self.literal
|
|
211
|
+
|
|
212
|
+
def to_funowl(self) -> str:
|
|
213
|
+
"""Represent this literal for functional OWL."""
|
|
214
|
+
return self.literal.n3(self._namespace_manager)
|
|
215
|
+
|
|
216
|
+
def to_funowl_args(self) -> str: # pragma: no cover
|
|
217
|
+
"""Get the inside of the functional OWL tag representing the literal (unused)."""
|
|
218
|
+
raise RuntimeError
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
IdentifierBoxOrHint: TypeAlias = IdentifierHint | IdentifierBox
|
|
222
|
+
LiteralBoxOrHint: TypeAlias = LiteralBox | term.Literal | SupportedLiterals | OBOLiteral
|
|
223
|
+
PrimitiveHint: TypeAlias = IdentifierBoxOrHint | LiteralBoxOrHint
|
|
224
|
+
PrimitiveBox: TypeAlias = LiteralBox | IdentifierBox
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _safe_primitive_box(value: PrimitiveHint) -> PrimitiveBox:
|
|
228
|
+
# if it's already boxed, then pass it through
|
|
229
|
+
if isinstance(value, PrimitiveBox):
|
|
230
|
+
return value
|
|
231
|
+
# note that literal is also a subclass of str,
|
|
232
|
+
# so it needs to be cheked first
|
|
233
|
+
if isinstance(value, term.Literal):
|
|
234
|
+
return LiteralBox(value)
|
|
235
|
+
# note that we decided that strings should be parsed
|
|
236
|
+
# by default as a CURIE. If you want to pass a literal
|
|
237
|
+
# through, wrap it with rdflib.Literal
|
|
238
|
+
if isinstance(value, str):
|
|
239
|
+
return IdentifierBox(value)
|
|
240
|
+
if isinstance(value, SupportedLiterals | OBOLiteral):
|
|
241
|
+
return LiteralBox(value)
|
|
242
|
+
# everything else (e.g., URIRef, Reference) are for identifier boxes
|
|
243
|
+
return IdentifierBox(value)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _make_sequence(
|
|
247
|
+
graph: Graph,
|
|
248
|
+
members: Iterable[Box],
|
|
249
|
+
converter: Converter,
|
|
250
|
+
*,
|
|
251
|
+
type_connector_nodes: bool = False,
|
|
252
|
+
) -> term.IdentifiedNode:
|
|
253
|
+
"""Make a sequence."""
|
|
254
|
+
return _make_sequence_nodes(
|
|
255
|
+
graph,
|
|
256
|
+
[m.to_rdflib_node(graph, converter) for m in members],
|
|
257
|
+
type_connector_nodes=type_connector_nodes,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _make_sequence_nodes(
|
|
262
|
+
graph: Graph,
|
|
263
|
+
members: Sequence[term.IdentifiedNode | term.Literal],
|
|
264
|
+
*,
|
|
265
|
+
type_connector_nodes: bool = False,
|
|
266
|
+
) -> term.IdentifiedNode:
|
|
267
|
+
"""Make a sequence."""
|
|
268
|
+
if not members:
|
|
269
|
+
return RDF.nil
|
|
270
|
+
node = term.BNode()
|
|
271
|
+
collection.Collection(graph, node, list(members))
|
|
272
|
+
if type_connector_nodes:
|
|
273
|
+
# This is a weird quirk required for DataOneOf, which for
|
|
274
|
+
# some reason emits `rdf:type rdfs:List` for each element
|
|
275
|
+
for connector_node in _yield_connector_nodes(graph, node):
|
|
276
|
+
graph.add((connector_node, RDF.type, RDF.List))
|
|
277
|
+
return node
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _yield_connector_nodes(
|
|
281
|
+
graph: Graph, start: term.IdentifiedNode
|
|
282
|
+
) -> Iterable[term.IdentifiedNode]:
|
|
283
|
+
"""Yield all of the nodes representing parts of a collection.
|
|
284
|
+
|
|
285
|
+
This is different than simply doing :meth:`rdflib.graph.items`,
|
|
286
|
+
because that function gets the "first" parts - this function
|
|
287
|
+
gets the blank nodes that are representing the prongs in the
|
|
288
|
+
list itself.
|
|
289
|
+
|
|
290
|
+
We have to do this because ROBOT implemenents RDF conversion for
|
|
291
|
+
:class:`DataOneOf` strangely, where each blank node in the collection
|
|
292
|
+
gets a triple typing it as a list ``<bnode> rdf:type rdfs:List``
|
|
293
|
+
"""
|
|
294
|
+
yield start
|
|
295
|
+
item: term.IdentifiedNode | None = start
|
|
296
|
+
while item := graph.value(item, RDF.rest): # type:ignore
|
|
297
|
+
if item == RDF.nil:
|
|
298
|
+
break
|
|
299
|
+
yield item
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
"""Section 5"""
|
|
303
|
+
|
|
304
|
+
DeclarationType: TypeAlias = typing.Literal[
|
|
305
|
+
"Class",
|
|
306
|
+
"ObjectProperty",
|
|
307
|
+
"DataProperty",
|
|
308
|
+
"Datatype",
|
|
309
|
+
"AnnotationProperty",
|
|
310
|
+
"NamedIndividual",
|
|
311
|
+
]
|
|
312
|
+
type_to_uri: dict[DeclarationType, term.URIRef] = {
|
|
313
|
+
"Class": OWL.Class,
|
|
314
|
+
"ObjectProperty": OWL.ObjectProperty,
|
|
315
|
+
"DataProperty": OWL.DatatypeProperty,
|
|
316
|
+
"AnnotationProperty": OWL.AnnotationProperty,
|
|
317
|
+
"Datatype": RDFS.Datatype,
|
|
318
|
+
"NamedIndividual": OWL.NamedIndividual,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class Declaration(Box):
|
|
323
|
+
"""Declarations."""
|
|
324
|
+
|
|
325
|
+
def __init__(self, node: IdentifierBoxOrHint, type: DeclarationType) -> None:
|
|
326
|
+
"""Initialize the declaration with a given identifier and type."""
|
|
327
|
+
self.node = IdentifierBox(node)
|
|
328
|
+
self.type = type
|
|
329
|
+
|
|
330
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
331
|
+
"""Represent this declaration for RDF."""
|
|
332
|
+
node = self.node.to_rdflib_node(graph, converter)
|
|
333
|
+
graph.add((node, RDF.type, type_to_uri[self.type]))
|
|
334
|
+
return node
|
|
335
|
+
|
|
336
|
+
def to_funowl_args(self) -> str:
|
|
337
|
+
"""Get the inside of the functional OWL tag representing the declaration."""
|
|
338
|
+
return f"{self.type}({self.node.to_funowl()})"
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
"""
|
|
342
|
+
Section 6: Property Expressions
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class ObjectPropertyExpression(Box):
|
|
347
|
+
"""A model representing `6.1 "Object Property Expressions" <https://www.w3.org/TR/owl2-syntax/#Object_Property_Expressions>`_.
|
|
348
|
+
|
|
349
|
+
.. image:: https://www.w3.org/TR/owl2-syntax/C_objectproperty.gif
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
@classmethod
|
|
353
|
+
def safe(cls, ope: ObjectPropertyExpression | IdentifierBoxOrHint) -> ObjectPropertyExpression:
|
|
354
|
+
"""Pass through a pre-instantiated object property expression, or create a simple one for an identifier."""
|
|
355
|
+
if isinstance(ope, IdentifierBoxOrHint):
|
|
356
|
+
return SimpleObjectPropertyExpression(ope)
|
|
357
|
+
return ope
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class SimpleObjectPropertyExpression(IdentifierBox, ObjectPropertyExpression):
|
|
361
|
+
"""A simple object property expression represented by an IRI/CURIE."""
|
|
362
|
+
|
|
363
|
+
#: A set of built-in object properties that shouldn't be re-defined, since they
|
|
364
|
+
#: appear in Table 3 of https://www.w3.org/TR/owl2-syntax/#IRIs.
|
|
365
|
+
_SKIP: ClassVar[set[term.Node]] = {OWL.topObjectProperty, OWL.bottomObjectProperty}
|
|
366
|
+
|
|
367
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
368
|
+
"""Represent this object property identifier for RDF."""
|
|
369
|
+
node = super().to_rdflib_node(graph, converter)
|
|
370
|
+
if node in self._SKIP:
|
|
371
|
+
return node
|
|
372
|
+
graph.add((node, RDF.type, OWL.ObjectProperty))
|
|
373
|
+
return node
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
class ObjectInverseOf(ObjectPropertyExpression):
|
|
377
|
+
"""A property expression defined in `6.1.1 "Inverse Object Properties" <https://www.w3.org/TR/owl2-syntax/#Inverse_Object_Properties>`_.
|
|
378
|
+
|
|
379
|
+
For example, ``ObjectPropertyAssertion( a:fatherOf a:Peter a:Stewie )`` implies
|
|
380
|
+
``ObjectPropertyAssertion( ObjectInverseOf(a:fatherOf) a:Stewie a:Peter )``.
|
|
381
|
+
|
|
382
|
+
>>> ObjectInverseOf("a:fatherOf")
|
|
383
|
+
|
|
384
|
+
.. warning::
|
|
385
|
+
|
|
386
|
+
This is the only instance in the specification where the
|
|
387
|
+
name of the tag is not the same as the name of the element
|
|
388
|
+
in the spec, which is ``InverseObjectProperty``.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
object_property: IdentifierBox
|
|
392
|
+
|
|
393
|
+
def __init__(self, object_property: IdentifierBoxOrHint) -> None:
|
|
394
|
+
"""Instantiate an inverse object property."""
|
|
395
|
+
# note that this can't be an expression - it has to be a defined thing.
|
|
396
|
+
# further, we can't use SimpleObjectPropertyExpression because
|
|
397
|
+
# we're trying to stay consistent with OWLAPI, and it sometimes doesn't
|
|
398
|
+
# automatically assert the enclosed property as a owl:ObjectProperty,
|
|
399
|
+
# e.g., inside ObjectMaxCardinality (not) and inside SubjectPropertyOf (does)
|
|
400
|
+
# see https://github.com/owlcs/owlapi/issues/1161
|
|
401
|
+
self.object_property = IdentifierBox(object_property)
|
|
402
|
+
|
|
403
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
404
|
+
"""Get a node representing the inverse object property."""
|
|
405
|
+
node = term.BNode()
|
|
406
|
+
graph.add((node, OWL.inverseOf, self.object_property.to_rdflib_node(graph, converter)))
|
|
407
|
+
return node
|
|
408
|
+
|
|
409
|
+
def declare_wrapped_ope(self, graph: Graph, converter: Converter) -> None:
|
|
410
|
+
"""Declare the wrapped object property."""
|
|
411
|
+
graph.add(
|
|
412
|
+
(self.object_property.to_rdflib_node(graph, converter), RDF.type, OWL.ObjectProperty)
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
def to_funowl_args(self) -> str:
|
|
416
|
+
"""Get the inside of the functional OWL tag representing the inverse object property."""
|
|
417
|
+
return self.object_property.to_funowl()
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
class DataPropertyExpression(Box):
|
|
421
|
+
"""A model representing `6.2 "Data Property Expressions" <https://www.w3.org/TR/owl2-syntax/#Data_Property_Expressions>`_.
|
|
422
|
+
|
|
423
|
+
.. image:: https://www.w3.org/TR/owl2-syntax/C_dataproperty.gif
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
@classmethod
|
|
427
|
+
def safe(cls, dpe: DataPropertyExpression | IdentifierBoxOrHint) -> DataPropertyExpression:
|
|
428
|
+
"""Pass through a pre-instantiated data property expression, or create a simple one for an identifier."""
|
|
429
|
+
if isinstance(dpe, IdentifierBoxOrHint):
|
|
430
|
+
return SimpleDataPropertyExpression(dpe)
|
|
431
|
+
return dpe
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
class SimpleDataPropertyExpression(IdentifierBox, DataPropertyExpression):
|
|
435
|
+
"""A simple data property expression represented by an IRI/CURIE."""
|
|
436
|
+
|
|
437
|
+
#: A set of built-in data properties that shouldn't be re-defined, since they
|
|
438
|
+
#: appear in Table 3 of https://www.w3.org/TR/owl2-syntax/#IRIs.
|
|
439
|
+
_SKIP: ClassVar[set[term.URIRef]] = {OWL.topDataProperty, OWL.bottomDataProperty}
|
|
440
|
+
|
|
441
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
442
|
+
"""Represent this data property identifier for RDF."""
|
|
443
|
+
node = super().to_rdflib_node(graph, converter)
|
|
444
|
+
if node in self._SKIP:
|
|
445
|
+
return node
|
|
446
|
+
graph.add((node, RDF.type, OWL.DatatypeProperty))
|
|
447
|
+
return node
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
"""
|
|
451
|
+
Section 7: Data Ranges
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class DataRange(Box):
|
|
456
|
+
"""A model representing `7 "Data Ranges" <https://www.w3.org/TR/owl2-syntax/#Datatypes>`_.
|
|
457
|
+
|
|
458
|
+
.. image:: https://www.w3.org/TR/owl2-syntax/C_datarange.gif
|
|
459
|
+
"""
|
|
460
|
+
|
|
461
|
+
@classmethod
|
|
462
|
+
def safe(cls, data_range: DataRange | IdentifierBoxOrHint) -> DataRange:
|
|
463
|
+
"""Pass through a pre-instantiated data range, or create a simple one for an identifier."""
|
|
464
|
+
if isinstance(data_range, IdentifierBoxOrHint):
|
|
465
|
+
return SimpleDateRange(data_range)
|
|
466
|
+
return data_range
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class SimpleDateRange(IdentifierBox, DataRange):
|
|
470
|
+
"""A simple data range represented by an IRI/CURIE."""
|
|
471
|
+
|
|
472
|
+
# TODO add skip to RDF node output for builtin data types?
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
class _ListDataRange(DataRange):
|
|
476
|
+
"""An abstract model for data intersection and union expressions."""
|
|
477
|
+
|
|
478
|
+
property_type: ClassVar[term.URIRef]
|
|
479
|
+
data_ranges: Sequence[DataRange]
|
|
480
|
+
|
|
481
|
+
def __init__(self, data_ranges: Sequence[DataRange | IdentifierBoxOrHint]) -> None:
|
|
482
|
+
"""Initialize this list of data ranges."""
|
|
483
|
+
self.data_ranges = [DataRange.safe(dr) for dr in data_ranges]
|
|
484
|
+
|
|
485
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
486
|
+
"""Represent this list of data range for RDF."""
|
|
487
|
+
node = term.BNode()
|
|
488
|
+
graph.add((node, RDF.type, RDFS.Datatype))
|
|
489
|
+
graph.add((node, self.property_type, _make_sequence(graph, self.data_ranges, converter)))
|
|
490
|
+
return node
|
|
491
|
+
|
|
492
|
+
def to_funowl_args(self) -> str:
|
|
493
|
+
"""Get the inside of the functional OWL tag representing the data range."""
|
|
494
|
+
return list_to_funowl(self.data_ranges)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
class DataIntersectionOf(_ListDataRange):
|
|
498
|
+
"""A data range defined in `7.1 "Intersection of Data Ranges" <https://www.w3.org/TR/owl2-syntax/#Intersection_of_Data_Ranges>`_.
|
|
499
|
+
|
|
500
|
+
The following data range contains exactly the integer 0:
|
|
501
|
+
|
|
502
|
+
>>> DataIntersectionOf(["xsd:nonNegativeInteger", "xsd:nonPositiveInteger"])
|
|
503
|
+
"""
|
|
504
|
+
|
|
505
|
+
property_type: ClassVar[term.URIRef] = OWL.intersectionOf
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
class DataUnionOf(_ListDataRange):
|
|
509
|
+
"""A data range defined in `7.2 "Union of Data Ranges" <https://www.w3.org/TR/owl2-syntax/#Union_of_Data_Ranges>`_.
|
|
510
|
+
|
|
511
|
+
The following data range contains all strings and all integers:
|
|
512
|
+
|
|
513
|
+
>>> DataUnionOf(["xsd:string", "xsd:integer"])
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
property_type: ClassVar[term.URIRef] = OWL.unionOf
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
class DataComplementOf(DataRange):
|
|
520
|
+
"""A data range defined in `7.3 Complement of Data Ranges" <https://www.w3.org/TR/owl2-syntax/#Complement_of_Data_Ranges>`_.
|
|
521
|
+
|
|
522
|
+
The following complement data range contains literals that are not positive integers:
|
|
523
|
+
|
|
524
|
+
>>> DataComplementOf("xsd:positiveInteger")
|
|
525
|
+
|
|
526
|
+
The following contains all non-zero integers:
|
|
527
|
+
|
|
528
|
+
>>> DataComplementOf(DataIntersectionOf(["xsd:nonNegativeInteger", "xsd:nonPositiveInteger"]))
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
data_range: DataRange
|
|
532
|
+
|
|
533
|
+
def __init__(self, data_range: DataRange | IdentifierBoxOrHint):
|
|
534
|
+
"""Initialize a complement of a data range using another data range."""
|
|
535
|
+
self.data_range = DataRange.safe(data_range)
|
|
536
|
+
|
|
537
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
538
|
+
"""Represent this complement of a data range for RDF."""
|
|
539
|
+
node = term.BNode()
|
|
540
|
+
graph.add((node, RDF.type, RDFS.Datatype))
|
|
541
|
+
graph.add(
|
|
542
|
+
(node, OWL.datatypeComplementOf, self.data_range.to_rdflib_node(graph, converter))
|
|
543
|
+
)
|
|
544
|
+
return node
|
|
545
|
+
|
|
546
|
+
def to_funowl_args(self) -> str:
|
|
547
|
+
"""Get the inside of the functional OWL tag representing the complement of data range."""
|
|
548
|
+
return self.data_range.to_funowl()
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
class DataOneOf(DataRange):
|
|
552
|
+
"""A data range defined in `7.4 Enumeration of Literals" <https://www.w3.org/TR/owl2-syntax/#Enumeration_of_Literals>`_.
|
|
553
|
+
|
|
554
|
+
The following data range contains exactly two literals: the string "Peter" and the integer one.
|
|
555
|
+
|
|
556
|
+
>>> DataOneOf(["Peter", 1])
|
|
557
|
+
|
|
558
|
+
This can be specified more explicitly with :class:`rdflib.Literal`:
|
|
559
|
+
|
|
560
|
+
>>> import rdflib
|
|
561
|
+
>>> DataOneOf(["Peter", rdflib.Literal(1, datatype=XSD.nonNegativeInteger)])
|
|
562
|
+
"""
|
|
563
|
+
|
|
564
|
+
literals: Sequence[LiteralBox]
|
|
565
|
+
|
|
566
|
+
def __init__(self, literals: Sequence[LiteralBoxOrHint]):
|
|
567
|
+
"""Initialize an enumeration of literals."""
|
|
568
|
+
self.literals = [LiteralBox(literal) for literal in literals]
|
|
569
|
+
|
|
570
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
571
|
+
"""Represent this enumeration of literals for RDF."""
|
|
572
|
+
node = term.BNode()
|
|
573
|
+
literal_nodes = [literal.to_rdflib_node(graph, converter) for literal in self.literals]
|
|
574
|
+
graph.add((node, RDF.type, RDFS.Datatype))
|
|
575
|
+
graph.add(
|
|
576
|
+
(node, OWL.oneOf, _make_sequence_nodes(graph, literal_nodes, type_connector_nodes=True))
|
|
577
|
+
)
|
|
578
|
+
return node
|
|
579
|
+
|
|
580
|
+
def to_funowl_args(self) -> str:
|
|
581
|
+
"""Get the inside of the functional OWL tag representing the enumeration of literals."""
|
|
582
|
+
return list_to_funowl(self.literals)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
class DatatypeRestriction(DataRange):
|
|
586
|
+
"""A data range defined in `7.5 Datatype Restrictions " <https://www.w3.org/TR/owl2-syntax/#Datatype_Restrictions>`_.
|
|
587
|
+
|
|
588
|
+
The following data range contains exactly the integers 5, 6, 7, 8, and 9:
|
|
589
|
+
|
|
590
|
+
>>> DatatypeRestriction("xsd:integer", [("xsd:minInclusive", 5), ("xsd:maxExclusive", 10)])
|
|
591
|
+
"""
|
|
592
|
+
|
|
593
|
+
datatype: IdentifierBox
|
|
594
|
+
pairs: list[tuple[IdentifierBox, LiteralBox]]
|
|
595
|
+
|
|
596
|
+
def __init__(
|
|
597
|
+
self,
|
|
598
|
+
datatype: IdentifierBoxOrHint,
|
|
599
|
+
pairs: list[tuple[IdentifierBoxOrHint, LiteralBoxOrHint]],
|
|
600
|
+
) -> None:
|
|
601
|
+
"""Initialize a datatype restriction.
|
|
602
|
+
|
|
603
|
+
:param datatype: The base datatype
|
|
604
|
+
:param pairs: A list of pairs of restrictions (e.g., ``xsd:minInclusive``) and literal values
|
|
605
|
+
"""
|
|
606
|
+
self.datatype = IdentifierBox(datatype)
|
|
607
|
+
self.pairs = [(IdentifierBox(facet), LiteralBox(value)) for facet, value in pairs]
|
|
608
|
+
|
|
609
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
610
|
+
"""Represent this datatype restriction for RDF."""
|
|
611
|
+
node = term.BNode()
|
|
612
|
+
|
|
613
|
+
restrictions: list[term.BNode] = []
|
|
614
|
+
for facet, value in self.pairs:
|
|
615
|
+
restriction = term.BNode()
|
|
616
|
+
graph.add(
|
|
617
|
+
(
|
|
618
|
+
restriction,
|
|
619
|
+
facet.to_rdflib_node(graph, converter),
|
|
620
|
+
value.to_rdflib_node(graph, converter),
|
|
621
|
+
)
|
|
622
|
+
)
|
|
623
|
+
restrictions.append(restriction)
|
|
624
|
+
|
|
625
|
+
graph.add((node, RDF.type, RDFS.Datatype))
|
|
626
|
+
graph.add((node, OWL.onDatatype, self.datatype.to_rdflib_node(graph, converter)))
|
|
627
|
+
graph.add((node, OWL.withRestrictions, _make_sequence_nodes(graph, restrictions)))
|
|
628
|
+
return node
|
|
629
|
+
|
|
630
|
+
def to_funowl_args(self) -> str:
|
|
631
|
+
"""Get the inside of the functional OWL tag representing the datatype restriction."""
|
|
632
|
+
pairs_funowl = " ".join(
|
|
633
|
+
f"{facet.to_funowl()} {value.to_funowl()}" for facet, value in self.pairs
|
|
634
|
+
)
|
|
635
|
+
return f"{self.datatype.to_funowl()} {pairs_funowl}"
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
"""
|
|
639
|
+
`Section 8: Class Expressions <https://www.w3.org/TR/owl2-syntax/#Class_Expressions>`_
|
|
640
|
+
"""
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
class ClassExpression(Box):
|
|
644
|
+
"""An abstract model representing `class expressions <https://www.w3.org/TR/owl2-syntax/#Class_Expressions>`_."""
|
|
645
|
+
|
|
646
|
+
@classmethod
|
|
647
|
+
def safe(cls, class_expresion: ClassExpression | IdentifierBoxOrHint) -> ClassExpression:
|
|
648
|
+
"""Pass through a pre-instantiated class expression, or create a simple one for an identifier."""
|
|
649
|
+
if isinstance(class_expresion, IdentifierBoxOrHint):
|
|
650
|
+
return SimpleClassExpression(class_expresion)
|
|
651
|
+
return class_expresion
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
class SimpleClassExpression(IdentifierBox, ClassExpression):
|
|
655
|
+
"""A simple class expression represented by an IRI/CURIE."""
|
|
656
|
+
|
|
657
|
+
#: A set of built-in classes that shouldn't be re-defined, since they
|
|
658
|
+
#: appear in Table 3 of https://www.w3.org/TR/owl2-syntax/#IRIs.
|
|
659
|
+
_SKIP: ClassVar[set[term.Node]] = {OWL.Thing, OWL.Nothing}
|
|
660
|
+
|
|
661
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
662
|
+
"""Represent this class identifier for RDF."""
|
|
663
|
+
node = super().to_rdflib_node(graph, converter)
|
|
664
|
+
if node in self._SKIP:
|
|
665
|
+
# i.e., don't add extra annotations for these
|
|
666
|
+
return node
|
|
667
|
+
graph.add((node, RDF.type, OWL.Class))
|
|
668
|
+
return node
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
class _ObjectList(ClassExpression):
|
|
672
|
+
"""An abstract model for class expressions defined by lists.
|
|
673
|
+
|
|
674
|
+
Defined in `8.1 Propositional Connectives and Enumeration of
|
|
675
|
+
Individuals <Propositional_Connectives_and_Enumeration_of_Individuals>`_
|
|
676
|
+
|
|
677
|
+
.. image:: https://www.w3.org/TR/owl2-syntax/C_propositional.gif
|
|
678
|
+
"""
|
|
679
|
+
|
|
680
|
+
property_type: ClassVar[term.URIRef]
|
|
681
|
+
class_expressions: Sequence[ClassExpression]
|
|
682
|
+
|
|
683
|
+
def __init__(self, class_expressions: Sequence[ClassExpression | IdentifierBoxOrHint]) -> None:
|
|
684
|
+
"""Initialize the model with a list of class expressions."""
|
|
685
|
+
if len(class_expressions) < 2:
|
|
686
|
+
raise ValueError("must have at least two class expressions")
|
|
687
|
+
self.class_expressions = [ClassExpression.safe(ce) for ce in class_expressions]
|
|
688
|
+
|
|
689
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
690
|
+
"""Represent this object list identifier for RDF."""
|
|
691
|
+
node = term.BNode()
|
|
692
|
+
graph.add((node, RDF.type, OWL.Class))
|
|
693
|
+
graph.add(
|
|
694
|
+
(node, self.property_type, _make_sequence(graph, self.class_expressions, converter))
|
|
695
|
+
)
|
|
696
|
+
return node
|
|
697
|
+
|
|
698
|
+
def to_funowl_args(self) -> str:
|
|
699
|
+
"""Get the inside of the functional OWL tag representing the object list."""
|
|
700
|
+
return list_to_funowl(self.class_expressions)
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
class ObjectIntersectionOf(_ObjectList):
|
|
704
|
+
"""A class expression defined in `8.1.1 Intersection of Class Expressions <https://www.w3.org/TR/owl2-syntax/#Intersection_of_Class_Expressions>`_.
|
|
705
|
+
|
|
706
|
+
Consider the ontology consisting of the following axioms.
|
|
707
|
+
|
|
708
|
+
>>> ClassAssertion("a:Dog", "a:Brian") # Brian is a dog.
|
|
709
|
+
>>> ClassAssertion("a:CanTalk", "a:Brian") # Brian can talk.
|
|
710
|
+
|
|
711
|
+
The following class expression describes all dogs that can talk;
|
|
712
|
+
furthermore, ``a:Brian`` is classified as its instance.
|
|
713
|
+
|
|
714
|
+
>>> ObjectIntersectionOf(["a:Dog", "a:CanTalk"])
|
|
715
|
+
"""
|
|
716
|
+
|
|
717
|
+
property_type: ClassVar[term.URIRef] = OWL.intersectionOf
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
class ObjectUnionOf(_ObjectList):
|
|
721
|
+
"""A class expression defined in `8.1.2 Union of Class Expressions <https://www.w3.org/TR/owl2-syntax/#Union_of_Class_Expressions>`_.
|
|
722
|
+
|
|
723
|
+
Consider the ontology consisting of the following axioms.
|
|
724
|
+
|
|
725
|
+
>>> ClassAssertion("a:Man", "a:Peter") # Peter is a man.
|
|
726
|
+
>>> ClassAssertion("a:Woman", "a:Lois") # Lois is a woman.
|
|
727
|
+
|
|
728
|
+
The following class expression describes all individuals that are instances of either
|
|
729
|
+
``a:Man`` or ``a:Woman``; furthermore, both ``a:Peter`` and ``a:Lois`` are classified
|
|
730
|
+
as its instances:
|
|
731
|
+
|
|
732
|
+
>>> ObjectUnionOf(["a:Man", "a:Woman"])
|
|
733
|
+
"""
|
|
734
|
+
|
|
735
|
+
property_type: ClassVar[term.URIRef] = OWL.unionOf
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
class ObjectComplementOf(ClassExpression):
|
|
739
|
+
"""A class expression defined in `8.1.3 Complement of Class Expressions <https://www.w3.org/TR/owl2-syntax/#Complement_of_Class_Expressions>`_.
|
|
740
|
+
|
|
741
|
+
Example 1
|
|
742
|
+
---------
|
|
743
|
+
Consider the ontology consisting of the following axioms.
|
|
744
|
+
|
|
745
|
+
>>> DisjointClasses(["a:Man", "a:Woman"]) # Nothing can be both a man and a woman.
|
|
746
|
+
>>> ClassAssertion("a:Woman", "a:Lois") # Lois is a woman.
|
|
747
|
+
|
|
748
|
+
The following class expression describes all things that are not instances of a:Man:
|
|
749
|
+
|
|
750
|
+
>>> ObjectComplementOf("a:Man")
|
|
751
|
+
|
|
752
|
+
Example 2
|
|
753
|
+
---------
|
|
754
|
+
OWL 2 has open-world semantics, so negation in OWL 2 is the same as in classical
|
|
755
|
+
(first-order) logic. To understand open-world semantics, consider the ontology
|
|
756
|
+
consisting of the following assertion.
|
|
757
|
+
|
|
758
|
+
>>> ClassAssertion("a:Dog", "a:Brian") # Brian is a dog.
|
|
759
|
+
|
|
760
|
+
One might expect ``a:Brian`` to be classified as an instance of
|
|
761
|
+
the following class expression:
|
|
762
|
+
|
|
763
|
+
>>> ObjectComplementOf("a:Bird")
|
|
764
|
+
|
|
765
|
+
However, because of the OWL reasoning, this can't be concluded
|
|
766
|
+
"""
|
|
767
|
+
|
|
768
|
+
class_expression: ClassExpression
|
|
769
|
+
|
|
770
|
+
def __init__(self, class_expression: ClassExpression | IdentifierBoxOrHint) -> None:
|
|
771
|
+
"""Initialize the model with a single class expression."""
|
|
772
|
+
self.class_expression = ClassExpression.safe(class_expression)
|
|
773
|
+
|
|
774
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
775
|
+
"""Represent this object complement as RDF."""
|
|
776
|
+
node = term.BNode()
|
|
777
|
+
graph.add((node, RDF.type, OWL.Class))
|
|
778
|
+
graph.add((node, OWL.complementOf, self.class_expression.to_rdflib_node(graph, converter)))
|
|
779
|
+
return node
|
|
780
|
+
|
|
781
|
+
def to_funowl_args(self) -> str:
|
|
782
|
+
"""Get the inside of the functional OWL tag representing the complement of class expression."""
|
|
783
|
+
return self.class_expression.to_funowl()
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
class ObjectOneOf(ClassExpression):
|
|
787
|
+
"""A class expression defined in `8.1.4 Enumeration of Individuals <https://www.w3.org/TR/owl2-syntax/#Enumeration_of_Individuals>`_."""
|
|
788
|
+
|
|
789
|
+
property_type: ClassVar[term.URIRef] = OWL.oneOf
|
|
790
|
+
individuals: Sequence[IdentifierBox]
|
|
791
|
+
|
|
792
|
+
def __init__(self, individuals: Sequence[IdentifierBoxOrHint]) -> None:
|
|
793
|
+
"""Initialize the model with a list of class expressions."""
|
|
794
|
+
if len(individuals) < 2:
|
|
795
|
+
raise ValueError("must have at least two class expressions")
|
|
796
|
+
self.individuals = [IdentifierBox(i) for i in individuals]
|
|
797
|
+
|
|
798
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
799
|
+
"""Represent this enumeration of individuals for RDF."""
|
|
800
|
+
node = term.BNode()
|
|
801
|
+
graph.add((node, RDF.type, OWL.Class))
|
|
802
|
+
nodes = [i.to_rdflib_node(graph, converter) for i in self.individuals]
|
|
803
|
+
nodes = sorted(nodes, key=str) # sort is very important to be consistent with OWLAPI!
|
|
804
|
+
for i in nodes:
|
|
805
|
+
graph.add((i, RDF.type, OWL.NamedIndividual))
|
|
806
|
+
graph.add((node, self.property_type, _make_sequence_nodes(graph, nodes)))
|
|
807
|
+
return node
|
|
808
|
+
|
|
809
|
+
def to_funowl_args(self) -> str:
|
|
810
|
+
"""Get the inside of the functional OWL tag representing the enumeration of individuals."""
|
|
811
|
+
return list_to_funowl(self.individuals)
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
def get_owl_restriction(
|
|
815
|
+
graph: Graph,
|
|
816
|
+
object_property_expression: ObjectPropertyExpression,
|
|
817
|
+
restriction_predicate: term.URIRef,
|
|
818
|
+
restriction_target: Box | term.Literal | term.IdentifiedNode,
|
|
819
|
+
converter: Converter,
|
|
820
|
+
) -> term.BNode:
|
|
821
|
+
"""Generate a blank node representing an OWL restriction.
|
|
822
|
+
|
|
823
|
+
:param graph: An RDFlib graph
|
|
824
|
+
:param object_property_expression: The object property expression that goes with ``owl:onProperty``
|
|
825
|
+
:param restriction_predicate: The predicate that connects the restriction to the target.
|
|
826
|
+
Can be one of ``owl:someValuesFrom``, ``owl:allValuesFrom``,
|
|
827
|
+
``owl:hasValue``, ``owl:hasSelf``, or something more exotic
|
|
828
|
+
:param restriction_target: The target reference or literal
|
|
829
|
+
:param converter: The a converter for CURIEs to URIs
|
|
830
|
+
:return: A blank node representing an OWL restriction
|
|
831
|
+
"""
|
|
832
|
+
node = term.BNode()
|
|
833
|
+
graph.add((node, RDF.type, OWL.Restriction))
|
|
834
|
+
graph.add((node, OWL.onProperty, object_property_expression.to_rdflib_node(graph, converter)))
|
|
835
|
+
if isinstance(restriction_target, Box):
|
|
836
|
+
graph.add(
|
|
837
|
+
(node, restriction_predicate, restriction_target.to_rdflib_node(graph, converter))
|
|
838
|
+
)
|
|
839
|
+
else:
|
|
840
|
+
graph.add((node, restriction_predicate, restriction_target))
|
|
841
|
+
return node
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
class _ObjectValuesFrom(ClassExpression):
|
|
845
|
+
#: The predicate used in the OWL restriction, either ``owl:someValuesFrom`` or ``owl:allValuesFrom``
|
|
846
|
+
restriction_predicate: ClassVar[term.URIRef]
|
|
847
|
+
object_property_expression: ObjectPropertyExpression
|
|
848
|
+
class_expression: ClassExpression
|
|
849
|
+
|
|
850
|
+
def __init__(
|
|
851
|
+
self,
|
|
852
|
+
object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint,
|
|
853
|
+
class_expression: ClassExpression | IdentifierBoxOrHint,
|
|
854
|
+
) -> None:
|
|
855
|
+
"""Instantiate a quantification."""
|
|
856
|
+
self.object_property_expression = ObjectPropertyExpression.safe(object_property_expression)
|
|
857
|
+
self.object_expression = ClassExpression.safe(class_expression)
|
|
858
|
+
|
|
859
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
860
|
+
"""Represent this quantification for RDF."""
|
|
861
|
+
return get_owl_restriction(
|
|
862
|
+
graph,
|
|
863
|
+
object_property_expression=self.object_property_expression,
|
|
864
|
+
restriction_predicate=self.restriction_predicate,
|
|
865
|
+
restriction_target=self.object_expression,
|
|
866
|
+
converter=converter,
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
def to_funowl_args(self) -> str:
|
|
870
|
+
"""Get the inside of the functional OWL tag representing the quantification."""
|
|
871
|
+
return f"{self.object_property_expression.to_funowl()} {self.object_expression.to_funowl()}"
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
class ObjectSomeValuesFrom(_ObjectValuesFrom):
|
|
875
|
+
"""A class expression defined in `8.2.1 Existential Quantification <https://www.w3.org/TR/owl2-syntax/#Existential_Quantification>`_."""
|
|
876
|
+
|
|
877
|
+
restriction_predicate: ClassVar[term.URIRef] = OWL.someValuesFrom
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
class ObjectAllValuesFrom(_ObjectValuesFrom):
|
|
881
|
+
"""A class expression defined in `8.2.2 Universal Quantification <https://www.w3.org/TR/owl2-syntax/# Universal_Quantification>`_."""
|
|
882
|
+
|
|
883
|
+
restriction_predicate: ClassVar[term.URIRef] = OWL.allValuesFrom
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
class ObjectHasValue(ClassExpression):
|
|
887
|
+
"""A class expression defined in `8.2.3 Individual Value Restriction <https://www.w3.org/TR/owl2-syntax/#Individual_Value_Restriction>`_."""
|
|
888
|
+
|
|
889
|
+
object_property_expression: ObjectPropertyExpression
|
|
890
|
+
individual: IdentifierBox
|
|
891
|
+
|
|
892
|
+
def __init__(
|
|
893
|
+
self,
|
|
894
|
+
object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint,
|
|
895
|
+
individual: IdentifierBoxOrHint,
|
|
896
|
+
) -> None:
|
|
897
|
+
"""Instantiate an individual value restriction."""
|
|
898
|
+
self.object_property_expression = ObjectPropertyExpression.safe(object_property_expression)
|
|
899
|
+
self.individual = IdentifierBox(individual)
|
|
900
|
+
|
|
901
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
902
|
+
"""Represent the individual value restriction for RDF."""
|
|
903
|
+
individual = self.individual.to_rdflib_node(graph, converter)
|
|
904
|
+
graph.add((individual, RDF.type, OWL.NamedIndividual))
|
|
905
|
+
return get_owl_restriction(
|
|
906
|
+
graph,
|
|
907
|
+
object_property_expression=self.object_property_expression,
|
|
908
|
+
restriction_predicate=OWL.hasValue,
|
|
909
|
+
restriction_target=individual,
|
|
910
|
+
converter=converter,
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
def to_funowl_args(self) -> str:
|
|
914
|
+
"""Get the inside of the functional OWL tag representing the individual value restriction."""
|
|
915
|
+
return f"{self.object_property_expression.to_funowl()} {self.individual.to_funowl()}"
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
class ObjectHasSelf(ClassExpression):
|
|
919
|
+
"""A class expression defined in `8.2.4 Self-Restriction <https://www.w3.org/TR/owl2-syntax/#Self-Restriction>`_."""
|
|
920
|
+
|
|
921
|
+
object_property_expression: ObjectPropertyExpression
|
|
922
|
+
|
|
923
|
+
def __init__(
|
|
924
|
+
self, object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint
|
|
925
|
+
) -> None:
|
|
926
|
+
"""Initialize the model with a property expression."""
|
|
927
|
+
self.object_property_expression = ObjectPropertyExpression.safe(object_property_expression)
|
|
928
|
+
|
|
929
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
930
|
+
"""Represent the self restriction for RDF."""
|
|
931
|
+
return get_owl_restriction(
|
|
932
|
+
graph,
|
|
933
|
+
object_property_expression=self.object_property_expression,
|
|
934
|
+
restriction_predicate=OWL.hasSelf,
|
|
935
|
+
restriction_target=term.Literal(True),
|
|
936
|
+
converter=converter,
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
def to_funowl_args(self) -> str:
|
|
940
|
+
"""Get the inside of the functional OWL tag representing the self-restriction."""
|
|
941
|
+
return self.object_property_expression.to_funowl()
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
class _Cardinality(ClassExpression):
|
|
945
|
+
"""A helper class for object and data cardinality constraints."""
|
|
946
|
+
|
|
947
|
+
property_qualified: ClassVar[term.URIRef]
|
|
948
|
+
property_unqualified: ClassVar[term.URIRef]
|
|
949
|
+
property_type: ClassVar[term.URIRef]
|
|
950
|
+
property_expression_type: ClassVar[term.URIRef] # the datatype for the target_expression
|
|
951
|
+
|
|
952
|
+
cardinality: int
|
|
953
|
+
property_expression: Box
|
|
954
|
+
target_expression: Box | None
|
|
955
|
+
|
|
956
|
+
def __init__(
|
|
957
|
+
self, cardinality: int, property_expression: Box, target_expression: Box | None = None
|
|
958
|
+
) -> None:
|
|
959
|
+
"""Instantiate the cardinality restriction."""
|
|
960
|
+
self.cardinality = cardinality
|
|
961
|
+
self.property_expression = property_expression
|
|
962
|
+
self.target_expression = target_expression
|
|
963
|
+
|
|
964
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
965
|
+
"""Get a node representing the object or data cardinality constraint."""
|
|
966
|
+
node = term.BNode()
|
|
967
|
+
graph.add((node, RDF.type, OWL.Restriction))
|
|
968
|
+
pe_node = self.property_expression.to_rdflib_node(graph, converter)
|
|
969
|
+
# don't annotate the expression type if it's a blank node
|
|
970
|
+
# (i.e., if the property expression is an ObjectInverseOf)
|
|
971
|
+
if not isinstance(pe_node, term.BNode):
|
|
972
|
+
graph.add((pe_node, RDF.type, self.property_expression_type))
|
|
973
|
+
graph.add((node, OWL.onProperty, pe_node))
|
|
974
|
+
literal = term.Literal(str(self.cardinality), datatype=XSD.nonNegativeInteger)
|
|
975
|
+
if self.target_expression is not None:
|
|
976
|
+
graph.add((node, self.property_qualified, literal))
|
|
977
|
+
graph.add(
|
|
978
|
+
(node, self.property_type, self.target_expression.to_rdflib_node(graph, converter))
|
|
979
|
+
)
|
|
980
|
+
else:
|
|
981
|
+
graph.add((node, self.property_unqualified, literal))
|
|
982
|
+
return node
|
|
983
|
+
|
|
984
|
+
def to_funowl_args(self) -> str:
|
|
985
|
+
"""Get the inside of the functional OWL tag representing the cardinality constraint."""
|
|
986
|
+
inside = f"{self.cardinality} {self.property_expression.to_funowl()}"
|
|
987
|
+
if self.target_expression is not None:
|
|
988
|
+
inside += f" {self.target_expression.to_funowl()}"
|
|
989
|
+
return inside
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
class _ObjectCardinality(_Cardinality):
|
|
993
|
+
"""A grouping class for object cardinality models.
|
|
994
|
+
|
|
995
|
+
The three subclasses only differ by the qualified and unqualified
|
|
996
|
+
ranges used.
|
|
997
|
+
|
|
998
|
+
.. image:: https://www.w3.org/TR/owl2-syntax/C_objectcardinality.gif
|
|
999
|
+
"""
|
|
1000
|
+
|
|
1001
|
+
property_type: ClassVar[term.URIRef] = OWL.onClass
|
|
1002
|
+
property_expression_type: ClassVar[term.URIRef] = OWL.ObjectProperty
|
|
1003
|
+
property_expression: ObjectPropertyExpression
|
|
1004
|
+
target_expression: ClassExpression | None
|
|
1005
|
+
|
|
1006
|
+
def __init__(
|
|
1007
|
+
self,
|
|
1008
|
+
cardinality: int,
|
|
1009
|
+
object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint,
|
|
1010
|
+
class_expression: ClassExpression | IdentifierBoxOrHint | None = None,
|
|
1011
|
+
) -> None:
|
|
1012
|
+
"""Instantiate an object cardinality restriction."""
|
|
1013
|
+
super().__init__(
|
|
1014
|
+
cardinality=cardinality,
|
|
1015
|
+
property_expression=ObjectPropertyExpression.safe(object_property_expression),
|
|
1016
|
+
target_expression=ClassExpression.safe(class_expression)
|
|
1017
|
+
if class_expression is not None
|
|
1018
|
+
else None,
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1022
|
+
"""Represent the object cardinality restriction for RDF."""
|
|
1023
|
+
node = super().to_rdflib_node(graph, converter)
|
|
1024
|
+
# we're special-casing this because of the inconsistent way that OWLAPI
|
|
1025
|
+
# adds type assertions when using ObjectInverseOf (for some reason,
|
|
1026
|
+
# it doesn't generate them inside EquivalentClasses)
|
|
1027
|
+
if isinstance(self.property_expression, ObjectInverseOf):
|
|
1028
|
+
self.property_expression.declare_wrapped_ope(graph, converter)
|
|
1029
|
+
return node
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
class ObjectMinCardinality(_ObjectCardinality):
|
|
1033
|
+
"""A class expression defined in `8.3.1 Minimum Cardinality <https://www.w3.org/TR/owl2-syntax/#Minimum_Cardinality>`_."""
|
|
1034
|
+
|
|
1035
|
+
property_qualified: ClassVar[term.URIRef] = OWL.minQualifiedCardinality
|
|
1036
|
+
property_unqualified: ClassVar[term.URIRef] = OWL.minCardinality
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
class ObjectMaxCardinality(_ObjectCardinality):
|
|
1040
|
+
"""A class expression defined in `8.3.2 Maximum Cardinality <https://www.w3.org/TR/owl2-syntax/#Maximum_Cardinality>`_."""
|
|
1041
|
+
|
|
1042
|
+
property_qualified: ClassVar[term.URIRef] = OWL.maxQualifiedCardinality
|
|
1043
|
+
property_unqualified: ClassVar[term.URIRef] = OWL.maxCardinality
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
class ObjectExactCardinality(_ObjectCardinality):
|
|
1047
|
+
"""A class expression defined in `8.3.2 Exact Cardinality <https://www.w3.org/TR/owl2-syntax/#Exact_Cardinality>`_."""
|
|
1048
|
+
|
|
1049
|
+
property_qualified: ClassVar[term.URIRef] = OWL.qualifiedCardinality
|
|
1050
|
+
property_unqualified: ClassVar[term.URIRef] = OWL.cardinality
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
class _DataValuesFrom(ClassExpression):
|
|
1054
|
+
"""A class expression defined in https://www.w3.org/TR/owl2-syntax/#Existential_Quantification_2."""
|
|
1055
|
+
|
|
1056
|
+
property_type: ClassVar[term.URIRef]
|
|
1057
|
+
data_property_expressions: Sequence[DataPropertyExpression]
|
|
1058
|
+
data_range: DataRange
|
|
1059
|
+
|
|
1060
|
+
def __init__(
|
|
1061
|
+
self,
|
|
1062
|
+
data_property_expressions: list[DataPropertyExpression | IdentifierBoxOrHint],
|
|
1063
|
+
data_range: DataRange | IdentifierBoxOrHint,
|
|
1064
|
+
) -> None:
|
|
1065
|
+
"""Instantiate a data values existential quantification."""
|
|
1066
|
+
if not data_property_expressions:
|
|
1067
|
+
raise ValueError
|
|
1068
|
+
if len(data_property_expressions) >= 2:
|
|
1069
|
+
raise NotImplementedError(
|
|
1070
|
+
"while the OWL 2 spec states that there can be multiple data property "
|
|
1071
|
+
"expressions, there is no explanation on what should be done if many "
|
|
1072
|
+
"appear. Therefore, it's left as unimplemented for now."
|
|
1073
|
+
)
|
|
1074
|
+
self.data_property_expressions = [
|
|
1075
|
+
DataPropertyExpression.safe(dpe) for dpe in data_property_expressions
|
|
1076
|
+
]
|
|
1077
|
+
self.data_range = DataRange.safe(data_range)
|
|
1078
|
+
|
|
1079
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.BNode:
|
|
1080
|
+
"""Represent the data values existential quantification for RDF."""
|
|
1081
|
+
node = term.BNode()
|
|
1082
|
+
graph.add((node, RDF.type, OWL.Restriction))
|
|
1083
|
+
p_o = _get_data_value_po(graph, converter, self.data_property_expressions)
|
|
1084
|
+
graph.add((node, self.property_type, self.data_range.to_rdflib_node(graph, converter)))
|
|
1085
|
+
graph.add((node, *p_o))
|
|
1086
|
+
return node
|
|
1087
|
+
|
|
1088
|
+
def to_funowl_args(self) -> str:
|
|
1089
|
+
"""Get the inside of the functional OWL tag representing the existential quantification."""
|
|
1090
|
+
return list_to_funowl((*self.data_property_expressions, self.data_range))
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
def _get_data_value_po(
|
|
1094
|
+
graph, converter, dpes: Sequence[DataPropertyExpression]
|
|
1095
|
+
) -> tuple[term.URIRef, term.IdentifiedNode]:
|
|
1096
|
+
if len(dpes) >= 2:
|
|
1097
|
+
# Note that this is currently not possible to get to with
|
|
1098
|
+
return OWL.onProperties, _make_sequence(graph, dpes, converter=converter)
|
|
1099
|
+
else:
|
|
1100
|
+
return OWL.onProperty, dpes[0].to_rdflib_node(graph, converter)
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
class DataSomeValuesFrom(_DataValuesFrom):
|
|
1104
|
+
"""A class expression defined in `8.4.1 Existential Qualifications <https://www.w3.org/TR/owl2-syntax/#Existential_Quantification_2>`_."""
|
|
1105
|
+
|
|
1106
|
+
property_type: ClassVar[term.URIRef] = OWL.someValuesFrom
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
class DataAllValuesFrom(_DataValuesFrom):
|
|
1110
|
+
"""A class expression defined in `8.4.2 Universal Qualifications <https://www.w3.org/TR/owl2-syntax/#Universal_Quantification_2>`_."""
|
|
1111
|
+
|
|
1112
|
+
property_type: ClassVar[term.URIRef] = OWL.allValuesFrom
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
class DataHasValue(_DataValuesFrom):
|
|
1116
|
+
"""A class expression defined in `8.4.3 Literal Value Restriction <https://www.w3.org/TR/owl2-syntax/#Literal_Value_Restriction>`_."""
|
|
1117
|
+
|
|
1118
|
+
property_type: ClassVar[term.URIRef] = OWL.hasValue
|
|
1119
|
+
data_property_expression: DataPropertyExpression
|
|
1120
|
+
literal: LiteralBox
|
|
1121
|
+
|
|
1122
|
+
def __init__(
|
|
1123
|
+
self,
|
|
1124
|
+
data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
|
|
1125
|
+
literal: LiteralBoxOrHint,
|
|
1126
|
+
) -> None:
|
|
1127
|
+
"""Instantiate a literal value restriction."""
|
|
1128
|
+
self.data_property_expression = DataPropertyExpression.safe(data_property_expression)
|
|
1129
|
+
self.literal = LiteralBox(literal)
|
|
1130
|
+
|
|
1131
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.BNode:
|
|
1132
|
+
"""Represent this literal value restriction for RDF."""
|
|
1133
|
+
node = term.BNode()
|
|
1134
|
+
graph.add((node, RDF.type, OWL.Restriction))
|
|
1135
|
+
graph.add((node, OWL.hasValue, self.literal.to_rdflib_node(graph, converter)))
|
|
1136
|
+
graph.add(
|
|
1137
|
+
(node, OWL.onProperty, self.data_property_expression.to_rdflib_node(graph, converter))
|
|
1138
|
+
)
|
|
1139
|
+
return node
|
|
1140
|
+
|
|
1141
|
+
def to_funowl_args(self) -> str:
|
|
1142
|
+
"""Get the inside of the functional OWL tag representing the literal value restriction."""
|
|
1143
|
+
return f"{self.data_property_expression.to_funowl()} {self.literal.to_funowl()}"
|
|
1144
|
+
|
|
1145
|
+
|
|
1146
|
+
class _DataCardinality(_Cardinality):
|
|
1147
|
+
"""A grouping class for data cardinality models.
|
|
1148
|
+
|
|
1149
|
+
The three subclasses only differ by the qualified and unqualified
|
|
1150
|
+
ranges used.
|
|
1151
|
+
"""
|
|
1152
|
+
|
|
1153
|
+
property_type: ClassVar[term.URIRef] = OWL.onDataRange
|
|
1154
|
+
property_expression_type: ClassVar[term.URIRef] = OWL.DatatypeProperty
|
|
1155
|
+
property_expression: DataPropertyExpression
|
|
1156
|
+
target_expression: DataRange | None
|
|
1157
|
+
|
|
1158
|
+
def __init__(
|
|
1159
|
+
self,
|
|
1160
|
+
cardinality: int,
|
|
1161
|
+
data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
|
|
1162
|
+
data_range: DataRange | IdentifierBoxOrHint | None = None,
|
|
1163
|
+
) -> None:
|
|
1164
|
+
"""Instantiate a data cardinality restriction."""
|
|
1165
|
+
super().__init__(
|
|
1166
|
+
cardinality,
|
|
1167
|
+
DataPropertyExpression.safe(data_property_expression),
|
|
1168
|
+
DataRange.safe(data_range) if data_range is not None else None,
|
|
1169
|
+
)
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
class DataMinCardinality(_DataCardinality):
|
|
1173
|
+
"""A class expression defined in `8.5.1 Minimum Cardinality <https://www.w3.org/TR/owl2-syntax/#Minimum_Cardinality_2>`_."""
|
|
1174
|
+
|
|
1175
|
+
property_qualified: ClassVar[term.URIRef] = OWL.minQualifiedCardinality
|
|
1176
|
+
property_unqualified: ClassVar[term.URIRef] = OWL.minCardinality
|
|
1177
|
+
|
|
1178
|
+
|
|
1179
|
+
class DataMaxCardinality(_DataCardinality):
|
|
1180
|
+
"""A class expression defined in `8.5.2 Maximum Cardinality <https://www.w3.org/TR/owl2-syntax/#Maximum_Cardinality_2>`_."""
|
|
1181
|
+
|
|
1182
|
+
property_qualified: ClassVar[term.URIRef] = OWL.maxQualifiedCardinality
|
|
1183
|
+
property_unqualified: ClassVar[term.URIRef] = OWL.maxCardinality
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
class DataExactCardinality(_DataCardinality):
|
|
1187
|
+
"""A class expression defined in `8.5.3 Exact Cardinality <https://www.w3.org/TR/owl2-syntax/#Exact_Cardinality_2>`_."""
|
|
1188
|
+
|
|
1189
|
+
property_qualified: ClassVar[term.URIRef] = OWL.qualifiedCardinality
|
|
1190
|
+
property_unqualified: ClassVar[term.URIRef] = OWL.cardinality
|
|
1191
|
+
|
|
1192
|
+
|
|
1193
|
+
"""
|
|
1194
|
+
`Section 9: Axioms <https://www.w3.org/TR/owl2-syntax/#Axioms>`_
|
|
1195
|
+
"""
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
class Axiom(Box):
|
|
1199
|
+
"""A model for an `axiom <https://www.w3.org/TR/owl2-syntax/#Axioms>`_."""
|
|
1200
|
+
|
|
1201
|
+
annotations: list[Annotation]
|
|
1202
|
+
|
|
1203
|
+
def __init__(self, annotations: list[Annotation] | None = None) -> None:
|
|
1204
|
+
"""Instantiate an axiom, with an optional list of annotations."""
|
|
1205
|
+
self.annotations = annotations or []
|
|
1206
|
+
|
|
1207
|
+
def to_funowl_args(self) -> str:
|
|
1208
|
+
"""Get the functional OWL tag representing the axiom."""
|
|
1209
|
+
if self.annotations:
|
|
1210
|
+
return list_to_funowl(self.annotations) + " " + self._funowl_inside_2()
|
|
1211
|
+
return self._funowl_inside_2()
|
|
1212
|
+
|
|
1213
|
+
@abstractmethod
|
|
1214
|
+
def _funowl_inside_2(self) -> str:
|
|
1215
|
+
"""Get the inside of the functional OWL tag representing the axiom."""
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
class ClassAxiom(Axiom):
|
|
1219
|
+
"""A model for a class axiom."""
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
def _add_triple(
|
|
1223
|
+
graph: Graph,
|
|
1224
|
+
s: term.IdentifiedNode,
|
|
1225
|
+
p: term.IdentifiedNode,
|
|
1226
|
+
o: term.IdentifiedNode | term.Literal,
|
|
1227
|
+
annotations: Annotations | None = None,
|
|
1228
|
+
*,
|
|
1229
|
+
converter: Converter,
|
|
1230
|
+
) -> term.BNode:
|
|
1231
|
+
graph.add((s, p, o))
|
|
1232
|
+
return _add_triple_annotations(graph, s, p, o, annotations=annotations, converter=converter)
|
|
1233
|
+
|
|
1234
|
+
|
|
1235
|
+
def _add_triple_annotations(
|
|
1236
|
+
graph: Graph,
|
|
1237
|
+
s: term.IdentifiedNode,
|
|
1238
|
+
p: term.IdentifiedNode,
|
|
1239
|
+
o: term.IdentifiedNode | term.Literal,
|
|
1240
|
+
*,
|
|
1241
|
+
annotations: Annotations | None = None,
|
|
1242
|
+
type: term.URIRef | None = None,
|
|
1243
|
+
converter: Converter,
|
|
1244
|
+
force_for_negative_assertion: bool = False,
|
|
1245
|
+
reified_s=OWL.annotatedSource,
|
|
1246
|
+
reified_p=OWL.annotatedProperty,
|
|
1247
|
+
reified_o=OWL.annotatedTarget,
|
|
1248
|
+
) -> term.BNode:
|
|
1249
|
+
# in order to represent annotations on a triple,
|
|
1250
|
+
# we need to "reify" the triple, which means to
|
|
1251
|
+
# represent it with a blank node
|
|
1252
|
+
reified_triple = term.BNode()
|
|
1253
|
+
if not annotations and not force_for_negative_assertion:
|
|
1254
|
+
return reified_triple
|
|
1255
|
+
if type is None:
|
|
1256
|
+
type = OWL.Axiom
|
|
1257
|
+
graph.add((reified_triple, RDF.type, type))
|
|
1258
|
+
graph.add((reified_triple, reified_s, s))
|
|
1259
|
+
graph.add((reified_triple, reified_p, p))
|
|
1260
|
+
graph.add((reified_triple, reified_o, o))
|
|
1261
|
+
for annotation in annotations or []:
|
|
1262
|
+
annotation._add_to_triple(graph, reified_triple, converter)
|
|
1263
|
+
return reified_triple
|
|
1264
|
+
|
|
1265
|
+
|
|
1266
|
+
class SubClassOf(ClassAxiom):
|
|
1267
|
+
r"""A class axiom defined in `9.1.1 "Subclass Axioms" <https://www.w3.org/TR/owl2-syntax/#Subclass_Axioms>`_.
|
|
1268
|
+
|
|
1269
|
+
Example:
|
|
1270
|
+
>>> SubClassOf("a:Baby", "a:Child") # Each baby is a child.
|
|
1271
|
+
>>> SubClassOf("a:Child", "a:Person") # Each child is a person.
|
|
1272
|
+
>>> ClassAssertion("a:Baby", "a:Stewie") # Stewie is a baby.
|
|
1273
|
+
|
|
1274
|
+
This axiom can be applied to more complicated expressions. For example,
|
|
1275
|
+
here's the long form for a :class:`FunctionalDataProperty`:
|
|
1276
|
+
|
|
1277
|
+
>>> axiom = SubClassOf("owl:Thing", DataMaxCardinality(1, "a:hasAge"))
|
|
1278
|
+
|
|
1279
|
+
which itself is eqivalent to:
|
|
1280
|
+
|
|
1281
|
+
>>> FunctionalDataProperty("a:hasAge")
|
|
1282
|
+
"""
|
|
1283
|
+
|
|
1284
|
+
child: ClassExpression
|
|
1285
|
+
parent: ClassExpression
|
|
1286
|
+
|
|
1287
|
+
def __init__(
|
|
1288
|
+
self,
|
|
1289
|
+
child: ClassExpression | IdentifierBoxOrHint,
|
|
1290
|
+
parent: ClassExpression | IdentifierBoxOrHint,
|
|
1291
|
+
*,
|
|
1292
|
+
annotations: Annotations | None = None,
|
|
1293
|
+
) -> None:
|
|
1294
|
+
"""Initialize a subclass axiom."""
|
|
1295
|
+
self.child = ClassExpression.safe(child)
|
|
1296
|
+
self.parent = ClassExpression.safe(parent)
|
|
1297
|
+
super().__init__(annotations)
|
|
1298
|
+
|
|
1299
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1300
|
+
"""Represent the subclass axiom for RDF."""
|
|
1301
|
+
s = self.child.to_rdflib_node(graph, converter)
|
|
1302
|
+
o = self.parent.to_rdflib_node(graph, converter)
|
|
1303
|
+
return _add_triple(graph, s, RDFS.subClassOf, o, self.annotations, converter=converter)
|
|
1304
|
+
|
|
1305
|
+
def _funowl_inside_2(self) -> str:
|
|
1306
|
+
return f"{self.child.to_funowl()} {self.parent.to_funowl()}"
|
|
1307
|
+
|
|
1308
|
+
|
|
1309
|
+
class EquivalentClasses(ClassAxiom):
|
|
1310
|
+
"""A class axiom defined in `9.1.2 "Subclass Axioms" <https://www.w3.org/TR/owl2-syntax/#Equivalent_Classes>`_.
|
|
1311
|
+
|
|
1312
|
+
>>> EquivalentClasses(
|
|
1313
|
+
... [
|
|
1314
|
+
... "a:GriffinFamilyMember",
|
|
1315
|
+
... ObjectOneOf(["a:Peter", "a:Lois", "a:Stewie", "a:Meg", "a:Chris", "a:Brian"]),
|
|
1316
|
+
... ]
|
|
1317
|
+
... )
|
|
1318
|
+
"""
|
|
1319
|
+
|
|
1320
|
+
class_expression: Sequence[ClassExpression]
|
|
1321
|
+
|
|
1322
|
+
def __init__(
|
|
1323
|
+
self,
|
|
1324
|
+
class_expressions: Sequence[ClassExpression | IdentifierBoxOrHint],
|
|
1325
|
+
*,
|
|
1326
|
+
annotations: Annotations | None = None,
|
|
1327
|
+
) -> None:
|
|
1328
|
+
"""Initialize a equivalent class axiom."""
|
|
1329
|
+
if len(class_expressions) < 2:
|
|
1330
|
+
raise ValueError
|
|
1331
|
+
self.class_expressions = [ClassExpression.safe(ce) for ce in class_expressions]
|
|
1332
|
+
super().__init__(annotations)
|
|
1333
|
+
|
|
1334
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1335
|
+
"""Represent the equivalent class axiom for RDF."""
|
|
1336
|
+
rv = term.BNode()
|
|
1337
|
+
nodes = [ce.to_rdflib_node(graph, converter) for ce in self.class_expressions]
|
|
1338
|
+
for s, o in itt.pairwise(nodes):
|
|
1339
|
+
_add_triple(graph, s, OWL.equivalentClass, o, self.annotations, converter=converter)
|
|
1340
|
+
# TODO connect all triples to this BNode?
|
|
1341
|
+
return rv
|
|
1342
|
+
|
|
1343
|
+
def _funowl_inside_2(self) -> str:
|
|
1344
|
+
return list_to_funowl(self.class_expressions)
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
class DisjointClasses(ClassAxiom):
|
|
1348
|
+
"""A class axiom defined in `9.1.3 "Disjoint Classes" <https://www.w3.org/TR/owl2-syntax/#Disjoint_Classes>`_.
|
|
1349
|
+
|
|
1350
|
+
>>> DisjointClasses("a:Boy a:Girl".split())
|
|
1351
|
+
"""
|
|
1352
|
+
|
|
1353
|
+
class_expression: Sequence[ClassExpression]
|
|
1354
|
+
|
|
1355
|
+
def __init__(
|
|
1356
|
+
self,
|
|
1357
|
+
class_expressions: Sequence[ClassExpression | IdentifierBoxOrHint],
|
|
1358
|
+
*,
|
|
1359
|
+
annotations: Annotations | None = None,
|
|
1360
|
+
) -> None:
|
|
1361
|
+
"""Initialize a disjoint classes axiom."""
|
|
1362
|
+
if len(class_expressions) < 2:
|
|
1363
|
+
raise ValueError
|
|
1364
|
+
self.class_expressions = [ClassExpression.safe(ce) for ce in class_expressions]
|
|
1365
|
+
super().__init__(annotations)
|
|
1366
|
+
|
|
1367
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1368
|
+
"""Represent the disjoint classes axiom for RDF."""
|
|
1369
|
+
nodes = [ce.to_rdflib_node(graph, converter) for ce in self.class_expressions]
|
|
1370
|
+
if len(nodes) == 2:
|
|
1371
|
+
return _add_triple(
|
|
1372
|
+
graph, nodes[0], OWL.disjointWith, nodes[1], self.annotations, converter=converter
|
|
1373
|
+
)
|
|
1374
|
+
else:
|
|
1375
|
+
node = term.BNode()
|
|
1376
|
+
graph.add((node, RDF.type, OWL.AllDisjointClasses))
|
|
1377
|
+
_add_triple(
|
|
1378
|
+
graph,
|
|
1379
|
+
node,
|
|
1380
|
+
OWL.members,
|
|
1381
|
+
_make_sequence_nodes(graph, nodes),
|
|
1382
|
+
self.annotations,
|
|
1383
|
+
converter=converter,
|
|
1384
|
+
)
|
|
1385
|
+
return node
|
|
1386
|
+
|
|
1387
|
+
def _funowl_inside_2(self) -> str:
|
|
1388
|
+
return list_to_funowl(self.class_expressions)
|
|
1389
|
+
|
|
1390
|
+
|
|
1391
|
+
class DisjointUnion(ClassAxiom):
|
|
1392
|
+
"""A class axiom defined in `9.1.4 "Disjoint Union of Class Expressions" <https://www.w3.org/TR/owl2-syntax/#Disjoint_Union_of_Class_Expressions>`_."""
|
|
1393
|
+
|
|
1394
|
+
parent: SimpleClassExpression
|
|
1395
|
+
class_expression: Sequence[ClassExpression]
|
|
1396
|
+
|
|
1397
|
+
def __init__(
|
|
1398
|
+
self,
|
|
1399
|
+
parent: IdentifierBoxOrHint,
|
|
1400
|
+
class_expressions: Sequence[ClassExpression | IdentifierBoxOrHint],
|
|
1401
|
+
*,
|
|
1402
|
+
annotations: Annotations | None = None,
|
|
1403
|
+
) -> None:
|
|
1404
|
+
"""Initialize a disjoint union of class expressions axiom."""
|
|
1405
|
+
if len(class_expressions) < 2:
|
|
1406
|
+
raise ValueError
|
|
1407
|
+
self.parent = SimpleClassExpression(parent)
|
|
1408
|
+
self.class_expressions = [ClassExpression.safe(ce) for ce in class_expressions]
|
|
1409
|
+
super().__init__(annotations)
|
|
1410
|
+
|
|
1411
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1412
|
+
"""Represent the disjoint union of class expressions axiom as RDF."""
|
|
1413
|
+
return _add_triple(
|
|
1414
|
+
graph,
|
|
1415
|
+
self.parent.to_rdflib_node(graph, converter),
|
|
1416
|
+
OWL.disjointUnionOf,
|
|
1417
|
+
_make_sequence(graph, self.class_expressions, converter),
|
|
1418
|
+
self.annotations,
|
|
1419
|
+
converter=converter,
|
|
1420
|
+
)
|
|
1421
|
+
|
|
1422
|
+
def _funowl_inside_2(self) -> str:
|
|
1423
|
+
return list_to_funowl((self.parent, *self.class_expressions))
|
|
1424
|
+
|
|
1425
|
+
|
|
1426
|
+
"""Section 9.2: Object Property Axioms"""
|
|
1427
|
+
|
|
1428
|
+
|
|
1429
|
+
class ObjectPropertyAxiom(Axiom):
|
|
1430
|
+
"""A grouping class for `9.2 "Object Property Axioms" <https://www.w3.org/TR/owl2-syntax/#Object_Property_Axioms>`_.
|
|
1431
|
+
|
|
1432
|
+
.. image:: https://www.w3.org/TR/owl2-syntax/A_objectproperty2.gif
|
|
1433
|
+
"""
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
class ObjectPropertyChain(Box):
|
|
1437
|
+
"""Represents a list of object properties."""
|
|
1438
|
+
|
|
1439
|
+
object_property_expressions: Sequence[ObjectPropertyExpression]
|
|
1440
|
+
|
|
1441
|
+
def __init__(
|
|
1442
|
+
self, object_property_expressions: Sequence[ObjectPropertyExpression | IdentifierBoxOrHint]
|
|
1443
|
+
):
|
|
1444
|
+
"""Instantiate a list of object property expressions."""
|
|
1445
|
+
self.object_property_expressions = [
|
|
1446
|
+
ObjectPropertyExpression.safe(ope) for ope in object_property_expressions
|
|
1447
|
+
]
|
|
1448
|
+
|
|
1449
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1450
|
+
"""Represent the list of object property expressions for RDF."""
|
|
1451
|
+
nodes = [ope.to_rdflib_node(graph, converter) for ope in self.object_property_expressions]
|
|
1452
|
+
return _make_sequence_nodes(graph, nodes)
|
|
1453
|
+
|
|
1454
|
+
def to_funowl_args(self) -> str:
|
|
1455
|
+
"""Get the inside of the functional OWL tag representing the object property chain."""
|
|
1456
|
+
return list_to_funowl(self.object_property_expressions)
|
|
1457
|
+
|
|
1458
|
+
|
|
1459
|
+
SubObjectPropertyExpression: TypeAlias = ObjectPropertyExpression | ObjectPropertyChain
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
class SubObjectPropertyOf(ObjectPropertyAxiom): # 9.2.1
|
|
1463
|
+
"""An object property axiom defined in `9.2.1 "Object Subproperties" <https://www.w3.org/TR/owl2-syntax/#Object_Subproperties>`_."""
|
|
1464
|
+
|
|
1465
|
+
child: SubObjectPropertyExpression
|
|
1466
|
+
parent: ObjectPropertyExpression
|
|
1467
|
+
|
|
1468
|
+
def __init__(
|
|
1469
|
+
self,
|
|
1470
|
+
child: SubObjectPropertyExpression | IdentifierBoxOrHint,
|
|
1471
|
+
parent: ObjectPropertyExpression | IdentifierBoxOrHint,
|
|
1472
|
+
*,
|
|
1473
|
+
annotations: Annotations | None = None,
|
|
1474
|
+
) -> None:
|
|
1475
|
+
"""Instantiate an object subproperty axiom."""
|
|
1476
|
+
if isinstance(child, ObjectPropertyChain):
|
|
1477
|
+
self.child = child
|
|
1478
|
+
else:
|
|
1479
|
+
self.child = ObjectPropertyExpression.safe(child)
|
|
1480
|
+
self.parent = ObjectPropertyExpression.safe(parent)
|
|
1481
|
+
super().__init__(annotations)
|
|
1482
|
+
|
|
1483
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1484
|
+
"""Represent the object subproperty axiom for RDF."""
|
|
1485
|
+
s = self.child.to_rdflib_node(graph, converter)
|
|
1486
|
+
o = self.parent.to_rdflib_node(graph, converter)
|
|
1487
|
+
if isinstance(self.child, ObjectInverseOf):
|
|
1488
|
+
self.child.declare_wrapped_ope(graph, converter)
|
|
1489
|
+
if isinstance(self.parent, ObjectInverseOf):
|
|
1490
|
+
self.parent.declare_wrapped_ope(graph, converter)
|
|
1491
|
+
if isinstance(self.child, ObjectPropertyChain):
|
|
1492
|
+
return _add_triple(
|
|
1493
|
+
graph, o, OWL.propertyChainAxiom, s, self.annotations, converter=converter
|
|
1494
|
+
)
|
|
1495
|
+
else:
|
|
1496
|
+
return _add_triple(
|
|
1497
|
+
graph, s, RDFS.subPropertyOf, o, self.annotations, converter=converter
|
|
1498
|
+
)
|
|
1499
|
+
|
|
1500
|
+
def _funowl_inside_2(self) -> str:
|
|
1501
|
+
return f"{self.child.to_funowl()} {self.parent.to_funowl()}"
|
|
1502
|
+
|
|
1503
|
+
|
|
1504
|
+
class _ObjectPropertyList(ObjectPropertyAxiom):
|
|
1505
|
+
"""A model for an object property axiom that accepts a list of object property expressions."""
|
|
1506
|
+
|
|
1507
|
+
object_property_expressions: Sequence[ObjectPropertyExpression]
|
|
1508
|
+
|
|
1509
|
+
def __init__(
|
|
1510
|
+
self,
|
|
1511
|
+
object_property_expressions: Sequence[ObjectPropertyExpression | IdentifierBoxOrHint],
|
|
1512
|
+
*,
|
|
1513
|
+
annotations: Annotations | None = None,
|
|
1514
|
+
) -> None:
|
|
1515
|
+
"""Instntiate an object property list."""
|
|
1516
|
+
if len(object_property_expressions) < 2:
|
|
1517
|
+
raise ValueError
|
|
1518
|
+
self.object_property_expressions = [
|
|
1519
|
+
ObjectPropertyExpression.safe(ope) for ope in object_property_expressions
|
|
1520
|
+
]
|
|
1521
|
+
super().__init__(annotations)
|
|
1522
|
+
|
|
1523
|
+
def _funowl_inside_2(self) -> str:
|
|
1524
|
+
return list_to_funowl(self.object_property_expressions)
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
def _equivalent_xxx(
|
|
1528
|
+
graph: Graph,
|
|
1529
|
+
expressions: Iterable[Box],
|
|
1530
|
+
*,
|
|
1531
|
+
annotations: Annotations | None = None,
|
|
1532
|
+
converter: Converter,
|
|
1533
|
+
) -> term.BNode:
|
|
1534
|
+
nodes = [expression.to_rdflib_node(graph, converter) for expression in expressions]
|
|
1535
|
+
for s, o in itt.combinations(nodes, 2):
|
|
1536
|
+
_add_triple(graph, s, OWL.equivalentProperty, o, annotations, converter=converter)
|
|
1537
|
+
# an unused blank node is returned here, since adding
|
|
1538
|
+
# these relationship doesn't correspond to a node itself
|
|
1539
|
+
return term.BNode()
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
class EquivalentObjectProperties(_ObjectPropertyList):
|
|
1543
|
+
"""An object property axiom defined in `9.2.2 "Equivalent Object Subproperties" <https://www.w3.org/TR/owl2-syntax/#Equivalent_Object_Properties>`_."""
|
|
1544
|
+
|
|
1545
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1546
|
+
"""Represent the equivalent object subproperties axiom for RDF."""
|
|
1547
|
+
return _equivalent_xxx(
|
|
1548
|
+
graph,
|
|
1549
|
+
self.object_property_expressions,
|
|
1550
|
+
annotations=self.annotations,
|
|
1551
|
+
converter=converter,
|
|
1552
|
+
)
|
|
1553
|
+
|
|
1554
|
+
|
|
1555
|
+
def _disjoint_xxx(
|
|
1556
|
+
graph: Graph,
|
|
1557
|
+
expressions: Iterable[Box],
|
|
1558
|
+
*,
|
|
1559
|
+
annotations: Annotations | None = None,
|
|
1560
|
+
converter: Converter,
|
|
1561
|
+
) -> term.BNode:
|
|
1562
|
+
nodes = [expression.to_rdflib_node(graph, converter) for expression in expressions]
|
|
1563
|
+
nodes = sorted(nodes, key=str)
|
|
1564
|
+
if len(nodes) == 2:
|
|
1565
|
+
return _add_triple(
|
|
1566
|
+
graph, nodes[0], OWL.propertyDisjointWith, nodes[1], annotations, converter=converter
|
|
1567
|
+
)
|
|
1568
|
+
else:
|
|
1569
|
+
node = term.BNode()
|
|
1570
|
+
graph.add((node, RDF.type, OWL.AllDisjointProperties))
|
|
1571
|
+
_add_triple(
|
|
1572
|
+
graph,
|
|
1573
|
+
node,
|
|
1574
|
+
OWL.members,
|
|
1575
|
+
_make_sequence_nodes(graph, nodes),
|
|
1576
|
+
annotations,
|
|
1577
|
+
converter=converter,
|
|
1578
|
+
)
|
|
1579
|
+
return node
|
|
1580
|
+
|
|
1581
|
+
|
|
1582
|
+
class DisjointObjectProperties(_ObjectPropertyList): # 9.2.3
|
|
1583
|
+
"""An object property axiom defined in `9.2.3 "Disjoint Object Properties" <https://www.w3.org/TR/owl2-syntax/#Disjoint_Object_Properties>`_."""
|
|
1584
|
+
|
|
1585
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1586
|
+
"""Represent the disjoint object properties axiom for RDF."""
|
|
1587
|
+
return _disjoint_xxx(
|
|
1588
|
+
graph,
|
|
1589
|
+
self.object_property_expressions,
|
|
1590
|
+
annotations=self.annotations,
|
|
1591
|
+
converter=converter,
|
|
1592
|
+
)
|
|
1593
|
+
|
|
1594
|
+
|
|
1595
|
+
class InverseObjectProperties(ObjectPropertyAxiom): # 9.2.4
|
|
1596
|
+
"""An object property axiom defined in `9.2.4 "Inverse Object Properties" <https://www.w3.org/TR/owl2-syntax/#Inverse_Object_Properties_2>`_.
|
|
1597
|
+
|
|
1598
|
+
For example, having a father is the opposite of being a father of someone:
|
|
1599
|
+
|
|
1600
|
+
>>> InverseObjectProperties("a:hasFather", "a:fatherOf")
|
|
1601
|
+
"""
|
|
1602
|
+
|
|
1603
|
+
left: ObjectPropertyExpression
|
|
1604
|
+
right: ObjectPropertyExpression
|
|
1605
|
+
|
|
1606
|
+
def __init__(
|
|
1607
|
+
self,
|
|
1608
|
+
left: ObjectPropertyExpression | IdentifierBoxOrHint,
|
|
1609
|
+
right: ObjectPropertyExpression | IdentifierBoxOrHint,
|
|
1610
|
+
*,
|
|
1611
|
+
annotations: Annotations | None = None,
|
|
1612
|
+
) -> None:
|
|
1613
|
+
"""Initialize an inverse object property axiom."""
|
|
1614
|
+
self.left = ObjectPropertyExpression.safe(left)
|
|
1615
|
+
self.right = ObjectPropertyExpression.safe(right)
|
|
1616
|
+
super().__init__(annotations)
|
|
1617
|
+
|
|
1618
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1619
|
+
"""Represent the inverse object property axiom for RDF."""
|
|
1620
|
+
# note these are backwards, since everything is backwards in OFN :shrug:
|
|
1621
|
+
s = self.right.to_rdflib_node(graph, converter)
|
|
1622
|
+
o = self.left.to_rdflib_node(graph, converter)
|
|
1623
|
+
return _add_triple(graph, s, OWL.inverseOf, o, self.annotations, converter=converter)
|
|
1624
|
+
|
|
1625
|
+
def _funowl_inside_2(self) -> str:
|
|
1626
|
+
return f"{self.left.to_funowl()} {self.right.to_funowl()}"
|
|
1627
|
+
|
|
1628
|
+
|
|
1629
|
+
class _ObjectPropertyTyping(ObjectPropertyAxiom): # 9.2.4
|
|
1630
|
+
"""An object property axiom for a range or domain."""
|
|
1631
|
+
|
|
1632
|
+
property_type: ClassVar[term.URIRef]
|
|
1633
|
+
object_property_expression: ObjectPropertyExpression
|
|
1634
|
+
value: ClassExpression
|
|
1635
|
+
|
|
1636
|
+
def __init__(
|
|
1637
|
+
self,
|
|
1638
|
+
left: ObjectPropertyExpression | IdentifierBoxOrHint,
|
|
1639
|
+
right: ClassExpression | IdentifierBoxOrHint,
|
|
1640
|
+
*,
|
|
1641
|
+
annotations: Annotations | None = None,
|
|
1642
|
+
) -> None:
|
|
1643
|
+
"""Initialize a object property domain or range."""
|
|
1644
|
+
self.object_property_expression = ObjectPropertyExpression.safe(left)
|
|
1645
|
+
self.value = ClassExpression.safe(right)
|
|
1646
|
+
super().__init__(annotations)
|
|
1647
|
+
|
|
1648
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1649
|
+
"""Represent the object property domain or range for RDF."""
|
|
1650
|
+
s = self.object_property_expression.to_rdflib_node(graph, converter)
|
|
1651
|
+
o = self.value.to_rdflib_node(graph, converter)
|
|
1652
|
+
return _add_triple(graph, s, self.property_type, o, self.annotations, converter=converter)
|
|
1653
|
+
|
|
1654
|
+
def _funowl_inside_2(self) -> str:
|
|
1655
|
+
return f"{self.object_property_expression.to_funowl()} {self.value.to_funowl()}"
|
|
1656
|
+
|
|
1657
|
+
|
|
1658
|
+
class ObjectPropertyDomain(_ObjectPropertyTyping): # 9.2.5
|
|
1659
|
+
"""An object property axiom defined in `9.2.5 "Object Property Domain" <https://www.w3.org/TR/owl2-syntax/#Object_Property_Domain>`_.
|
|
1660
|
+
|
|
1661
|
+
Consider the ontology consisting of the following axioms.
|
|
1662
|
+
|
|
1663
|
+
>>> ObjectPropertyDomain("a:hasDog", "a:Person") # Only people can own dogs.
|
|
1664
|
+
>>> ObjectPropertyAssertion("a:hasDog", "a:Peter", "a:Brian") # Brian is a dog of Peter.
|
|
1665
|
+
|
|
1666
|
+
This ontology therefore entails:
|
|
1667
|
+
|
|
1668
|
+
>>> ClassAssertion("a:Person", "a:Peter") # Peter is a person
|
|
1669
|
+
"""
|
|
1670
|
+
|
|
1671
|
+
property_type: ClassVar[term.URIRef] = RDFS.domain
|
|
1672
|
+
|
|
1673
|
+
|
|
1674
|
+
class ObjectPropertyRange(_ObjectPropertyTyping): # 9.2.6
|
|
1675
|
+
"""An object property axiom defined in `9.2.5 "Object Property Range" <https://www.w3.org/TR/owl2-syntax/#Object_Property_Range>`_.
|
|
1676
|
+
|
|
1677
|
+
Consider the ontology consisting of the following axioms.
|
|
1678
|
+
|
|
1679
|
+
>>> # The range of the a:hasDog property is the class a:Dog.
|
|
1680
|
+
>>> ObjectPropertyRange("a:hasDog", "a:Dog")
|
|
1681
|
+
>>> ObjectPropertyAssertion("a:hasDog", "a:Peter", "a:Brian") # Brian is a dog of Peter.
|
|
1682
|
+
|
|
1683
|
+
By the first axiom, each individual that has an incoming
|
|
1684
|
+
``a:hasDog`` connection must be an instance of ``a:Dog``.
|
|
1685
|
+
Therefore, ``a:Brian`` can be classified as an instance of
|
|
1686
|
+
``a:Dog``; that is, this ontology entails the following assertion:
|
|
1687
|
+
|
|
1688
|
+
>>> ClassAssertion("a:Dog", "a:Brian")
|
|
1689
|
+
"""
|
|
1690
|
+
|
|
1691
|
+
property_type: ClassVar[term.URIRef] = RDFS.range
|
|
1692
|
+
|
|
1693
|
+
|
|
1694
|
+
class _UnaryObjectProperty(ObjectPropertyAxiom):
|
|
1695
|
+
"""A grouping class for object property axioms with a single argument."""
|
|
1696
|
+
|
|
1697
|
+
property_type: ClassVar[term.URIRef]
|
|
1698
|
+
object_property_expression: ObjectPropertyExpression
|
|
1699
|
+
|
|
1700
|
+
def __init__(
|
|
1701
|
+
self,
|
|
1702
|
+
object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint,
|
|
1703
|
+
*,
|
|
1704
|
+
annotations: Annotations | None = None,
|
|
1705
|
+
) -> None:
|
|
1706
|
+
"""Initialize a unary object property axiom."""
|
|
1707
|
+
self.object_property_expression = ObjectPropertyExpression.safe(object_property_expression)
|
|
1708
|
+
super().__init__(annotations)
|
|
1709
|
+
|
|
1710
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1711
|
+
"""Represent the unary object property axiom for RDF."""
|
|
1712
|
+
return _add_triple(
|
|
1713
|
+
graph,
|
|
1714
|
+
self.object_property_expression.to_rdflib_node(graph, converter),
|
|
1715
|
+
RDF.type,
|
|
1716
|
+
self.property_type,
|
|
1717
|
+
self.annotations,
|
|
1718
|
+
converter=converter,
|
|
1719
|
+
)
|
|
1720
|
+
|
|
1721
|
+
def _funowl_inside_2(self) -> str:
|
|
1722
|
+
return self.object_property_expression.to_funowl()
|
|
1723
|
+
|
|
1724
|
+
|
|
1725
|
+
class FunctionalObjectProperty(_UnaryObjectProperty): # 9.2.7
|
|
1726
|
+
"""An object property axiom defined in `9.2.7 "Functional Object Properties" <https://www.w3.org/TR/owl2-syntax/#Functional_Object_Properties>`_.
|
|
1727
|
+
|
|
1728
|
+
Consider the ontology consisting of the following axioms.
|
|
1729
|
+
|
|
1730
|
+
>>> FunctionalObjectProperty("a:hasFather") # Each object can have at most one father.
|
|
1731
|
+
>>> ObjectPropertyAssertion("a:hasFather", "a:Stewie", "a:Peter") # Peter is Stewie's father.
|
|
1732
|
+
>>> ObjectPropertyAssertion(
|
|
1733
|
+
... "a:hasFather", "a:Stewie", "a:Peter_Griffin"
|
|
1734
|
+
... ) # Peter Griffin is Stewie's father.
|
|
1735
|
+
|
|
1736
|
+
By the first axiom, ``a:hasFather`` can point from a:Stewie to
|
|
1737
|
+
at most one distinct individual, so ``a:Peter`` and ``a:Peter_Griffin``
|
|
1738
|
+
must be equal; that is, this ontology entails the following assertion:
|
|
1739
|
+
|
|
1740
|
+
>>> SameIndividual(["a:Peter", "a:Peter_Griffin"])
|
|
1741
|
+
|
|
1742
|
+
One might expect the previous ontology to be inconsistent, since
|
|
1743
|
+
the a:hasFather property points to two different values for
|
|
1744
|
+
``a:Stewie``. OWL 2, however, does not make the unique name assumption,
|
|
1745
|
+
so ``a:Peter`` and ``a:Peter_Griffin`` are not necessarily distinct individuals.
|
|
1746
|
+
If the ontology were extended with the following assertion, then it
|
|
1747
|
+
would indeed become inconsistent:
|
|
1748
|
+
|
|
1749
|
+
>>> DifferentIndividuals(["a:Peter", "a:Peter_Griffin"])
|
|
1750
|
+
"""
|
|
1751
|
+
|
|
1752
|
+
property_type = OWL.FunctionalProperty
|
|
1753
|
+
|
|
1754
|
+
|
|
1755
|
+
class InverseFunctionalObjectProperty(_UnaryObjectProperty):
|
|
1756
|
+
"""An object property axiom defined in `9.2.8 "Inverse-Functional Object Properties" <https://www.w3.org/TR/owl2-syntax/#Inverse-Functional_Object_Properties>`_."""
|
|
1757
|
+
|
|
1758
|
+
property_type: ClassVar[term.URIRef] = OWL.InverseFunctionalProperty
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
class ReflexiveObjectProperty(_UnaryObjectProperty):
|
|
1762
|
+
"""An object property axiom defined in `9.2.9 "Irreflexive Object Properties" <https://www.w3.org/TR/owl2-syntax/#Reflexive_Object_Properties>`_."""
|
|
1763
|
+
|
|
1764
|
+
property_type: ClassVar[term.URIRef] = OWL.ReflexiveProperty
|
|
1765
|
+
|
|
1766
|
+
|
|
1767
|
+
class IrreflexiveObjectProperty(_UnaryObjectProperty):
|
|
1768
|
+
"""An object property axiom defined in `9.2.10 "Reflexive Object Properties" <https://www.w3.org/TR/owl2-syntax/#Ireflexive_Object_Properties>`_."""
|
|
1769
|
+
|
|
1770
|
+
property_type: ClassVar[term.URIRef] = OWL.IrreflexiveProperty
|
|
1771
|
+
|
|
1772
|
+
|
|
1773
|
+
class SymmetricObjectProperty(_UnaryObjectProperty):
|
|
1774
|
+
"""An object property axiom defined in `9.2.11 "Symmetric Object Properties" <https://www.w3.org/TR/owl2-syntax/#Symmetric_Object_Properties>`_."""
|
|
1775
|
+
|
|
1776
|
+
property_type: ClassVar[term.URIRef] = OWL.SymmetricProperty
|
|
1777
|
+
|
|
1778
|
+
|
|
1779
|
+
class AsymmetricObjectProperty(_UnaryObjectProperty): # 9.2.12
|
|
1780
|
+
"""An object property axiom defined in `9.2.12 "Asymmetric Object Properties" <https://www.w3.org/TR/owl2-syntax/#Asymmetric_Object_Properties>`_."""
|
|
1781
|
+
|
|
1782
|
+
property_type: ClassVar[term.URIRef] = OWL.AsymmetricProperty
|
|
1783
|
+
|
|
1784
|
+
|
|
1785
|
+
class TransitiveObjectProperty(_UnaryObjectProperty): # 9.2.13
|
|
1786
|
+
"""An object property axiom defined in `9.2.13 "Transitive Object Properties" <https://www.w3.org/TR/owl2-syntax/#Transitive_Object_Properties>`_."""
|
|
1787
|
+
|
|
1788
|
+
property_type: ClassVar[term.URIRef] = OWL.TransitiveProperty
|
|
1789
|
+
|
|
1790
|
+
|
|
1791
|
+
"""9.3: Data Property Axioms"""
|
|
1792
|
+
|
|
1793
|
+
|
|
1794
|
+
class DataPropertyAxiom(Axiom):
|
|
1795
|
+
"""A model for `9.3 "Data Property Axioms" <https://www.w3.org/TR/owl2-syntax/#Data_Property_Axioms>`_."""
|
|
1796
|
+
|
|
1797
|
+
|
|
1798
|
+
class SubDataPropertyOf(DataPropertyAxiom):
|
|
1799
|
+
"""A data property axiom for `9.3.1 "Data Subproperties" <https://www.w3.org/TR/owl2-syntax/#Data_Subproperties>`_."""
|
|
1800
|
+
|
|
1801
|
+
child: DataPropertyExpression
|
|
1802
|
+
parent: DataPropertyExpression
|
|
1803
|
+
|
|
1804
|
+
def __init__(
|
|
1805
|
+
self,
|
|
1806
|
+
child: DataPropertyExpression | IdentifierBoxOrHint,
|
|
1807
|
+
parent: DataPropertyExpression | IdentifierBoxOrHint,
|
|
1808
|
+
*,
|
|
1809
|
+
annotations: Annotations | None = None,
|
|
1810
|
+
) -> None:
|
|
1811
|
+
"""Instantiate a data subproperties axiom."""
|
|
1812
|
+
self.child = DataPropertyExpression.safe(child)
|
|
1813
|
+
self.parent = DataPropertyExpression.safe(parent)
|
|
1814
|
+
super().__init__(annotations)
|
|
1815
|
+
|
|
1816
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1817
|
+
"""Represent the data subproperties axiom for RDF."""
|
|
1818
|
+
s = self.child.to_rdflib_node(graph, converter)
|
|
1819
|
+
o = self.parent.to_rdflib_node(graph, converter)
|
|
1820
|
+
return _add_triple(graph, s, RDFS.subPropertyOf, o, self.annotations, converter=converter)
|
|
1821
|
+
|
|
1822
|
+
def _funowl_inside_2(self) -> str:
|
|
1823
|
+
return f"{self.child.to_funowl()} {self.parent.to_funowl()}"
|
|
1824
|
+
|
|
1825
|
+
|
|
1826
|
+
class _DataPropertyList(DataPropertyAxiom):
|
|
1827
|
+
"""A model for a data property axiom that takes a list of data property expressions."""
|
|
1828
|
+
|
|
1829
|
+
data_property_expressions: Sequence[DataPropertyExpression]
|
|
1830
|
+
|
|
1831
|
+
def __init__(
|
|
1832
|
+
self,
|
|
1833
|
+
data_property_expressions: Sequence[DataPropertyExpression | IdentifierBoxOrHint],
|
|
1834
|
+
*,
|
|
1835
|
+
annotations: Annotations | None = None,
|
|
1836
|
+
) -> None:
|
|
1837
|
+
"""Instantiate a data property list axiom."""
|
|
1838
|
+
if len(data_property_expressions) < 2:
|
|
1839
|
+
raise ValueError
|
|
1840
|
+
self.data_property_expressions = [
|
|
1841
|
+
DataPropertyExpression.safe(dpe) for dpe in data_property_expressions
|
|
1842
|
+
]
|
|
1843
|
+
super().__init__(annotations)
|
|
1844
|
+
|
|
1845
|
+
def _funowl_inside_2(self) -> str:
|
|
1846
|
+
return list_to_funowl(self.data_property_expressions)
|
|
1847
|
+
|
|
1848
|
+
|
|
1849
|
+
class EquivalentDataProperties(_DataPropertyList):
|
|
1850
|
+
"""A data property axiom for `9.3.2 "Equivalent Data Properties" <https://www.w3.org/TR/owl2-syntax/#Equivalent_Data_Properties>`_."""
|
|
1851
|
+
|
|
1852
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1853
|
+
"""Represent the equivalent data properties axiom for RDF."""
|
|
1854
|
+
for dpe in self.data_property_expressions:
|
|
1855
|
+
graph.add((dpe.to_rdflib_node(graph, converter), RDF.type, OWL.DatatypeProperty))
|
|
1856
|
+
return _equivalent_xxx(
|
|
1857
|
+
graph,
|
|
1858
|
+
self.data_property_expressions,
|
|
1859
|
+
annotations=self.annotations,
|
|
1860
|
+
converter=converter,
|
|
1861
|
+
)
|
|
1862
|
+
|
|
1863
|
+
|
|
1864
|
+
class DisjointDataProperties(_DataPropertyList):
|
|
1865
|
+
"""A data property axiom for `9.3.3 "Disjoint Data Properties" <https://www.w3.org/TR/owl2-syntax/#Disjoint_Data_Properties>`_."""
|
|
1866
|
+
|
|
1867
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1868
|
+
"""Represent the disjoint data properties axiom for RDF."""
|
|
1869
|
+
for dpe in self.data_property_expressions:
|
|
1870
|
+
graph.add((dpe.to_rdflib_node(graph, converter), RDF.type, OWL.DatatypeProperty))
|
|
1871
|
+
return _disjoint_xxx(
|
|
1872
|
+
graph, self.data_property_expressions, annotations=self.annotations, converter=converter
|
|
1873
|
+
)
|
|
1874
|
+
|
|
1875
|
+
|
|
1876
|
+
class _DataPropertyTyping(DataPropertyAxiom): # 9.2.4
|
|
1877
|
+
"""An axiom that represents the range or domain of a data property."""
|
|
1878
|
+
|
|
1879
|
+
property_type: ClassVar[term.URIRef]
|
|
1880
|
+
data_property_expression: DataPropertyExpression
|
|
1881
|
+
target: Box
|
|
1882
|
+
|
|
1883
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1884
|
+
"""Represent the equivalent data properties axiom for RDF."""
|
|
1885
|
+
s = self.data_property_expression.to_rdflib_node(graph, converter)
|
|
1886
|
+
graph.add((s, RDF.type, OWL.DatatypeProperty))
|
|
1887
|
+
o = self.target.to_rdflib_node(graph, converter)
|
|
1888
|
+
return _add_triple(graph, s, self.property_type, o, self.annotations, converter=converter)
|
|
1889
|
+
|
|
1890
|
+
def _funowl_inside_2(self) -> str:
|
|
1891
|
+
return f"{self.data_property_expression.to_funowl()} {self.target.to_funowl()}"
|
|
1892
|
+
|
|
1893
|
+
|
|
1894
|
+
class DataPropertyDomain(_DataPropertyTyping): # 9.3.4
|
|
1895
|
+
"""A data property axiom for `9.3.4 "Data Property Domain" <https://www.w3.org/TR/owl2-syntax/#Data_Property_Domain>`_."""
|
|
1896
|
+
|
|
1897
|
+
property_type: ClassVar[term.URIRef] = RDFS.domain
|
|
1898
|
+
|
|
1899
|
+
def __init__(
|
|
1900
|
+
self,
|
|
1901
|
+
data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
|
|
1902
|
+
class_expression: ClassExpression | IdentifierBoxOrHint,
|
|
1903
|
+
*,
|
|
1904
|
+
annotations: Annotations | None = None,
|
|
1905
|
+
) -> None:
|
|
1906
|
+
"""Instantiate a data property domain axiom."""
|
|
1907
|
+
self.data_property_expression = DataPropertyExpression.safe(data_property_expression)
|
|
1908
|
+
self.target = ClassExpression.safe(class_expression)
|
|
1909
|
+
super().__init__(annotations)
|
|
1910
|
+
|
|
1911
|
+
|
|
1912
|
+
class DataPropertyRange(_DataPropertyTyping):
|
|
1913
|
+
"""A data property axiom for `9.3.5 "Data Property Range" <https://www.w3.org/TR/owl2-syntax/#Data_Property_Range>`_."""
|
|
1914
|
+
|
|
1915
|
+
property_type: ClassVar[term.URIRef] = RDFS.range
|
|
1916
|
+
|
|
1917
|
+
def __init__(
|
|
1918
|
+
self,
|
|
1919
|
+
data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
|
|
1920
|
+
data_range: DataRange | IdentifierBoxOrHint,
|
|
1921
|
+
*,
|
|
1922
|
+
annotations: Annotations | None = None,
|
|
1923
|
+
) -> None:
|
|
1924
|
+
"""Instantiate a data property range axiom."""
|
|
1925
|
+
self.data_property_expression = DataPropertyExpression.safe(data_property_expression)
|
|
1926
|
+
self.target = DataRange.safe(data_range)
|
|
1927
|
+
super().__init__(annotations)
|
|
1928
|
+
|
|
1929
|
+
|
|
1930
|
+
class FunctionalDataProperty(DataPropertyAxiom):
|
|
1931
|
+
"""A data property axiom for `9.3.6 "Functional Data Properties" <https://www.w3.org/TR/owl2-syntax/#Functional_Data_Properties>`_.
|
|
1932
|
+
|
|
1933
|
+
Consider the ontology consisting of the following axioms.
|
|
1934
|
+
|
|
1935
|
+
>>> FunctionalDataProperty("a:hasAge") # Each object can have at most one age.
|
|
1936
|
+
>>> DataPropertyAssertion("a:hasAge", "a:Meg", 17) # Meg is seventeen years old.
|
|
1937
|
+
|
|
1938
|
+
By the first axiom, ``a:hasAge`` can point from ``a:Meg`` to at most one
|
|
1939
|
+
distinct literal. In this example ontology, this axiom is satisfied. If,
|
|
1940
|
+
however, the ontology were extended with the following assertion, the
|
|
1941
|
+
semantics of functionality axioms would imply that ``"15"^^xsd:integer`` is
|
|
1942
|
+
equal to ``"17"^^xsd:integer``, which is a contradiction and the ontology
|
|
1943
|
+
would become inconsistent:
|
|
1944
|
+
|
|
1945
|
+
>>> DataPropertyAssertion("a:hasAge", "a:Meg", 15)
|
|
1946
|
+
"""
|
|
1947
|
+
|
|
1948
|
+
data_property_expression: DataPropertyExpression
|
|
1949
|
+
|
|
1950
|
+
def __init__(
|
|
1951
|
+
self,
|
|
1952
|
+
data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
|
|
1953
|
+
*,
|
|
1954
|
+
annotations: Annotations | None = None,
|
|
1955
|
+
):
|
|
1956
|
+
"""Instantiate a functional data property axiom."""
|
|
1957
|
+
self.data_property_expression = DataPropertyExpression.safe(data_property_expression)
|
|
1958
|
+
super().__init__(annotations)
|
|
1959
|
+
|
|
1960
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1961
|
+
"""Represent the functional data property for RDF."""
|
|
1962
|
+
return _add_triple(
|
|
1963
|
+
graph,
|
|
1964
|
+
self.data_property_expression.to_rdflib_node(graph, converter),
|
|
1965
|
+
RDF.type,
|
|
1966
|
+
OWL.FunctionalProperty,
|
|
1967
|
+
self.annotations,
|
|
1968
|
+
converter=converter,
|
|
1969
|
+
)
|
|
1970
|
+
|
|
1971
|
+
def _funowl_inside_2(self) -> str:
|
|
1972
|
+
return self.data_property_expression.to_funowl()
|
|
1973
|
+
|
|
1974
|
+
|
|
1975
|
+
"""Section 9.4: Datatype Definitions"""
|
|
1976
|
+
|
|
1977
|
+
|
|
1978
|
+
class DatatypeDefinition(Axiom):
|
|
1979
|
+
"""A model for `9.4 "Datatype Definitions" <https://www.w3.org/TR/owl2-syntax/#Datatype_Definitions>`_."""
|
|
1980
|
+
|
|
1981
|
+
datatype: IdentifierBox
|
|
1982
|
+
data_range: DataRange
|
|
1983
|
+
|
|
1984
|
+
def __init__(
|
|
1985
|
+
self,
|
|
1986
|
+
datatype: IdentifierBoxOrHint,
|
|
1987
|
+
data_range: DataRange | IdentifierBoxOrHint,
|
|
1988
|
+
*,
|
|
1989
|
+
annotations: Annotations | None = None,
|
|
1990
|
+
) -> None:
|
|
1991
|
+
"""Instantiate a datatype definition axiom."""
|
|
1992
|
+
self.datatype = IdentifierBox(datatype)
|
|
1993
|
+
self.data_range = DataRange.safe(data_range)
|
|
1994
|
+
super().__init__(annotations)
|
|
1995
|
+
|
|
1996
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
1997
|
+
"""Represent the datatype definition axiom for RDF."""
|
|
1998
|
+
s = self.datatype.to_rdflib_node(graph, converter)
|
|
1999
|
+
graph.add((s, RDF.type, RDFS.Datatype))
|
|
2000
|
+
return _add_triple(
|
|
2001
|
+
graph,
|
|
2002
|
+
s,
|
|
2003
|
+
OWL.equivalentClass,
|
|
2004
|
+
self.data_range.to_rdflib_node(graph, converter),
|
|
2005
|
+
annotations=self.annotations,
|
|
2006
|
+
converter=converter,
|
|
2007
|
+
)
|
|
2008
|
+
|
|
2009
|
+
def _funowl_inside_2(self) -> str:
|
|
2010
|
+
return f"{self.datatype.to_funowl()} {self.data_range.to_funowl()}"
|
|
2011
|
+
|
|
2012
|
+
|
|
2013
|
+
"""Section 9.5: Keys"""
|
|
2014
|
+
|
|
2015
|
+
|
|
2016
|
+
class HasKey(Axiom):
|
|
2017
|
+
"""An axiom for `9.5 "Keys" <https://www.w3.org/TR/owl2-syntax/#Keys>`_."""
|
|
2018
|
+
|
|
2019
|
+
class_expression: ClassExpression
|
|
2020
|
+
object_property_expressions: list[ObjectPropertyExpression]
|
|
2021
|
+
data_property_expressions: list[DataPropertyExpression]
|
|
2022
|
+
|
|
2023
|
+
def __init__(
|
|
2024
|
+
self,
|
|
2025
|
+
class_expression: ClassExpression | IdentifierBoxOrHint,
|
|
2026
|
+
object_property_expressions: Sequence[ObjectPropertyExpression | IdentifierBoxOrHint],
|
|
2027
|
+
data_property_expressions: Sequence[DataPropertyExpression | IdentifierBoxOrHint],
|
|
2028
|
+
*,
|
|
2029
|
+
annotations: Annotations | None = None,
|
|
2030
|
+
) -> None:
|
|
2031
|
+
"""Instantiate a "has key" axiom."""
|
|
2032
|
+
self.class_expression = ClassExpression.safe(class_expression)
|
|
2033
|
+
self.object_property_expressions = [
|
|
2034
|
+
ObjectPropertyExpression.safe(ope) for ope in object_property_expressions
|
|
2035
|
+
]
|
|
2036
|
+
self.data_property_expressions = [
|
|
2037
|
+
DataPropertyExpression.safe(dpe) for dpe in data_property_expressions
|
|
2038
|
+
]
|
|
2039
|
+
super().__init__(annotations)
|
|
2040
|
+
|
|
2041
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2042
|
+
"""Represent the "has key" axiom for RDF."""
|
|
2043
|
+
object_and_data_property_expressions: list[
|
|
2044
|
+
ObjectPropertyExpression | DataPropertyExpression
|
|
2045
|
+
] = []
|
|
2046
|
+
object_and_data_property_expressions.extend(self.object_property_expressions)
|
|
2047
|
+
object_and_data_property_expressions.extend(self.data_property_expressions)
|
|
2048
|
+
|
|
2049
|
+
return _add_triple(
|
|
2050
|
+
graph,
|
|
2051
|
+
self.class_expression.to_rdflib_node(graph, converter),
|
|
2052
|
+
OWL.hasKey,
|
|
2053
|
+
_make_sequence(graph, object_and_data_property_expressions, converter),
|
|
2054
|
+
annotations=self.annotations,
|
|
2055
|
+
converter=converter,
|
|
2056
|
+
)
|
|
2057
|
+
|
|
2058
|
+
def _funowl_inside_2(self) -> str:
|
|
2059
|
+
aa = f"{self.class_expression.to_funowl()}"
|
|
2060
|
+
if self.object_property_expressions:
|
|
2061
|
+
aa += f" ( {list_to_funowl(self.object_property_expressions)} )"
|
|
2062
|
+
else:
|
|
2063
|
+
aa += " ()"
|
|
2064
|
+
if self.data_property_expressions:
|
|
2065
|
+
aa += f" ( {list_to_funowl(self.data_property_expressions)} )"
|
|
2066
|
+
else:
|
|
2067
|
+
aa += " ()"
|
|
2068
|
+
return aa
|
|
2069
|
+
|
|
2070
|
+
|
|
2071
|
+
"""Section 9.6: Assertions"""
|
|
2072
|
+
|
|
2073
|
+
|
|
2074
|
+
class Assertion(Axiom):
|
|
2075
|
+
"""Axioms for `9.6 "Assertions" <https://www.w3.org/TR/owl2-syntax/#Assertions>`_."""
|
|
2076
|
+
|
|
2077
|
+
|
|
2078
|
+
class _IndividualListAssertion(Assertion):
|
|
2079
|
+
"""A grouping class for individual equality and inequality axioms."""
|
|
2080
|
+
|
|
2081
|
+
individuals: Sequence[IdentifierBox]
|
|
2082
|
+
|
|
2083
|
+
def __init__(
|
|
2084
|
+
self,
|
|
2085
|
+
individuals: Sequence[IdentifierBoxOrHint],
|
|
2086
|
+
*,
|
|
2087
|
+
annotations: Annotations | None = None,
|
|
2088
|
+
) -> None:
|
|
2089
|
+
"""Instantiate an individual list axiom."""
|
|
2090
|
+
self.individuals = [IdentifierBox(i) for i in individuals]
|
|
2091
|
+
super().__init__(annotations)
|
|
2092
|
+
|
|
2093
|
+
def _funowl_inside_2(self) -> str:
|
|
2094
|
+
return list_to_funowl(self.individuals)
|
|
2095
|
+
|
|
2096
|
+
|
|
2097
|
+
class SameIndividual(_IndividualListAssertion):
|
|
2098
|
+
"""An axiom for `9.6.1 "Individual Equality" <https://www.w3.org/TR/owl2-syntax/#Individual_Equality>`_."""
|
|
2099
|
+
|
|
2100
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2101
|
+
"""Represent the individual equality axiom for RDF."""
|
|
2102
|
+
nodes = [i.to_rdflib_node(graph, converter) for i in self.individuals]
|
|
2103
|
+
for node in nodes:
|
|
2104
|
+
graph.add((node, RDF.type, OWL.NamedIndividual))
|
|
2105
|
+
for s, o in itt.combinations(nodes, 2):
|
|
2106
|
+
_add_triple(graph, s, OWL.sameAs, o, annotations=self.annotations, converter=converter)
|
|
2107
|
+
# TODO connect this node to triples?
|
|
2108
|
+
return term.BNode()
|
|
2109
|
+
|
|
2110
|
+
|
|
2111
|
+
class DifferentIndividuals(_IndividualListAssertion):
|
|
2112
|
+
"""An axiom for `9.6.2 "Individual Inequality" <https://www.w3.org/TR/owl2-syntax/#Individual_Inequality>`_."""
|
|
2113
|
+
|
|
2114
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2115
|
+
"""Represent the individual inequality axiom for RDF."""
|
|
2116
|
+
nodes = [i.to_rdflib_node(graph, converter) for i in self.individuals]
|
|
2117
|
+
for node in nodes:
|
|
2118
|
+
graph.add((node, RDF.type, OWL.NamedIndividual))
|
|
2119
|
+
if len(nodes) == 2:
|
|
2120
|
+
return _add_triple(
|
|
2121
|
+
graph,
|
|
2122
|
+
nodes[0],
|
|
2123
|
+
OWL.differentFrom,
|
|
2124
|
+
nodes[1],
|
|
2125
|
+
annotations=self.annotations,
|
|
2126
|
+
converter=converter,
|
|
2127
|
+
)
|
|
2128
|
+
else:
|
|
2129
|
+
node = term.BNode()
|
|
2130
|
+
graph.add((node, RDF.type, OWL.AllDifferent))
|
|
2131
|
+
graph.add((node, OWL.distinctMembers, _make_sequence_nodes(graph, nodes)))
|
|
2132
|
+
# FIXME add annotations
|
|
2133
|
+
return node
|
|
2134
|
+
|
|
2135
|
+
|
|
2136
|
+
class ClassAssertion(Assertion):
|
|
2137
|
+
"""An axiom for `9.6.3 "Class Assertions" <https://www.w3.org/TR/owl2-syntax/#Class_Assertions>`_."""
|
|
2138
|
+
|
|
2139
|
+
class_expression: ClassExpression
|
|
2140
|
+
individual: IdentifierBox
|
|
2141
|
+
|
|
2142
|
+
def __init__(
|
|
2143
|
+
self,
|
|
2144
|
+
class_expression: ClassExpression | IdentifierBoxOrHint,
|
|
2145
|
+
individual: IdentifierBoxOrHint,
|
|
2146
|
+
*,
|
|
2147
|
+
annotations: Annotations | None = None,
|
|
2148
|
+
) -> None:
|
|
2149
|
+
"""Instantiate a class assertion axiom."""
|
|
2150
|
+
self.class_expression = ClassExpression.safe(class_expression)
|
|
2151
|
+
self.individual = IdentifierBox(individual)
|
|
2152
|
+
super().__init__(annotations)
|
|
2153
|
+
|
|
2154
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2155
|
+
"""Represent the class assertion axiom for RDF."""
|
|
2156
|
+
s = self.individual.to_rdflib_node(graph, converter)
|
|
2157
|
+
graph.add((s, RDF.type, OWL.NamedIndividual))
|
|
2158
|
+
return _add_triple(
|
|
2159
|
+
graph,
|
|
2160
|
+
s,
|
|
2161
|
+
RDF.type,
|
|
2162
|
+
self.class_expression.to_rdflib_node(graph, converter),
|
|
2163
|
+
annotations=self.annotations,
|
|
2164
|
+
converter=converter,
|
|
2165
|
+
)
|
|
2166
|
+
|
|
2167
|
+
def _funowl_inside_2(self) -> str:
|
|
2168
|
+
return f"{self.class_expression.to_funowl()} {self.individual.to_funowl()}"
|
|
2169
|
+
|
|
2170
|
+
|
|
2171
|
+
class _BaseObjectPropertyAssertion(Assertion):
|
|
2172
|
+
"""A grouping class for positive and negative object property assertion axioms."""
|
|
2173
|
+
|
|
2174
|
+
object_property_expression: ObjectPropertyExpression
|
|
2175
|
+
source_individual: IdentifierBox
|
|
2176
|
+
target_individual: IdentifierBox
|
|
2177
|
+
|
|
2178
|
+
def __init__(
|
|
2179
|
+
self,
|
|
2180
|
+
object_property_expression: ObjectPropertyExpression | IdentifierBoxOrHint,
|
|
2181
|
+
source_individual: IdentifierBoxOrHint,
|
|
2182
|
+
target_individual: IdentifierBoxOrHint,
|
|
2183
|
+
*,
|
|
2184
|
+
annotations: Annotations | None = None,
|
|
2185
|
+
) -> None:
|
|
2186
|
+
"""Initialize an object property assertion axiom."""
|
|
2187
|
+
self.object_property_expression = ObjectPropertyExpression.safe(object_property_expression)
|
|
2188
|
+
self.source_individual = IdentifierBox(source_individual)
|
|
2189
|
+
self.target_individual = IdentifierBox(target_individual)
|
|
2190
|
+
super().__init__(annotations)
|
|
2191
|
+
|
|
2192
|
+
def _funowl_inside_2(self) -> str:
|
|
2193
|
+
return f"{self.object_property_expression.to_funowl()} {self.source_individual.to_funowl()} {self.target_individual.to_funowl()}"
|
|
2194
|
+
|
|
2195
|
+
|
|
2196
|
+
class ObjectPropertyAssertion(_BaseObjectPropertyAssertion):
|
|
2197
|
+
"""An axiom for `9.6.4 "Positive Object Property Assertions" <https://www.w3.org/TR/owl2-syntax/#Positive_Object_Property_Assertions>`_."""
|
|
2198
|
+
|
|
2199
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2200
|
+
"""Represent the positive object property assertion axiom for RDF."""
|
|
2201
|
+
s = self.source_individual.to_rdflib_node(graph, converter)
|
|
2202
|
+
o = self.target_individual.to_rdflib_node(graph, converter)
|
|
2203
|
+
graph.add((s, RDF.type, OWL.NamedIndividual))
|
|
2204
|
+
graph.add((o, RDF.type, OWL.NamedIndividual))
|
|
2205
|
+
if isinstance(self.object_property_expression, ObjectInverseOf):
|
|
2206
|
+
# flip them around
|
|
2207
|
+
s, o = o, s
|
|
2208
|
+
# unpack the inverse property
|
|
2209
|
+
p = self.object_property_expression.object_property.to_rdflib_node(graph, converter)
|
|
2210
|
+
# make sure the inverse property is declared
|
|
2211
|
+
graph.add((p, RDF.type, OWL.ObjectProperty))
|
|
2212
|
+
else:
|
|
2213
|
+
p = self.object_property_expression.to_rdflib_node(graph, converter)
|
|
2214
|
+
|
|
2215
|
+
return _add_triple(graph, s, p, o, annotations=self.annotations, converter=converter)
|
|
2216
|
+
|
|
2217
|
+
|
|
2218
|
+
class NegativeObjectPropertyAssertion(_BaseObjectPropertyAssertion):
|
|
2219
|
+
"""An axiom for `9.6.5 "Negative Object Property Assertions" <https://www.w3.org/TR/owl2-syntax/#Negative_Object_Property_Assertions>`_."""
|
|
2220
|
+
|
|
2221
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2222
|
+
"""Represent the negative object property assertion axiom for RDF."""
|
|
2223
|
+
s = self.source_individual.to_rdflib_node(graph, converter)
|
|
2224
|
+
o = self.target_individual.to_rdflib_node(graph, converter)
|
|
2225
|
+
graph.add((s, RDF.type, OWL.NamedIndividual))
|
|
2226
|
+
graph.add((o, RDF.type, OWL.NamedIndividual))
|
|
2227
|
+
if isinstance(self.object_property_expression, ObjectInverseOf):
|
|
2228
|
+
# TODO OWLAPI is not consistent with the way ObjectPropertyAssertion and
|
|
2229
|
+
# NegativeObjectPropertyAssertion work. For some reason, it doesn't
|
|
2230
|
+
# flip the subject and object / unpack the inverse OPE for
|
|
2231
|
+
# NegativeObjectPropertyAssertion. Therefore, it's implemented here
|
|
2232
|
+
# to reflect that. Need to make an issue on OWLAPI about this
|
|
2233
|
+
self.object_property_expression.declare_wrapped_ope(graph, converter)
|
|
2234
|
+
return _add_triple_annotations(
|
|
2235
|
+
graph,
|
|
2236
|
+
s,
|
|
2237
|
+
self.object_property_expression.to_rdflib_node(graph, converter),
|
|
2238
|
+
o,
|
|
2239
|
+
annotations=self.annotations,
|
|
2240
|
+
type=OWL.NegativePropertyAssertion,
|
|
2241
|
+
converter=converter,
|
|
2242
|
+
force_for_negative_assertion=True,
|
|
2243
|
+
reified_p=OWL.assertionProperty,
|
|
2244
|
+
reified_s=OWL.sourceIndividual,
|
|
2245
|
+
reified_o=OWL.targetIndividual,
|
|
2246
|
+
)
|
|
2247
|
+
|
|
2248
|
+
|
|
2249
|
+
class _BaseDataPropertyAssertion(Assertion):
|
|
2250
|
+
"""A grouping class for positive and negative data property assertion axioms."""
|
|
2251
|
+
|
|
2252
|
+
source_individual: IdentifierBox
|
|
2253
|
+
target: PrimitiveBox
|
|
2254
|
+
|
|
2255
|
+
def __init__(
|
|
2256
|
+
self,
|
|
2257
|
+
data_property_expression: DataPropertyExpression | IdentifierBoxOrHint,
|
|
2258
|
+
source_individual: IdentifierBoxOrHint,
|
|
2259
|
+
target: PrimitiveHint,
|
|
2260
|
+
*,
|
|
2261
|
+
annotations: Annotations | None = None,
|
|
2262
|
+
) -> None:
|
|
2263
|
+
"""Initialize a data property assertion axiom."""
|
|
2264
|
+
self.data_property_expression = DataPropertyExpression.safe(data_property_expression)
|
|
2265
|
+
self.source_individual = IdentifierBox(source_individual)
|
|
2266
|
+
self.target = _safe_primitive_box(target)
|
|
2267
|
+
super().__init__(annotations)
|
|
2268
|
+
|
|
2269
|
+
def _funowl_inside_2(self) -> str:
|
|
2270
|
+
return f"{self.data_property_expression.to_funowl()} {self.source_individual.to_funowl()} {self.target.to_funowl()}"
|
|
2271
|
+
|
|
2272
|
+
|
|
2273
|
+
class DataPropertyAssertion(_BaseDataPropertyAssertion):
|
|
2274
|
+
"""An axiom for `9.6.6 "Positive Data Property Assertions" <https://www.w3.org/TR/owl2-syntax/#Positive_Data_Property_Assertions>`_.
|
|
2275
|
+
|
|
2276
|
+
>>> DataPropertyAssertion("a:hasAge", "a:Meg", 17)
|
|
2277
|
+
"""
|
|
2278
|
+
|
|
2279
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2280
|
+
"""Represent the positive data property assertion axiom for RDF."""
|
|
2281
|
+
s = self.source_individual.to_rdflib_node(graph, converter)
|
|
2282
|
+
graph.add((s, RDF.type, OWL.NamedIndividual))
|
|
2283
|
+
return _add_triple(
|
|
2284
|
+
graph,
|
|
2285
|
+
s,
|
|
2286
|
+
self.data_property_expression.to_rdflib_node(graph, converter),
|
|
2287
|
+
self.target.to_rdflib_node(graph, converter),
|
|
2288
|
+
annotations=self.annotations,
|
|
2289
|
+
converter=converter,
|
|
2290
|
+
)
|
|
2291
|
+
|
|
2292
|
+
|
|
2293
|
+
class NegativeDataPropertyAssertion(_BaseDataPropertyAssertion):
|
|
2294
|
+
"""An axiom for `9.6.7 "Negative Data Property Assertions" <https://www.w3.org/TR/owl2-syntax/#Negative_Data_Property_Assertions>`_.
|
|
2295
|
+
|
|
2296
|
+
>>> NegativeDataPropertyAssertion("a:hasAge", "a:Meg", 5)
|
|
2297
|
+
"""
|
|
2298
|
+
|
|
2299
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2300
|
+
"""Represent the negative data property assertion axiom for RDF."""
|
|
2301
|
+
s = self.source_individual.to_rdflib_node(graph, converter)
|
|
2302
|
+
graph.add((s, RDF.type, OWL.NamedIndividual))
|
|
2303
|
+
return _add_triple_annotations(
|
|
2304
|
+
graph,
|
|
2305
|
+
s,
|
|
2306
|
+
self.data_property_expression.to_rdflib_node(graph, converter),
|
|
2307
|
+
self.target.to_rdflib_node(graph, converter),
|
|
2308
|
+
annotations=self.annotations,
|
|
2309
|
+
type=OWL.NegativePropertyAssertion,
|
|
2310
|
+
converter=converter,
|
|
2311
|
+
force_for_negative_assertion=True,
|
|
2312
|
+
reified_p=OWL.assertionProperty,
|
|
2313
|
+
reified_s=OWL.sourceIndividual,
|
|
2314
|
+
reified_o=OWL.targetValue,
|
|
2315
|
+
)
|
|
2316
|
+
|
|
2317
|
+
|
|
2318
|
+
"""Section 10: Annotations"""
|
|
2319
|
+
|
|
2320
|
+
|
|
2321
|
+
class Annotation(Box): # 10.1
|
|
2322
|
+
"""An element defined in `10.1 "Annotations of Ontologies, Axioms, and other Annotations" <https://www.w3.org/TR/owl2-syntax/#Annotations_of_Ontologies.2C_Axioms.2C_and_other_Annotations>`_.
|
|
2323
|
+
|
|
2324
|
+
.. image:: https://www.w3.org/TR/owl2-syntax/Annotations.gif
|
|
2325
|
+
|
|
2326
|
+
Annotations can be used to add additional context, like curation provenance, to
|
|
2327
|
+
assertions
|
|
2328
|
+
|
|
2329
|
+
>>> AnnotationAssertion(
|
|
2330
|
+
... "skos:exactMatch",
|
|
2331
|
+
... "agrovoc:0619dd9e",
|
|
2332
|
+
... "agro:00000137",
|
|
2333
|
+
... annotations=[
|
|
2334
|
+
... Annotation("dcterms:contributor", "orcid:0000-0003-4423-4370"),
|
|
2335
|
+
... Annotation("sssom:mapping_justification", "semapv:ManualMappingCuration"),
|
|
2336
|
+
... ],
|
|
2337
|
+
... )
|
|
2338
|
+
|
|
2339
|
+
Annotations can even be used on themselves, adding arbitrary levels of detail.
|
|
2340
|
+
In the following example, we annotate the affiliation of the contributor
|
|
2341
|
+
via the `wd:P1416 (affiliation) <https://www.wikidata.org/wiki/Property:P1416>`_
|
|
2342
|
+
predicate.
|
|
2343
|
+
|
|
2344
|
+
>>> AnnotationAssertion(
|
|
2345
|
+
... "skos:exactMatch",
|
|
2346
|
+
... "agrovoc:0619dd9e",
|
|
2347
|
+
... "agro:00000137",
|
|
2348
|
+
... annotations=[
|
|
2349
|
+
... Annotation(
|
|
2350
|
+
... "dcterms:contributor",
|
|
2351
|
+
... "orcid:0000-0003-4423-4370",
|
|
2352
|
+
... annotations=[
|
|
2353
|
+
... Annotation("wd:P1416", "wd:Q126066280"),
|
|
2354
|
+
... ],
|
|
2355
|
+
... ),
|
|
2356
|
+
... Annotation("sssom:mapping_justification", "semapv:ManualMappingCuration"),
|
|
2357
|
+
... ],
|
|
2358
|
+
... )
|
|
2359
|
+
"""
|
|
2360
|
+
|
|
2361
|
+
annotation_property: IdentifierBox
|
|
2362
|
+
value: PrimitiveBox
|
|
2363
|
+
annotations: list[Annotation]
|
|
2364
|
+
|
|
2365
|
+
def __init__(
|
|
2366
|
+
self,
|
|
2367
|
+
annotation_property: IdentifierBoxOrHint,
|
|
2368
|
+
value: PrimitiveHint,
|
|
2369
|
+
*,
|
|
2370
|
+
annotations: list[Annotation] | None = None,
|
|
2371
|
+
) -> None:
|
|
2372
|
+
"""Initialize an annotation."""
|
|
2373
|
+
self.annotation_property = IdentifierBox(annotation_property)
|
|
2374
|
+
self.value = _safe_primitive_box(value)
|
|
2375
|
+
self.annotations = annotations or []
|
|
2376
|
+
|
|
2377
|
+
def to_rdflib_node(
|
|
2378
|
+
self, graph: Graph, converter: Converter
|
|
2379
|
+
) -> term.IdentifiedNode: # pragma: no cover
|
|
2380
|
+
"""Represent the annotation as an RDF node (unused)."""
|
|
2381
|
+
raise RuntimeError
|
|
2382
|
+
|
|
2383
|
+
def _add_to_triple(
|
|
2384
|
+
self,
|
|
2385
|
+
graph: Graph,
|
|
2386
|
+
subject: term.IdentifiedNode,
|
|
2387
|
+
converter: Converter,
|
|
2388
|
+
) -> None:
|
|
2389
|
+
annotation_property = self.annotation_property.to_rdflib_node(graph, converter)
|
|
2390
|
+
annotation_object = self.value.to_rdflib_node(graph, converter)
|
|
2391
|
+
graph.add((annotation_property, RDF.type, OWL.AnnotationProperty))
|
|
2392
|
+
graph.add((subject, annotation_property, annotation_object))
|
|
2393
|
+
if self.annotations:
|
|
2394
|
+
_add_triple_annotations(
|
|
2395
|
+
graph,
|
|
2396
|
+
subject,
|
|
2397
|
+
annotation_property,
|
|
2398
|
+
annotation_object,
|
|
2399
|
+
converter=converter,
|
|
2400
|
+
annotations=self.annotations,
|
|
2401
|
+
type=OWL.Annotation,
|
|
2402
|
+
)
|
|
2403
|
+
|
|
2404
|
+
def to_funowl_args(self) -> str:
|
|
2405
|
+
"""Get the inside of the functional OWL tag representing the annotation."""
|
|
2406
|
+
end = f"{self.annotation_property.to_funowl()} {self.value.to_funowl()}"
|
|
2407
|
+
if self.annotations:
|
|
2408
|
+
return list_to_funowl(self.annotations) + " " + end
|
|
2409
|
+
return end
|
|
2410
|
+
|
|
2411
|
+
|
|
2412
|
+
Annotations: TypeAlias = list[Annotation]
|
|
2413
|
+
|
|
2414
|
+
|
|
2415
|
+
class AnnotationAxiom(Axiom): # 10.2
|
|
2416
|
+
"""A grouping class for annotation axioms defined in `10.2 "Axiom Annotations" <https://www.w3.org/TR/owl2-syntax/#Annotation_Axioms>`_.
|
|
2417
|
+
|
|
2418
|
+
.. image:: https://www.w3.org/TR/owl2-syntax/A_annotation.gif
|
|
2419
|
+
"""
|
|
2420
|
+
|
|
2421
|
+
|
|
2422
|
+
class AnnotationProperty(IdentifierBox):
|
|
2423
|
+
"""A wrapper around an identifier box with custom functionality."""
|
|
2424
|
+
|
|
2425
|
+
#: A set of built-in annotation properties that shouldn't be re-defined, since they
|
|
2426
|
+
#: appear in Table 3 of https://www.w3.org/TR/owl2-syntax/#IRIs.
|
|
2427
|
+
_SKIP: ClassVar[set[term.Node]] = {
|
|
2428
|
+
OWL.backwardCompatibleWith,
|
|
2429
|
+
OWL.deprecated,
|
|
2430
|
+
OWL.incompatibleWith,
|
|
2431
|
+
OWL.priorVersion,
|
|
2432
|
+
OWL.versionInfo,
|
|
2433
|
+
RDFS.comment,
|
|
2434
|
+
RDFS.isDefinedBy,
|
|
2435
|
+
RDFS.label,
|
|
2436
|
+
RDFS.seeAlso,
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2440
|
+
"""Represent the annotation property for RDF."""
|
|
2441
|
+
node = super().to_rdflib_node(graph, converter)
|
|
2442
|
+
if node in self._SKIP:
|
|
2443
|
+
return node
|
|
2444
|
+
graph.add((node, RDF.type, OWL.AnnotationProperty))
|
|
2445
|
+
return node
|
|
2446
|
+
|
|
2447
|
+
|
|
2448
|
+
class AnnotationAssertion(AnnotationAxiom): # 10.2.1
|
|
2449
|
+
"""An annotation axiom defined in `10.2.1 Annotation Assertion <https://www.w3.org/TR/owl2-syntax/#Annotation_Assertion>`_."""
|
|
2450
|
+
|
|
2451
|
+
annotation_property: AnnotationProperty
|
|
2452
|
+
subject: IdentifierBox
|
|
2453
|
+
value: PrimitiveBox
|
|
2454
|
+
|
|
2455
|
+
def __init__(
|
|
2456
|
+
self,
|
|
2457
|
+
annotation_property: IdentifierBoxOrHint,
|
|
2458
|
+
subject: IdentifierBoxOrHint,
|
|
2459
|
+
value: PrimitiveHint,
|
|
2460
|
+
*,
|
|
2461
|
+
annotations: list[Annotation] | None = None,
|
|
2462
|
+
) -> None:
|
|
2463
|
+
"""Initialize an annotation assertion axiom."""
|
|
2464
|
+
self.annotation_property = AnnotationProperty(annotation_property)
|
|
2465
|
+
self.subject = IdentifierBox(subject)
|
|
2466
|
+
self.value = _safe_primitive_box(value)
|
|
2467
|
+
super().__init__(annotations)
|
|
2468
|
+
|
|
2469
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2470
|
+
"""Represent the annotation assertion axiom for RDF."""
|
|
2471
|
+
return _add_triple(
|
|
2472
|
+
graph,
|
|
2473
|
+
self.subject.to_rdflib_node(graph, converter),
|
|
2474
|
+
self.annotation_property.to_rdflib_node(graph, converter),
|
|
2475
|
+
self.value.to_rdflib_node(graph, converter),
|
|
2476
|
+
annotations=self.annotations,
|
|
2477
|
+
converter=converter,
|
|
2478
|
+
)
|
|
2479
|
+
|
|
2480
|
+
def _funowl_inside_2(self) -> str:
|
|
2481
|
+
return " ".join(
|
|
2482
|
+
(
|
|
2483
|
+
self.annotation_property.to_funowl(),
|
|
2484
|
+
self.subject.to_funowl(),
|
|
2485
|
+
self.value.to_funowl(),
|
|
2486
|
+
)
|
|
2487
|
+
)
|
|
2488
|
+
|
|
2489
|
+
|
|
2490
|
+
class SubAnnotationPropertyOf(AnnotationAxiom): # 10.2.2
|
|
2491
|
+
"""An annotation axiom defined in `10.2.2 Annotation Subproperties <https://www.w3.org/TR/owl2-syntax/#Annotation_Subproperties>`_."""
|
|
2492
|
+
|
|
2493
|
+
child: AnnotationProperty
|
|
2494
|
+
parent: AnnotationProperty
|
|
2495
|
+
|
|
2496
|
+
def __init__(
|
|
2497
|
+
self,
|
|
2498
|
+
child: IdentifierBoxOrHint,
|
|
2499
|
+
parent: IdentifierBoxOrHint,
|
|
2500
|
+
*,
|
|
2501
|
+
annotations: Annotations | None = None,
|
|
2502
|
+
) -> None:
|
|
2503
|
+
"""Initialize an annotation subproperty axiom."""
|
|
2504
|
+
self.child = AnnotationProperty(child)
|
|
2505
|
+
self.parent = AnnotationProperty(parent)
|
|
2506
|
+
super().__init__(annotations)
|
|
2507
|
+
|
|
2508
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2509
|
+
"""Represent the annotation subproperty axiom for RDF."""
|
|
2510
|
+
s = self.child.to_rdflib_node(graph, converter)
|
|
2511
|
+
o = self.parent.to_rdflib_node(graph, converter)
|
|
2512
|
+
return _add_triple(graph, s, RDFS.subPropertyOf, o, self.annotations, converter=converter)
|
|
2513
|
+
|
|
2514
|
+
def _funowl_inside_2(self) -> str:
|
|
2515
|
+
return f"{self.child.to_funowl()} {self.parent.to_funowl()}"
|
|
2516
|
+
|
|
2517
|
+
|
|
2518
|
+
class AnnotationPropertyTypingAxiom(AnnotationAxiom):
|
|
2519
|
+
"""A helper class that defines shared functionality between annotation property domains and ranges."""
|
|
2520
|
+
|
|
2521
|
+
property_type: ClassVar[term.URIRef]
|
|
2522
|
+
annotation_property: AnnotationProperty
|
|
2523
|
+
value: PrimitiveBox
|
|
2524
|
+
|
|
2525
|
+
def __init__(
|
|
2526
|
+
self,
|
|
2527
|
+
annotation_property: IdentifierBoxOrHint,
|
|
2528
|
+
value: PrimitiveHint,
|
|
2529
|
+
*,
|
|
2530
|
+
annotations: Annotations | None = None,
|
|
2531
|
+
) -> None:
|
|
2532
|
+
"""Initialize an annotation property range or domain axiom."""
|
|
2533
|
+
self.annotation_property = AnnotationProperty(annotation_property)
|
|
2534
|
+
self.value = _safe_primitive_box(value)
|
|
2535
|
+
super().__init__(annotations)
|
|
2536
|
+
|
|
2537
|
+
def to_rdflib_node(self, graph: Graph, converter: Converter) -> term.IdentifiedNode:
|
|
2538
|
+
"""Represent the annotation property range or domain axiom for RDF."""
|
|
2539
|
+
s = self.annotation_property.to_rdflib_node(graph, converter)
|
|
2540
|
+
o = self.value.to_rdflib_node(graph, converter)
|
|
2541
|
+
graph.add((s, RDF.type, OWL.AnnotationProperty))
|
|
2542
|
+
return _add_triple(graph, s, self.property_type, o, self.annotations, converter=converter)
|
|
2543
|
+
|
|
2544
|
+
def _funowl_inside_2(self) -> str:
|
|
2545
|
+
return f"{self.annotation_property.to_funowl()} {self.value.to_funowl()}"
|
|
2546
|
+
|
|
2547
|
+
|
|
2548
|
+
class AnnotationPropertyDomain(AnnotationPropertyTypingAxiom): # 10.2.3
|
|
2549
|
+
"""An annotation axiom defined in `10.2.3 Annotation Property Domain <https://www.w3.org/TR/owl2-syntax/#Annotation_Property_Domain>`_."""
|
|
2550
|
+
|
|
2551
|
+
property_type: ClassVar[term.URIRef] = RDFS.domain
|
|
2552
|
+
|
|
2553
|
+
|
|
2554
|
+
class AnnotationPropertyRange(AnnotationPropertyTypingAxiom): # 10.2.4
|
|
2555
|
+
"""An annotation axiom defined in `10.2.4 Annotation Property Range <https://www.w3.org/TR/owl2-syntax/#Annotation_Property_Range>`_.
|
|
2556
|
+
|
|
2557
|
+
For example, the range of all ``rdfs:label`` should be a string.
|
|
2558
|
+
This can be represented as with the functional OWL
|
|
2559
|
+
``AnnotationPropertyRange( rdfs:label xsd:string )``, or in
|
|
2560
|
+
Python like the following:
|
|
2561
|
+
|
|
2562
|
+
Using :mod:`rdflib` namespaces:
|
|
2563
|
+
|
|
2564
|
+
>>> from rdflib import RDFS, XSD
|
|
2565
|
+
>>> AnnotationPropertyRange(RDFS.label, XSD.string)
|
|
2566
|
+
|
|
2567
|
+
Using a string:
|
|
2568
|
+
|
|
2569
|
+
>>> AnnotationPropertyRange("rdfs:label", "xsd:string")
|
|
2570
|
+
"""
|
|
2571
|
+
|
|
2572
|
+
property_type: ClassVar[term.URIRef] = RDFS.range
|