linkml 1.5.8__py3-none-any.whl → 1.6.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.
- linkml/generators/legacy/__init__.py +0 -0
- linkml/generators/owlgen.py +674 -391
- {linkml-1.5.8.dist-info → linkml-1.6.0.dist-info}/METADATA +3 -3
- {linkml-1.5.8.dist-info → linkml-1.6.0.dist-info}/RECORD +7 -6
- {linkml-1.5.8.dist-info → linkml-1.6.0.dist-info}/LICENSE +0 -0
- {linkml-1.5.8.dist-info → linkml-1.6.0.dist-info}/WHEEL +0 -0
- {linkml-1.5.8.dist-info → linkml-1.6.0.dist-info}/entry_points.txt +0 -0
linkml/generators/owlgen.py
CHANGED
@@ -1,39 +1,41 @@
|
|
1
|
-
"""Generate OWL ontology
|
2
|
-
|
3
|
-
model classes are translated to OWL classes, slots to OWL properties.
|
4
|
-
"""
|
1
|
+
"""Generate OWL ontology representation of a LinkML schema."""
|
5
2
|
import logging
|
6
3
|
import os
|
4
|
+
from collections import defaultdict
|
7
5
|
from dataclasses import dataclass, field
|
8
6
|
from enum import Enum, unique
|
9
|
-
from typing import Optional,
|
7
|
+
from typing import List, Mapping, Optional, Set, Tuple, Union
|
10
8
|
|
11
9
|
import click
|
10
|
+
from linkml_runtime import SchemaView
|
12
11
|
from linkml_runtime.linkml_model.meta import (
|
12
|
+
AnonymousClassExpression,
|
13
|
+
AnonymousSlotExpression,
|
14
|
+
AnonymousTypeExpression,
|
13
15
|
ClassDefinition,
|
14
16
|
ClassDefinitionName,
|
15
17
|
Definition,
|
16
|
-
Element,
|
17
|
-
ElementName,
|
18
18
|
EnumDefinition,
|
19
19
|
EnumDefinitionName,
|
20
|
-
SchemaDefinition,
|
21
20
|
SlotDefinition,
|
22
21
|
SlotDefinitionName,
|
23
22
|
TypeDefinition,
|
24
23
|
TypeDefinitionName,
|
25
24
|
)
|
26
25
|
from linkml_runtime.utils.formatutils import camelcase, underscore
|
27
|
-
from
|
26
|
+
from linkml_runtime.utils.introspection import package_schemaview
|
27
|
+
from rdflib import OWL, RDF, XSD, BNode, Graph, Literal, URIRef
|
28
28
|
from rdflib.collection import Collection
|
29
|
-
from rdflib.namespace import
|
29
|
+
from rdflib.namespace import RDFS, SKOS
|
30
30
|
from rdflib.plugin import Parser as rdflib_Parser
|
31
31
|
from rdflib.plugin import plugins as rdflib_plugins
|
32
32
|
|
33
|
-
from linkml import
|
33
|
+
from linkml import METAMODEL_NAMESPACE_NAME
|
34
34
|
from linkml._version import __version__
|
35
35
|
from linkml.utils.generator import Generator, shared_arguments
|
36
36
|
|
37
|
+
OWL_TYPE = URIRef ## RDFS.Literal or OWL.Thing
|
38
|
+
|
37
39
|
|
38
40
|
@unique
|
39
41
|
class MetadataProfile(Enum):
|
@@ -44,6 +46,21 @@ class MetadataProfile(Enum):
|
|
44
46
|
|
45
47
|
linkml = "linkml"
|
46
48
|
rdfs = "rdfs"
|
49
|
+
ols = "ols"
|
50
|
+
|
51
|
+
@staticmethod
|
52
|
+
def list():
|
53
|
+
return list(map(lambda c: c.value, MetadataProfile))
|
54
|
+
|
55
|
+
|
56
|
+
@unique
|
57
|
+
class OWLProfile(Enum):
|
58
|
+
"""
|
59
|
+
An enumeration of OWL Profiles
|
60
|
+
"""
|
61
|
+
|
62
|
+
dl = "dl"
|
63
|
+
full = "full"
|
47
64
|
|
48
65
|
@staticmethod
|
49
66
|
def list():
|
@@ -61,12 +78,16 @@ class OwlSchemaGenerator(Generator):
|
|
61
78
|
|
62
79
|
`OWL Generator Docs <https://linkml.io/linkml/generators/owl>`_
|
63
80
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
81
|
+
.
|
82
|
+
|
83
|
+
- LinkML ClassDefinitions are translated to OWL Classes
|
84
|
+
- LinkML SlotDefinitions are translated to OWL Properties
|
85
|
+
- LinkML Enumerations are translated to OWL Classes
|
86
|
+
- LinkML TypeDefinitions are translated to OWL Datatypes
|
68
87
|
|
69
|
-
|
88
|
+
The translation aims to be as faithful as possible. But note that OWL is open-world,
|
89
|
+
whereas LinkML is closed-world
|
90
|
+
"""
|
70
91
|
|
71
92
|
# ClassVars
|
72
93
|
generatorname = os.path.basename(__file__)
|
@@ -75,148 +96,168 @@ class OwlSchemaGenerator(Generator):
|
|
75
96
|
x.name for x in rdflib_plugins(None, rdflib_Parser) if "/" not in str(x.name)
|
76
97
|
]
|
77
98
|
file_extension = "owl"
|
78
|
-
|
79
|
-
|
80
|
-
|
99
|
+
uses_schemaloader = False
|
100
|
+
|
101
|
+
ontology_uri_suffix: str = None
|
102
|
+
"""Suffix to add to the schema name to create the ontology URI, e.g. .owl.ttl"""
|
81
103
|
|
82
104
|
# ObjectVars
|
83
105
|
metadata_profile: MetadataProfile = None
|
84
|
-
|
106
|
+
"""Deprecated - use metadata_profiles."""
|
107
|
+
|
108
|
+
metadata_profiles: List[MetadataProfile] = field(default_factory=lambda: [])
|
109
|
+
"""By default, use the linkml metadata profile,
|
110
|
+
this allows for overrides."""
|
111
|
+
|
85
112
|
metaclasses: bool = field(default_factory=lambda: True)
|
113
|
+
"""if True, include OWL representations of ClassDefinition, SlotDefinition, etc. Introduces punning"""
|
114
|
+
|
86
115
|
add_ols_annotations: bool = field(default_factory=lambda: True)
|
87
116
|
graph: Optional[Graph] = None
|
117
|
+
"""Mutable graph that is being built up during OWL generation."""
|
118
|
+
|
88
119
|
top_value_uri: Optional[URIRef] = None
|
120
|
+
"""If metaclasses=True, then this property is used to connect object shadows to literals"""
|
121
|
+
|
89
122
|
type_objects: bool = field(default_factory=lambda: True)
|
90
|
-
|
123
|
+
"""if True, represents types as classes (and thus all slots are object properties);
|
124
|
+
typed object classes effectively shadow the main xsd literal types.
|
125
|
+
The purpose of this is to allow a uniform ObjectProperty representation for all slots,
|
126
|
+
without having to commit to being either Data or Object property (OWL-DL does not
|
127
|
+
allow a property to be both."""
|
128
|
+
|
129
|
+
assert_equivalent_classes: bool = field(default_factory=lambda: False)
|
130
|
+
"""If True, assert equivalence between definition_uris and class_uris"""
|
131
|
+
|
132
|
+
use_native_uris: bool = field(default_factory=lambda: True)
|
133
|
+
"""If True, use the definition_uris, otherwise use class_uris."""
|
134
|
+
|
135
|
+
mixins_as_expressions: bool = None
|
136
|
+
"""EXPERIMENTAL: If True, use OWL existential restrictions to represent mixins"""
|
137
|
+
|
138
|
+
slot_is_literal_map: Mapping[str, Set[bool]] = field(default_factory=lambda: defaultdict(set))
|
139
|
+
"""DEPRECATED: use node_owltypes"""
|
140
|
+
|
141
|
+
node_owltypes: Mapping[Union[BNode, URIRef], Set[OWL_TYPE]] = field(
|
142
|
+
default_factory=lambda: defaultdict(set)
|
143
|
+
)
|
144
|
+
"""rdfs:Datatype, owl:Thing"""
|
145
|
+
|
146
|
+
simplify: bool = field(default_factory=lambda: True)
|
147
|
+
"""Reduce complex expressions to simpler forms"""
|
148
|
+
|
149
|
+
target_profile: OWLProfile = field(default_factory=lambda: OWLProfile.dl)
|
150
|
+
"""Target OWL profile. Currently the only distinction is between DL and Full"""
|
91
151
|
|
92
|
-
|
93
|
-
|
152
|
+
metamodel_schemaview: SchemaView = field(
|
153
|
+
default_factory=lambda: package_schemaview("linkml_runtime.linkml_model.meta")
|
154
|
+
)
|
155
|
+
|
156
|
+
def as_graph(self) -> Graph:
|
157
|
+
"""
|
158
|
+
Generate an rdflib Graph from the schema
|
159
|
+
|
160
|
+
:return:
|
161
|
+
"""
|
162
|
+
sv = self.schemaview
|
163
|
+
schema = sv.schema
|
164
|
+
owl_id = schema.id
|
94
165
|
if self.ontology_uri_suffix:
|
95
166
|
owl_id = f"{owl_id}{self.ontology_uri_suffix}"
|
167
|
+
mergeimports = self.mergeimports
|
96
168
|
base = URIRef(owl_id)
|
97
|
-
|
169
|
+
graph = Graph(identifier=base)
|
170
|
+
self.graph = graph
|
98
171
|
for prefix in self.metamodel.schema.emit_prefixes:
|
99
172
|
self.graph.bind(prefix, self.metamodel.namespaces[prefix])
|
100
|
-
for pfx in
|
173
|
+
for pfx in schema.prefixes.values():
|
101
174
|
self.graph.namespace_manager.bind(pfx.prefix_prefix, URIRef(pfx.prefix_reference))
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
self.graph.add((self.top_value_uri, RDF.type, OWL.DatatypeProperty))
|
121
|
-
self.graph.add((self.top_value_uri, RDFS.label, Literal("value")))
|
122
|
-
|
123
|
-
def end_schema(self, output: Optional[str] = None, **_) -> None:
|
175
|
+
graph.add((base, RDF.type, OWL.Ontology))
|
176
|
+
|
177
|
+
for cls in sv.all_classes(imports=mergeimports).values():
|
178
|
+
self.add_class(cls)
|
179
|
+
for a in cls.attributes.values():
|
180
|
+
self.add_slot(a, attribute=True)
|
181
|
+
for slot in sv.all_slots(imports=mergeimports, attributes=False).values():
|
182
|
+
self.add_slot(slot, attribute=False)
|
183
|
+
for typ in sv.all_types(imports=mergeimports).values():
|
184
|
+
self.add_type(typ)
|
185
|
+
for enm in sv.all_enums(imports=mergeimports).values():
|
186
|
+
self.add_enum(enm)
|
187
|
+
|
188
|
+
self.add_metadata(schema, base)
|
189
|
+
return graph
|
190
|
+
|
191
|
+
def serialize(self, **kwargs) -> str:
|
192
|
+
self.as_graph()
|
124
193
|
data = self.graph.serialize(
|
125
194
|
format="turtle" if self.format in ["owl", "ttl"] else self.format
|
126
|
-
)
|
127
|
-
|
128
|
-
with open(output, "w", encoding="UTF-8") as outf:
|
129
|
-
outf.write(data)
|
130
|
-
else:
|
131
|
-
print(data)
|
195
|
+
)
|
196
|
+
return data
|
132
197
|
|
133
198
|
def add_metadata(self, e: Definition, uri: URIRef) -> None:
|
134
199
|
"""
|
135
|
-
Add generic annotation properties
|
200
|
+
Add generic annotation properties.
|
136
201
|
|
137
202
|
:param e: schema element
|
138
203
|
:param uri: URI representation of schema element
|
139
204
|
:return:
|
140
205
|
"""
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
logging.
|
177
|
-
if e.narrow_mappings is not None:
|
178
|
-
for m in e.narrow_mappings:
|
179
|
-
m_uri = self.namespaces.uri_for(m)
|
180
|
-
if m_uri is not None:
|
181
|
-
self.graph.add((uri, SKOS.narrowMatch, m_uri))
|
182
|
-
else:
|
183
|
-
logging.warning(f"No URI for {m}")
|
184
|
-
if e.broad_mappings is not None:
|
185
|
-
for m in e.broad_mappings:
|
186
|
-
m_uri = self.namespaces.uri_for(m)
|
187
|
-
if m_uri is not None:
|
188
|
-
self.graph.add((uri, SKOS.broadMatch, m_uri))
|
189
|
-
else:
|
190
|
-
logging.warning(f"No URI for {m}")
|
191
|
-
if e.related_mappings is not None:
|
192
|
-
for m in e.related_mappings:
|
193
|
-
m_uri = self.namespaces.uri_for(m)
|
194
|
-
if m_uri is not None:
|
195
|
-
self.graph.add((uri, SKOS.relatedMatch, m_uri))
|
206
|
+
|
207
|
+
msv = self.metamodel_schemaview
|
208
|
+
this_sv = self.schemaview
|
209
|
+
sn_mappings = msv.slot_name_mappings()
|
210
|
+
|
211
|
+
for metaslot_name, metaslot_value in vars(e).items():
|
212
|
+
if not metaslot_value:
|
213
|
+
continue
|
214
|
+
metaslot_name = sn_mappings.get(metaslot_name).name
|
215
|
+
metaslot = msv.induced_slot(metaslot_name, e.class_name)
|
216
|
+
metaslot_curie = msv.get_uri(metaslot, native=False, expand=False)
|
217
|
+
if metaslot_curie.startswith("linkml:"):
|
218
|
+
# only mapped properties
|
219
|
+
continue
|
220
|
+
metaslot_uri = URIRef(msv.get_uri(metaslot, native=False, expand=True))
|
221
|
+
if metaslot_name == "description" and self.has_profile(MetadataProfile.rdfs):
|
222
|
+
metaslot_uri = RDFS.comment
|
223
|
+
metaslot_range = metaslot.range
|
224
|
+
if not isinstance(metaslot_value, list):
|
225
|
+
metaslot_value = [metaslot_value]
|
226
|
+
for v in metaslot_value:
|
227
|
+
if metaslot_range in msv.all_types():
|
228
|
+
if metaslot_range == "uri":
|
229
|
+
obj = URIRef(v)
|
230
|
+
elif metaslot_range == "uriorcurie":
|
231
|
+
obj = URIRef(this_sv.expand_curie(v))
|
232
|
+
else:
|
233
|
+
obj = Literal(v)
|
234
|
+
elif metaslot_range in msv.all_subsets():
|
235
|
+
obj = Literal(v) # TODO
|
236
|
+
elif metaslot_range in msv.all_classes():
|
237
|
+
continue
|
238
|
+
# if isinstance(v, str):
|
239
|
+
# obj = URIRef(msv.expand_curie(v))
|
240
|
+
# else:
|
241
|
+
# logging.debug(f"Skipping {uri} {metaslot_uri} => {v}")
|
196
242
|
else:
|
197
|
-
|
243
|
+
obj = Literal(v)
|
244
|
+
self.graph.add((uri, metaslot_uri, obj))
|
245
|
+
|
198
246
|
for k, v in e.annotations.items():
|
199
|
-
if ":" in k:
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
k_uri = self.metamodel.namespaces.uri_for(k_curie)
|
206
|
-
except ValueError as me:
|
207
|
-
logging.info("Ignoring namespace error: " + str(me))
|
208
|
-
finally:
|
209
|
-
if k_uri is not None:
|
210
|
-
self.graph.add((uri, k_uri, Literal(v.value)))
|
211
|
-
# use default schema prefix
|
247
|
+
if ":" not in k:
|
248
|
+
default_prefix = this_sv.schema.default_prefix
|
249
|
+
if default_prefix in this_sv.schema.prefixes:
|
250
|
+
default_prefix = this_sv.schema.prefixes[default_prefix].prefix_reference
|
251
|
+
k = default_prefix + k
|
252
|
+
k_uri = this_sv.expand_curie(k)
|
212
253
|
else:
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
254
|
+
k_uri = this_sv.expand_curie(k)
|
255
|
+
if k_uri == k:
|
256
|
+
k_uri = None
|
257
|
+
if k_uri:
|
258
|
+
self.graph.add((uri, URIRef(k_uri), Literal(v.value)))
|
218
259
|
|
219
|
-
def
|
260
|
+
def add_class(self, cls: ClassDefinition) -> bool:
|
220
261
|
"""
|
221
262
|
Each ClassDefinition is represented as an OWL class
|
222
263
|
|
@@ -228,8 +269,8 @@ class OwlSchemaGenerator(Generator):
|
|
228
269
|
"""
|
229
270
|
# To understand how the RDF-level operations here related to the OWL
|
230
271
|
# representation, consult https://www.w3.org/TR/owl2-mapping-to-rdf/
|
272
|
+
sv = self.schemaview
|
231
273
|
cls_uri = self._class_uri(cls.name)
|
232
|
-
self.add_mappings(cls)
|
233
274
|
self.add_metadata(cls, cls_uri)
|
234
275
|
# add declaration
|
235
276
|
self.graph.add((cls_uri, RDF.type, OWL.Class))
|
@@ -239,24 +280,20 @@ class OwlSchemaGenerator(Generator):
|
|
239
280
|
(
|
240
281
|
cls_uri,
|
241
282
|
RDF.type,
|
242
|
-
|
243
|
-
camelcase("class definition")
|
244
|
-
],
|
283
|
+
ClassDefinition.class_class_uri,
|
245
284
|
)
|
246
285
|
)
|
247
|
-
self._add_element_properties(cls_uri, cls)
|
286
|
+
# self._add_element_properties(cls_uri, cls)
|
248
287
|
|
249
288
|
# Parent classes
|
250
289
|
if cls.is_a:
|
251
290
|
self.graph.add((cls_uri, RDFS.subClassOf, self._class_uri(cls.is_a)))
|
252
|
-
if cls.mixin:
|
253
|
-
self.graph.add((cls_uri, RDFS.subClassOf, METAMODEL_NAMESPACE.mixin))
|
254
291
|
for mixin in sorted(cls.mixins):
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
if self.
|
292
|
+
parent = self._class_uri(mixin)
|
293
|
+
if self.mixins_as_expressions:
|
294
|
+
parent = self._some_values_from(self._metaslot_uri("mixin"), parent)
|
295
|
+
self.graph.add((cls_uri, RDFS.subClassOf, parent))
|
296
|
+
if self.has_profile(MetadataProfile.ols):
|
260
297
|
# Add annotations for browser hints. See https://www.ebi.ac.uk/ols/docs/installation-guide
|
261
298
|
if cls.is_a is None:
|
262
299
|
if len(cls.mixins) == 0:
|
@@ -269,125 +306,269 @@ class OwlSchemaGenerator(Generator):
|
|
269
306
|
)
|
270
307
|
)
|
271
308
|
|
272
|
-
if cls.class_uri
|
273
|
-
|
274
|
-
if
|
275
|
-
|
276
|
-
self.graph.
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
# if cls.name in self.synopsis.applytorefs:
|
291
|
-
# for appl in self.synopsis.applytorefs[cls.name].classrefs:
|
292
|
-
# self.graph.add((cls_uri, RDFS.subClassOf, self._class_uri(appl)))
|
293
|
-
#
|
294
|
-
# for slotname in cls.defining_slots:
|
295
|
-
# restr_node = BNode()
|
296
|
-
# slot = self.schema.slots[slotname]
|
297
|
-
#
|
298
|
-
# self.graph.add((restr_node, RDF.type, OWL.Restriction))
|
299
|
-
# self.graph.add((restr_node, OWL.onProperty, self._prop_uri(slotname)))
|
300
|
-
# self._add_cardinality(restr_node, slot)
|
301
|
-
# # TODO: fix this
|
302
|
-
# # self.graph.add((restr_node, OWL.someValuesFrom, self._build_range(slot)))
|
303
|
-
# elts.append(restr_node)
|
304
|
-
#
|
305
|
-
# coll_bnode = BNode()
|
306
|
-
# Collection(self.graph, coll_bnode, elts)
|
307
|
-
# self.graph.add((equ_node, OWL.intersectionOf, coll_bnode))
|
308
|
-
|
309
|
-
# TODO: see whether unions belong
|
310
|
-
# if cls.union_of:
|
311
|
-
# union_node = BNode()
|
312
|
-
# Collection(self.graph, union_coll, [self.class_uri(union_node) for union_node in cls.union_of])
|
313
|
-
# self.graph.add((union_node, OWL.unionOf, union_coll))
|
314
|
-
# self.graph.add((cls_uri, RDFS.subClassOf, union_node))
|
315
|
-
|
316
|
-
for sn in sorted(self.own_slot_names(cls)):
|
317
|
-
# Defining_slots are covered above
|
318
|
-
slot = self.schema.slots[sn]
|
319
|
-
slot_node = BNode()
|
320
|
-
self.graph.add((cls_uri, RDFS.subClassOf, slot_node))
|
321
|
-
|
322
|
-
if self._range_is_datatype(slot):
|
323
|
-
cardinality_on = OWL.onDataRange
|
309
|
+
if cls.class_uri:
|
310
|
+
mapped_uri = sv.get_uri(cls, expand=True, native=not self.use_native_uris)
|
311
|
+
if cls_uri != mapped_uri:
|
312
|
+
p = OWL.equivalentClass if self.assert_equivalent_classes else SKOS.exactMatch
|
313
|
+
self.graph.add((URIRef(cls_uri), p, URIRef(mapped_uri)))
|
314
|
+
superclass_expr = self.transform_class_expression(cls)
|
315
|
+
if superclass_expr:
|
316
|
+
ixn_listnodes = []
|
317
|
+
if isinstance(superclass_expr, BNode):
|
318
|
+
ixn_listnodes = list(self.graph.objects(superclass_expr, OWL.intersectionOf))
|
319
|
+
if self.simplify and ixn_listnodes:
|
320
|
+
# simplify
|
321
|
+
if len(ixn_listnodes) > 1:
|
322
|
+
raise AssertionError
|
323
|
+
for x in Collection(self.graph, ixn_listnodes[0]):
|
324
|
+
self.graph.add((cls_uri, RDFS.subClassOf, x))
|
325
|
+
self._remove_list(ixn_listnodes[0])
|
326
|
+
self.graph.remove((superclass_expr, OWL.intersectionOf, ixn_listnodes[0]))
|
324
327
|
else:
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
328
|
+
self.graph.add((cls_uri, RDFS.subClassOf, superclass_expr))
|
329
|
+
|
330
|
+
def transform_class_expression(
|
331
|
+
self, cls: Union[ClassDefinition, AnonymousClassExpression]
|
332
|
+
) -> BNode:
|
333
|
+
graph = self.graph
|
334
|
+
sv = self.schemaview
|
335
|
+
if isinstance(cls, ClassDefinition):
|
336
|
+
own_slots = list(cls.slot_usage.values()) + list(cls.attributes.values())
|
337
|
+
else:
|
338
|
+
own_slots = []
|
339
|
+
own_slots.extend(cls.slot_conditions.values())
|
340
|
+
# sort by name
|
341
|
+
own_slots.sort(key=lambda x: x.name)
|
342
|
+
owl_exprs = []
|
343
|
+
if cls.any_of:
|
344
|
+
owl_exprs.append(
|
345
|
+
self._union_of([self.transform_class_expression(x) for x in cls.any_of])
|
346
|
+
)
|
347
|
+
if cls.exactly_one_of:
|
348
|
+
sub_exprs = [self.transform_class_expression(x) for x in cls.exactly_one_of]
|
349
|
+
if isinstance(cls, ClassDefinition):
|
350
|
+
cls_uri = self._class_uri(cls.name)
|
351
|
+
listnode = BNode()
|
352
|
+
Collection(graph, listnode, sub_exprs)
|
353
|
+
graph.add((cls_uri, OWL.disjointUnionOf, listnode))
|
354
|
+
else:
|
355
|
+
sub_sub_exprs = []
|
356
|
+
for i, x in enumerate(sub_exprs):
|
357
|
+
rest = sub_exprs[0:i] + sub_exprs[i + 1 :]
|
358
|
+
neg_expr = self._complement_of_union_of(rest)
|
359
|
+
sub_sub_exprs.append(self._intersection_of([x, neg_expr]))
|
360
|
+
sub_exprs.append(self._union_of(sub_sub_exprs))
|
361
|
+
if cls.all_of:
|
362
|
+
owl_exprs.append(
|
363
|
+
self._intersection_of([self.transform_class_expression(x) for x in cls.all_of])
|
364
|
+
)
|
365
|
+
if cls.none_of:
|
366
|
+
owl_exprs.append(
|
367
|
+
self._complement_of_union_of(
|
368
|
+
[self.transform_class_expression(x) for x in cls.any_of]
|
330
369
|
)
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
self.graph.add((slot_node, OWL.intersectionOf, coll_bnode))
|
347
|
-
self.graph.add((slot_node, RDF.type, OWL.Class))
|
370
|
+
)
|
371
|
+
for slot in own_slots:
|
372
|
+
x = self.transform_class_slot_expression(cls, slot, slot)
|
373
|
+
if not x:
|
374
|
+
range = sv.schema.default_range
|
375
|
+
if range:
|
376
|
+
if range in sv.all_types():
|
377
|
+
x = self._type_uri(range)
|
378
|
+
elif range in sv.all_enums():
|
379
|
+
x = self._enum_uri(range)
|
380
|
+
elif range in sv.all_classes():
|
381
|
+
x = self._enum_uri(range)
|
382
|
+
else:
|
383
|
+
raise ValueError(f"Unknown range {range}")
|
384
|
+
# x = self._class_uri(range)
|
348
385
|
else:
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
self.graph.add((slot_node, cardinality_on, self._range_uri(slot)))
|
386
|
+
x = OWL.Thing
|
387
|
+
slot_uri = self._prop_uri(slot)
|
388
|
+
if slot.name in sv.all_slots():
|
389
|
+
top_slot = sv.get_slot(slot.name)
|
354
390
|
else:
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
391
|
+
top_slot = slot
|
392
|
+
avf = BNode()
|
393
|
+
graph.add((avf, RDF.type, OWL.Restriction))
|
394
|
+
graph.add((avf, OWL.allValuesFrom, x))
|
395
|
+
graph.add((avf, OWL.onProperty, slot_uri))
|
396
|
+
owl_exprs.append(avf)
|
397
|
+
min_card_expr = BNode()
|
398
|
+
owl_exprs.append(min_card_expr)
|
399
|
+
min_card = 1 if slot.required or top_slot.required else 0
|
400
|
+
graph.add((min_card_expr, RDF.type, OWL.Restriction))
|
401
|
+
graph.add((min_card_expr, OWL.minCardinality, Literal(min_card)))
|
402
|
+
graph.add((min_card_expr, OWL.onProperty, slot_uri))
|
403
|
+
if not slot.multivalued and not top_slot.multivalued:
|
404
|
+
max_card_expr = BNode()
|
405
|
+
owl_exprs.append(max_card_expr)
|
406
|
+
graph.add((max_card_expr, RDF.type, OWL.Restriction))
|
407
|
+
graph.add((max_card_expr, OWL.maxCardinality, Literal(1)))
|
408
|
+
graph.add((max_card_expr, OWL.onProperty, slot_uri))
|
409
|
+
return self._intersection_of(owl_exprs)
|
410
|
+
|
411
|
+
def transform_class_slot_expression(
|
412
|
+
self,
|
413
|
+
cls: Optional[Union[ClassDefinition, AnonymousClassExpression]],
|
414
|
+
slot: Union[SlotDefinition, AnonymousSlotExpression],
|
415
|
+
main_slot: SlotDefinition = None,
|
416
|
+
) -> Union[BNode, URIRef]:
|
417
|
+
"""
|
418
|
+
Take a ClassExpression and SlotExpression combination and transform to a node.
|
375
419
|
|
376
|
-
|
377
|
-
|
378
|
-
|
420
|
+
:param cls:
|
421
|
+
:param slot:
|
422
|
+
:param main_slot:
|
423
|
+
:return:
|
379
424
|
"""
|
425
|
+
sv = self.schemaview
|
426
|
+
if main_slot is None:
|
427
|
+
if not isinstance(slot, SlotDefinition):
|
428
|
+
raise ValueError(f"Must pass main slot for {slot}")
|
429
|
+
main_slot = slot
|
430
|
+
|
431
|
+
owl_exprs = []
|
432
|
+
|
433
|
+
if slot.any_of:
|
434
|
+
owl_exprs.append(
|
435
|
+
self._union_of(
|
436
|
+
[self.transform_class_slot_expression(cls, x, main_slot) for x in slot.any_of]
|
437
|
+
)
|
438
|
+
)
|
439
|
+
if slot.all_of:
|
440
|
+
owl_exprs.append(
|
441
|
+
self._intersection_of(
|
442
|
+
[self.transform_class_slot_expression(cls, x, main_slot) for x in slot.all_of]
|
443
|
+
)
|
444
|
+
)
|
445
|
+
if slot.none_of:
|
446
|
+
owl_exprs.append(
|
447
|
+
self._complement_of_union_of(
|
448
|
+
[self.transform_class_slot_expression(cls, x, main_slot) for x in slot.none_of]
|
449
|
+
)
|
450
|
+
)
|
451
|
+
if slot.exactly_one_of:
|
452
|
+
owl_exprs.append(
|
453
|
+
self._exactly_one_of(
|
454
|
+
[
|
455
|
+
self.transform_class_slot_expression(cls, x, main_slot)
|
456
|
+
for x in slot.exactly_one_of
|
457
|
+
]
|
458
|
+
)
|
459
|
+
)
|
460
|
+
range = slot.range
|
461
|
+
# if not range and not owl_exprs:
|
462
|
+
# range = sv.schema.default_range
|
463
|
+
owl_types = set()
|
464
|
+
if range:
|
465
|
+
if range in sv.all_types(imports=True):
|
466
|
+
self.slot_is_literal_map[main_slot.name].add(True)
|
467
|
+
owl_types.add(RDFS.Literal)
|
468
|
+
typ = sv.get_type(range)
|
469
|
+
if self.type_objects:
|
470
|
+
# TODO
|
471
|
+
owl_exprs.append(self._type_uri(typ.name))
|
472
|
+
else:
|
473
|
+
owl_exprs.append(self._type_uri(typ.name))
|
474
|
+
elif range in self.schema.enums:
|
475
|
+
# TODO: enums fill this in
|
476
|
+
owl_exprs.append(self._enum_uri(EnumDefinitionName(range)))
|
477
|
+
else:
|
478
|
+
owl_types.add(OWL.Thing)
|
479
|
+
self.slot_is_literal_map[main_slot.name].add(False)
|
480
|
+
owl_exprs.append(self._class_uri(ClassDefinitionName(range)))
|
481
|
+
constraints_exprs, constraints_owltypes = self.add_constraints(slot)
|
482
|
+
owl_types.update(constraints_owltypes)
|
483
|
+
owl_exprs.extend(constraints_exprs)
|
484
|
+
# constraints = {
|
485
|
+
# XSD.minInclusive: slot.minimum_value,
|
486
|
+
# XSD.maxInclusive: slot.maximum_value,
|
487
|
+
# XSD.pattern: slot.pattern, # TODO: map between ECMAScript and XSD regular expressions
|
488
|
+
# }
|
489
|
+
# for constraint_prop, constraint_val in constraints.items():
|
490
|
+
# if constraint_val is not None:
|
491
|
+
# owl_types.add(RDFS.Literal)
|
492
|
+
# dr = BNode()
|
493
|
+
# graph.add((dr, RDF.type, RDFS.Datatype))
|
494
|
+
# if isinstance(constraint_val, float):
|
495
|
+
# graph.add((dr, OWL.onDatatype, XSD.float))
|
496
|
+
# elif isinstance(constraint_val, int):
|
497
|
+
# graph.add((dr, OWL.onDatatype, XSD.integer))
|
498
|
+
# else:
|
499
|
+
# graph.add((dr, OWL.onDatatype, XSD.string))
|
500
|
+
# listnode = BNode()
|
501
|
+
# x = BNode()
|
502
|
+
# Collection(graph, listnode, [x])
|
503
|
+
# graph.add((dr, OWL.withRestrictions, listnode))
|
504
|
+
# graph.add((x, constraint_prop, Literal(constraint_val)))
|
505
|
+
# owl_exprs.append(dr)
|
506
|
+
# constraints = {k: v for k, v in constraints.items() if v is not None}
|
507
|
+
# if False and constraints:
|
508
|
+
# owl_types.add(RDFS.Literal)
|
509
|
+
# dr = BNode()
|
510
|
+
# listnode = BNode()
|
511
|
+
# graph.add((dr, RDF.type, RDFS.Datatype))
|
512
|
+
# # TODO: allow for mixing of constraints
|
513
|
+
# #if isinstance(v, int):
|
514
|
+
# # dt = XSD.integer
|
515
|
+
# #elif isinstance(v, float):
|
516
|
+
# # dt = XSD.decimal
|
517
|
+
# #else:
|
518
|
+
# # logging.warning(f"Unknown datatype for {v}")
|
519
|
+
# # dt = XSD.integer
|
520
|
+
# graph.add((dr, OWL.onDatatype, XSD.integer))
|
521
|
+
# graph.add((dr, OWL.withRestrictions, listnode))
|
522
|
+
# dr_exprs = []
|
523
|
+
# for prop, val in constraints.items():
|
524
|
+
# x = BNode()
|
525
|
+
# graph.add((x, prop, Literal(val)))
|
526
|
+
# dr_exprs.append(x)
|
527
|
+
# Collection(graph, listnode, dr_exprs)
|
528
|
+
# owl_exprs.append(dr)
|
529
|
+
this_expr = self._intersection_of(owl_exprs, owl_types=owl_types)
|
530
|
+
self.node_owltypes[this_expr].update(self._get_owltypes(owl_types, owl_exprs))
|
531
|
+
return this_expr
|
532
|
+
|
533
|
+
def add_constraints(
|
534
|
+
self,
|
535
|
+
element: Union[
|
536
|
+
SlotDefinition, AnonymousSlotExpression, TypeDefinition, AnonymousTypeExpression
|
537
|
+
],
|
538
|
+
) -> Tuple[List[BNode], Set[OWL_TYPE]]:
|
539
|
+
owl_types = set()
|
540
|
+
owl_exprs = []
|
541
|
+
graph = self.graph
|
542
|
+
constraints = {
|
543
|
+
XSD.minInclusive: element.minimum_value,
|
544
|
+
XSD.maxInclusive: element.maximum_value,
|
545
|
+
XSD.pattern: element.pattern, # TODO: map between ECMAScript and XSD regular expressions
|
546
|
+
}
|
547
|
+
for constraint_prop, constraint_val in constraints.items():
|
548
|
+
if constraint_val is not None:
|
549
|
+
owl_types.add(RDFS.Literal)
|
550
|
+
dr = BNode()
|
551
|
+
graph.add((dr, RDF.type, RDFS.Datatype))
|
552
|
+
if isinstance(constraint_val, float):
|
553
|
+
graph.add((dr, OWL.onDatatype, XSD.float))
|
554
|
+
elif isinstance(constraint_val, int):
|
555
|
+
graph.add((dr, OWL.onDatatype, XSD.integer))
|
556
|
+
else:
|
557
|
+
graph.add((dr, OWL.onDatatype, XSD.string))
|
558
|
+
listnode = BNode()
|
559
|
+
x = BNode()
|
560
|
+
Collection(graph, listnode, [x])
|
561
|
+
graph.add((dr, OWL.withRestrictions, listnode))
|
562
|
+
graph.add((x, constraint_prop, Literal(constraint_val)))
|
563
|
+
owl_exprs.append(dr)
|
564
|
+
return owl_exprs, owl_types
|
565
|
+
|
566
|
+
def add_slot(self, slot: SlotDefinition, attribute=False) -> None:
|
380
567
|
# determine if this is a slot that has been induced by slot_usage; if so
|
381
568
|
# the meaning of the slot is context-specific and should not be used for
|
382
569
|
# global properties
|
383
|
-
if slot.alias is not None and slot.alias != slot.name and slot.alias in self.schema.slots:
|
384
|
-
logging.debug(
|
385
|
-
f"SKIPPING slot induced by slot_usage: {slot.alias} // {slot.name} // {slot}"
|
386
|
-
)
|
387
|
-
return
|
388
570
|
|
389
|
-
slot_uri = self._prop_uri(slot
|
390
|
-
# logging.error(f'SLOT_URI={slot_uri}')
|
571
|
+
slot_uri = self._prop_uri(slot)
|
391
572
|
|
392
573
|
# Slots may be modeled as Object or Datatype Properties
|
393
574
|
# if type_objects is True, then ALL slots are ObjectProperties
|
@@ -398,53 +579,54 @@ class OwlSchemaGenerator(Generator):
|
|
398
579
|
(
|
399
580
|
slot_uri,
|
400
581
|
RDF.type,
|
401
|
-
|
402
|
-
camelcase("slot definition")
|
403
|
-
],
|
582
|
+
SlotDefinition.class_class_uri,
|
404
583
|
)
|
405
584
|
)
|
406
585
|
|
407
|
-
|
408
|
-
|
409
|
-
for
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
586
|
+
if attribute:
|
587
|
+
n = 0
|
588
|
+
for c in self.schemaview.all_classes().values():
|
589
|
+
for a in c.attributes.values():
|
590
|
+
att_uri = self.schemaview.get_uri(a, native=False, expand=True)
|
591
|
+
if slot_uri == URIRef(att_uri):
|
592
|
+
n += 1
|
593
|
+
if n > 1:
|
594
|
+
logging.warning(f"Ambiguous attribute: {slot.name} {slot_uri}")
|
595
|
+
return
|
417
596
|
|
418
|
-
self.add_mappings(slot)
|
419
597
|
self.add_metadata(slot, slot_uri)
|
420
|
-
self._add_element_properties(slot_uri, slot)
|
421
598
|
|
422
|
-
|
599
|
+
if attribute:
|
600
|
+
return
|
601
|
+
|
602
|
+
range_expr = self.transform_class_slot_expression(None, slot)
|
603
|
+
if range_expr:
|
604
|
+
self.graph.add((slot_uri, RDFS.range, range_expr))
|
423
605
|
if slot.domain:
|
424
606
|
self.graph.add((slot_uri, RDFS.domain, self._class_uri(slot.domain)))
|
425
607
|
if slot.inverse:
|
426
608
|
self.graph.add((slot_uri, OWL.inverseOf, self._prop_uri(slot.inverse)))
|
427
|
-
|
428
|
-
|
609
|
+
characteristics = {
|
610
|
+
"symmetric": OWL.SymmetricProperty,
|
611
|
+
"asymmetric": OWL.AsymmetricProperty,
|
612
|
+
"transitive": OWL.TransitiveProperty,
|
613
|
+
"reflexive": OWL.ReflexiveProperty,
|
614
|
+
"irreflexive": OWL.IrreflexiveProperty,
|
615
|
+
}
|
616
|
+
for metaslot, uri in characteristics.items():
|
617
|
+
if getattr(slot, metaslot, False):
|
618
|
+
self.graph.add((slot_uri, RDF.type, uri))
|
429
619
|
|
430
|
-
# Parent slots. Note that is_a and mixin both map to subPropertyOf,
|
431
|
-
# and are not distinguishable
|
432
|
-
# TODO: consider annotating axiom to indicate if this is is-a or mixin
|
433
620
|
if slot.is_a:
|
434
621
|
self.graph.add((slot_uri, RDFS.subPropertyOf, self._prop_uri(slot.is_a)))
|
435
622
|
for mixin in slot.mixins:
|
436
623
|
self.graph.add((slot_uri, RDFS.subPropertyOf, self._prop_uri(mixin)))
|
437
|
-
if slot.name in self.synopsis.applytorefs:
|
438
|
-
for appl in self.synopsis.applytorefs[slot.name].slotrefs:
|
439
|
-
self.graph.add((slot_uri, RDFS.subClassOf, self._prop_uri(appl)))
|
440
624
|
|
441
|
-
def
|
625
|
+
def add_type(self, typ: TypeDefinition) -> None:
|
442
626
|
type_uri = self._type_uri(typ.name)
|
443
|
-
if not self.type_objects:
|
444
|
-
return False
|
445
627
|
if typ.from_schema == "https://w3id.org/linkml/types":
|
446
628
|
return
|
447
|
-
|
629
|
+
|
448
630
|
if self.metaclasses:
|
449
631
|
self.graph.add(
|
450
632
|
(
|
@@ -455,18 +637,43 @@ class OwlSchemaGenerator(Generator):
|
|
455
637
|
],
|
456
638
|
)
|
457
639
|
)
|
458
|
-
self._add_element_properties(type_uri, typ)
|
459
|
-
if
|
460
|
-
self.graph.add((type_uri,
|
640
|
+
# self._add_element_properties(type_uri, typ)
|
641
|
+
if self.type_objects:
|
642
|
+
self.graph.add((type_uri, RDF.type, OWL.Class))
|
643
|
+
if typ.typeof:
|
644
|
+
self.graph.add((type_uri, RDFS.subClassOf, self._type_uri(typ.typeof)))
|
645
|
+
else:
|
646
|
+
if not self.top_value_uri:
|
647
|
+
self.top_value_uri = self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][
|
648
|
+
"topValue"
|
649
|
+
]
|
650
|
+
self.graph.add((self.top_value_uri, RDF.type, OWL.DatatypeProperty))
|
651
|
+
self.graph.add((self.top_value_uri, RDFS.label, Literal("value")))
|
652
|
+
restr = BNode()
|
653
|
+
self.graph.add((restr, RDF.type, OWL.Restriction))
|
654
|
+
self.graph.add((restr, OWL.qualifiedCardinality, Literal(1)))
|
655
|
+
self.graph.add((restr, OWL.onProperty, self.top_value_uri))
|
656
|
+
self.graph.add((restr, OWL.onDataRange, self._type_uri(typ.name)))
|
657
|
+
self.graph.add((type_uri, RDFS.subClassOf, restr))
|
461
658
|
else:
|
462
|
-
|
463
|
-
|
464
|
-
self.
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
659
|
+
self.graph.add((type_uri, RDF.type, RDFS.Datatype))
|
660
|
+
eq_conjunctions = []
|
661
|
+
if typ.typeof and type_uri != self._type_uri(typ.typeof):
|
662
|
+
# self.graph.add((type_uri, OWL.equivalentClass, self._type_uri(typ.typeof)))
|
663
|
+
eq_conjunctions.append(self._type_uri(typ.typeof))
|
664
|
+
# self.graph.add((type_uri, RDFS.subClassOf, self._type_uri(typ.typeof)))
|
665
|
+
if typ.uri and type_uri != URIRef(self.schemaview.expand_curie(typ.uri)):
|
666
|
+
eq_conjunctions.append(URIRef(self.schemaview.expand_curie(typ.uri)))
|
667
|
+
# self.graph.add(
|
668
|
+
# (type_uri, OWL.equivalentClass, URIRef(self.schemaview.expand_curie(typ.uri)))
|
669
|
+
# )
|
670
|
+
constraints_exprs, _ = self.add_constraints(typ)
|
671
|
+
eq_conjunctions.extend(constraints_exprs)
|
672
|
+
ixn = self._intersection_of(eq_conjunctions)
|
673
|
+
if ixn:
|
674
|
+
self.graph.add((type_uri, OWL.equivalentClass, ixn))
|
675
|
+
|
676
|
+
def add_enum(self, e: EnumDefinition) -> None:
|
470
677
|
g = self.graph
|
471
678
|
enum_uri = self._enum_uri(e.name)
|
472
679
|
g.add((enum_uri, RDF.type, OWL.Class))
|
@@ -480,15 +687,12 @@ class OwlSchemaGenerator(Generator):
|
|
480
687
|
],
|
481
688
|
)
|
482
689
|
)
|
483
|
-
self._add_element_properties(enum_uri, e)
|
484
690
|
pv_uris = []
|
485
691
|
for pv in e.permissible_values.values():
|
486
692
|
if pv.meaning:
|
487
|
-
pv_uri = self.
|
693
|
+
pv_uri = URIRef(self.schemaview.expand_curie(pv.meaning))
|
488
694
|
else:
|
489
695
|
pv_uri = enum_uri + "#" + pv.text.replace(" ", "+")
|
490
|
-
# pv_uri = self.namespaces.uri_for(downcase(pv.text))
|
491
|
-
# pv_uri = None
|
492
696
|
pv_uris.append(pv_uri)
|
493
697
|
if pv_uri:
|
494
698
|
g.add((pv_uri, RDF.type, OWL.Class))
|
@@ -500,71 +704,120 @@ class OwlSchemaGenerator(Generator):
|
|
500
704
|
pv_uri,
|
501
705
|
)
|
502
706
|
)
|
503
|
-
self._add_element_properties(pv_uri, pv)
|
707
|
+
# self._add_element_properties(pv_uri, pv)
|
504
708
|
if self.metaclasses:
|
505
709
|
g.add((pv_uri, RDF.type, enum_uri))
|
506
710
|
if all([pv is not None for pv in pv_uris]):
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
711
|
+
self._union_of(pv_uris, node=enum_uri)
|
712
|
+
|
713
|
+
def has_profile(self, profile: MetadataProfile, default=False) -> bool:
|
714
|
+
"""
|
715
|
+
Determine if a metadata profile is active.
|
716
|
+
|
717
|
+
:param profile: profile to check
|
718
|
+
:param default: True if the configured profiles include the specified profile
|
719
|
+
:return:
|
720
|
+
"""
|
721
|
+
if default and not self.metadata_profile and not self.metadata_profiles:
|
722
|
+
return True
|
723
|
+
return profile in self.metadata_profiles or profile == self.metadata_profile
|
724
|
+
|
725
|
+
def _get_owltypes(
|
726
|
+
self, current: Set[OWL_TYPE], exprs: List[Union[BNode, URIRef]]
|
727
|
+
) -> Set[OWL_TYPE]:
|
728
|
+
"""
|
729
|
+
Gets the OWL types of specified expressions plus current owl types.
|
730
|
+
|
731
|
+
:param current:
|
732
|
+
:param exprs:
|
733
|
+
:return:
|
734
|
+
"""
|
735
|
+
owltypes = set()
|
736
|
+
for x in exprs:
|
737
|
+
x_owltypes = self.node_owltypes.get(x, None)
|
738
|
+
if x_owltypes:
|
739
|
+
owltypes.update(x_owltypes)
|
740
|
+
owltypes.update(current)
|
741
|
+
if len(owltypes) > 1:
|
742
|
+
logging.warning(f"Multiple owl types {owltypes}")
|
743
|
+
# if self.target_profile == OWLProfile.dl:
|
744
|
+
return owltypes
|
745
|
+
|
746
|
+
def _remove_list(self, listnode: BNode) -> None:
|
747
|
+
graph = self.graph
|
748
|
+
triples = list(graph.triples((listnode, None, None)))
|
749
|
+
while triples:
|
750
|
+
t = triples.pop(0)
|
751
|
+
subj, pred, obj = t
|
752
|
+
if pred not in [RDF.first, RDF.rest]:
|
753
|
+
continue
|
754
|
+
graph.remove(t)
|
755
|
+
if isinstance(obj, BNode):
|
756
|
+
triples.extend(graph.triples((obj, None, None)))
|
757
|
+
|
758
|
+
def _some_values_from(self, property: URIRef, filler: Union[URIRef, BNode]) -> BNode:
|
759
|
+
node = BNode()
|
760
|
+
self.graph.add((node, RDF.type, OWL.Restriction))
|
761
|
+
self.graph.add((node, OWL.onProperty, property))
|
762
|
+
self.graph.add((node, OWL.someValuesFrom, filler))
|
763
|
+
return node
|
764
|
+
|
765
|
+
def _complement_of_union_of(
|
766
|
+
self, exprs: List[Union[BNode, URIRef]], **kwargs
|
767
|
+
) -> Optional[Union[BNode, URIRef]]:
|
768
|
+
if not exprs:
|
769
|
+
raise ValueError("Must pass at least one")
|
770
|
+
neg_expr = BNode()
|
771
|
+
self.graph.add((neg_expr, OWL.complementOf, self._union_of(exprs)), **kwargs)
|
772
|
+
return neg_expr
|
773
|
+
|
774
|
+
def _intersection_of(
|
775
|
+
self, exprs: List[Union[BNode, URIRef]], **kwargs
|
776
|
+
) -> Optional[Union[BNode, URIRef]]:
|
777
|
+
return self._boolean_expression(exprs, OWL.intersectionOf, **kwargs)
|
778
|
+
|
779
|
+
def _union_of(
|
780
|
+
self, exprs: List[Union[BNode, URIRef]], **kwargs
|
781
|
+
) -> Optional[Union[BNode, URIRef]]:
|
782
|
+
return self._boolean_expression(exprs, OWL.unionOf, **kwargs)
|
783
|
+
|
784
|
+
def _exactly_one_of(self, exprs: List[Union[BNode, URIRef]]) -> Optional[Union[BNode, URIRef]]:
|
785
|
+
if not exprs:
|
786
|
+
raise ValueError("Must pass at least one")
|
787
|
+
if len(exprs) == 1:
|
788
|
+
return exprs[0]
|
789
|
+
sub_exprs = []
|
790
|
+
for i, x in enumerate(exprs):
|
791
|
+
rest = exprs[0:i] + exprs[i + 1 :]
|
792
|
+
neg_expr = self._complement_of_union_of(rest)
|
793
|
+
sub_exprs.append(self._intersection_of([x, neg_expr]))
|
794
|
+
return self._union_of(sub_exprs)
|
795
|
+
|
796
|
+
def _boolean_expression(
|
797
|
+
self,
|
798
|
+
exprs: List[Union[BNode, URIRef]],
|
799
|
+
predicate: URIRef,
|
800
|
+
node: Optional[URIRef] = None,
|
801
|
+
owl_types: Set[OWL_TYPE] = None,
|
802
|
+
) -> Optional[Union[BNode, URIRef]]:
|
803
|
+
graph = self.graph
|
804
|
+
if len(exprs) == 0:
|
567
805
|
return None
|
806
|
+
elif len(exprs) == 1:
|
807
|
+
return exprs[0]
|
808
|
+
else:
|
809
|
+
if node is None:
|
810
|
+
node = BNode()
|
811
|
+
listnode = BNode()
|
812
|
+
Collection(graph, listnode, exprs)
|
813
|
+
graph.add((node, predicate, listnode))
|
814
|
+
if owl_types is None:
|
815
|
+
owl_types = set()
|
816
|
+
owl_types = owl_types.union(self._get_owltypes(set(), exprs))
|
817
|
+
if len(owl_types) == 1:
|
818
|
+
if RDFS.Literal in owl_types:
|
819
|
+
graph.add((node, RDF.type, RDFS.Datatype))
|
820
|
+
return node
|
568
821
|
|
569
822
|
def _range_is_datatype(self, slot: SlotDefinition) -> bool:
|
570
823
|
if self.type_objects:
|
@@ -585,26 +838,53 @@ class OwlSchemaGenerator(Generator):
|
|
585
838
|
else:
|
586
839
|
return self._class_uri(ClassDefinitionName(slot.range))
|
587
840
|
|
588
|
-
def _class_uri(self, cn: ClassDefinitionName) -> URIRef:
|
589
|
-
c = self.
|
590
|
-
return URIRef(c.
|
841
|
+
def _class_uri(self, cn: Union[str, ClassDefinitionName]) -> URIRef:
|
842
|
+
c = self.schemaview.get_class(cn)
|
843
|
+
return URIRef(self.schemaview.get_uri(c, expand=True, native=self.use_native_uris))
|
591
844
|
|
592
|
-
def _enum_uri(self, en: EnumDefinitionName) -> URIRef:
|
593
|
-
|
845
|
+
def _enum_uri(self, en: Union[str, EnumDefinitionName]) -> URIRef:
|
846
|
+
sv = self.schemaview
|
847
|
+
e = sv.get_enum(en)
|
848
|
+
uri = e.enum_uri
|
849
|
+
if not uri:
|
850
|
+
uri = f"{sv.schema.default_prefix}:{camelcase(en)}"
|
594
851
|
# TODO: allow control over URIs
|
595
|
-
return URIRef(
|
852
|
+
return URIRef(self.schemaview.expand_curie(uri))
|
596
853
|
|
597
|
-
def _prop_uri(self, pn: SlotDefinitionName) -> URIRef:
|
598
|
-
|
599
|
-
|
600
|
-
return self.namespaces.uri_for(p.slot_uri)
|
854
|
+
def _prop_uri(self, pn: Union[SlotDefinition, SlotDefinitionName]) -> URIRef:
|
855
|
+
if isinstance(pn, SlotDefinition):
|
856
|
+
p = pn
|
601
857
|
else:
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
858
|
+
p = self.schemaview.get_slot(pn, attributes=True)
|
859
|
+
if not p:
|
860
|
+
raise ValueError(f"No such slot or attribute: {pn}")
|
861
|
+
try:
|
862
|
+
return URIRef(self.schemaview.get_uri(p, expand=True, native=self.use_native_uris))
|
863
|
+
except (KeyError, ValueError):
|
864
|
+
# TODO: fix this upstream in schemaview
|
865
|
+
default_prefix = self.schemaview.schema.default_prefix or ""
|
866
|
+
return URIRef(self.schemaview.expand_curie(f"{default_prefix}:{underscore(p.name)}"))
|
867
|
+
|
868
|
+
def _type_uri(self, tn: TypeDefinitionName, native: bool = None) -> URIRef:
|
869
|
+
if native is None:
|
870
|
+
# never use native unless type shadowing with objects is enabled
|
871
|
+
native = self.type_objects
|
872
|
+
if native:
|
873
|
+
# UGLY HACK: Currently schemaview does not camelcase types
|
874
|
+
e = self.schemaview.get_element(tn, imports=True)
|
875
|
+
if e.from_schema is not None:
|
876
|
+
schema = next(
|
877
|
+
sc for sc in self.schemaview.schema_map.values() if sc.id == e.from_schema
|
878
|
+
)
|
879
|
+
pfx = schema.default_prefix
|
880
|
+
if pfx == "linkml":
|
881
|
+
return URIRef(self.schemaview.expand_curie(f"{pfx}:{camelcase(tn)}"))
|
882
|
+
t = self.schemaview.get_type(tn)
|
883
|
+
expanded = self.schemaview.get_uri(t, expand=True, native=native)
|
884
|
+
if expanded.startswith("xsd:"):
|
885
|
+
# TODO: fix upstream in schemaview; default_curi_maps is different on windows
|
886
|
+
return XSD[expanded[4:]]
|
887
|
+
return URIRef(expanded)
|
608
888
|
|
609
889
|
def _add_metamodel_class(self, cname: str) -> None:
|
610
890
|
metac = self.metamodel.schema.classes[cname]
|
@@ -613,30 +893,33 @@ class OwlSchemaGenerator(Generator):
|
|
613
893
|
# self._add_element_properties(metac_uri, metac)
|
614
894
|
|
615
895
|
def slot_owl_type(self, slot: SlotDefinition) -> URIRef:
|
896
|
+
sv = self.schemaview
|
897
|
+
if slot.range is None:
|
898
|
+
range = self.schemaview.schema.default_range
|
899
|
+
else:
|
900
|
+
range = slot.range
|
616
901
|
if self.type_objects:
|
617
902
|
return OWL.ObjectProperty
|
618
|
-
|
903
|
+
is_literal_vals = self.slot_is_literal_map[slot.name]
|
904
|
+
if len(is_literal_vals) > 1:
|
905
|
+
logging.warning(f"Ambiguous type for: {slot.name}")
|
906
|
+
if range is None:
|
907
|
+
if not is_literal_vals:
|
908
|
+
logging.warning(f"Guessing type for {slot.name}")
|
909
|
+
return OWL.ObjectProperty
|
910
|
+
if (list(is_literal_vals))[0]:
|
911
|
+
return OWL.DatatypeProperty
|
912
|
+
else:
|
913
|
+
return OWL.ObjectProperty
|
914
|
+
elif range in sv.all_classes():
|
619
915
|
return OWL.ObjectProperty
|
620
|
-
elif
|
916
|
+
elif range in sv.all_enums():
|
621
917
|
return OWL.ObjectProperty
|
622
|
-
elif
|
918
|
+
elif range in sv.all_types():
|
623
919
|
return OWL.DatatypeProperty
|
624
920
|
else:
|
625
921
|
raise Exception(f"Unknown range: {slot.range}")
|
626
922
|
|
627
|
-
# @Deprecated
|
628
|
-
def is_slot_object_property(self, slot: SlotDefinition) -> bool:
|
629
|
-
if self.type_objects:
|
630
|
-
return True
|
631
|
-
elif slot.range in self.schema.classes:
|
632
|
-
return True
|
633
|
-
elif slot.range in self.schema.enums:
|
634
|
-
return True
|
635
|
-
elif slot.range in self.schema.types:
|
636
|
-
return False
|
637
|
-
else:
|
638
|
-
raise Exception(f"Unknown range: {slot.range}")
|
639
|
-
|
640
923
|
|
641
924
|
@shared_arguments(OwlSchemaGenerator)
|
642
925
|
@click.command()
|
@@ -650,13 +933,13 @@ class OwlSchemaGenerator(Generator):
|
|
650
933
|
)
|
651
934
|
@click.option(
|
652
935
|
"--type-objects/--no-type-objects",
|
653
|
-
default=
|
936
|
+
default=False,
|
654
937
|
show_default=True,
|
655
938
|
help="If true, will model linkml types as objects, not literals",
|
656
939
|
)
|
657
940
|
@click.option(
|
658
941
|
"--metaclasses/--no-metaclasses",
|
659
|
-
default=
|
942
|
+
default=False,
|
660
943
|
show_default=True,
|
661
944
|
help="If true, include linkml metamodel classes as metaclasses. Note this introduces punning in OWL-DL",
|
662
945
|
)
|
@@ -695,11 +978,11 @@ def cli(yamlfile, metadata_profile: str, **kwargs):
|
|
695
978
|
For more info, see: https://linkml.io/linkml/generators/owl
|
696
979
|
"""
|
697
980
|
if metadata_profile is not None:
|
698
|
-
|
981
|
+
metadata_profiles = [MetadataProfile(metadata_profile)]
|
699
982
|
else:
|
700
|
-
|
983
|
+
metadata_profiles = [MetadataProfile.linkml]
|
701
984
|
print(
|
702
|
-
OwlSchemaGenerator(yamlfile,
|
985
|
+
OwlSchemaGenerator(yamlfile, metadata_profiles=metadata_profiles, **kwargs).serialize(
|
703
986
|
**kwargs
|
704
987
|
)
|
705
988
|
)
|