linkml 1.5.8rc1__py3-none-any.whl → 1.6.1__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/jsonschemagen.py +8 -0
- linkml/generators/legacy/__init__.py +0 -0
- linkml/generators/owlgen.py +676 -391
- linkml/generators/pythongen.py +121 -17
- linkml/utils/generator.py +0 -1
- {linkml-1.5.8rc1.dist-info → linkml-1.6.1.dist-info}/METADATA +3 -3
- {linkml-1.5.8rc1.dist-info → linkml-1.6.1.dist-info}/RECORD +10 -9
- {linkml-1.5.8rc1.dist-info → linkml-1.6.1.dist-info}/LICENSE +0 -0
- {linkml-1.5.8rc1.dist-info → linkml-1.6.1.dist-info}/WHEEL +0 -0
- {linkml-1.5.8rc1.dist-info → linkml-1.6.1.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,271 @@ 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 sv.all_enums(imports=True):
|
475
|
+
# TODO: enums fill this in
|
476
|
+
owl_exprs.append(self._enum_uri(EnumDefinitionName(range)))
|
477
|
+
elif range in sv.all_classes(imports=True):
|
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
|
+
else:
|
482
|
+
raise ValueError(f"Unknown range {range}")
|
483
|
+
constraints_exprs, constraints_owltypes = self.add_constraints(slot)
|
484
|
+
owl_types.update(constraints_owltypes)
|
485
|
+
owl_exprs.extend(constraints_exprs)
|
486
|
+
# constraints = {
|
487
|
+
# XSD.minInclusive: slot.minimum_value,
|
488
|
+
# XSD.maxInclusive: slot.maximum_value,
|
489
|
+
# XSD.pattern: slot.pattern, # TODO: map between ECMAScript and XSD regular expressions
|
490
|
+
# }
|
491
|
+
# for constraint_prop, constraint_val in constraints.items():
|
492
|
+
# if constraint_val is not None:
|
493
|
+
# owl_types.add(RDFS.Literal)
|
494
|
+
# dr = BNode()
|
495
|
+
# graph.add((dr, RDF.type, RDFS.Datatype))
|
496
|
+
# if isinstance(constraint_val, float):
|
497
|
+
# graph.add((dr, OWL.onDatatype, XSD.float))
|
498
|
+
# elif isinstance(constraint_val, int):
|
499
|
+
# graph.add((dr, OWL.onDatatype, XSD.integer))
|
500
|
+
# else:
|
501
|
+
# graph.add((dr, OWL.onDatatype, XSD.string))
|
502
|
+
# listnode = BNode()
|
503
|
+
# x = BNode()
|
504
|
+
# Collection(graph, listnode, [x])
|
505
|
+
# graph.add((dr, OWL.withRestrictions, listnode))
|
506
|
+
# graph.add((x, constraint_prop, Literal(constraint_val)))
|
507
|
+
# owl_exprs.append(dr)
|
508
|
+
# constraints = {k: v for k, v in constraints.items() if v is not None}
|
509
|
+
# if False and constraints:
|
510
|
+
# owl_types.add(RDFS.Literal)
|
511
|
+
# dr = BNode()
|
512
|
+
# listnode = BNode()
|
513
|
+
# graph.add((dr, RDF.type, RDFS.Datatype))
|
514
|
+
# # TODO: allow for mixing of constraints
|
515
|
+
# #if isinstance(v, int):
|
516
|
+
# # dt = XSD.integer
|
517
|
+
# #elif isinstance(v, float):
|
518
|
+
# # dt = XSD.decimal
|
519
|
+
# #else:
|
520
|
+
# # logging.warning(f"Unknown datatype for {v}")
|
521
|
+
# # dt = XSD.integer
|
522
|
+
# graph.add((dr, OWL.onDatatype, XSD.integer))
|
523
|
+
# graph.add((dr, OWL.withRestrictions, listnode))
|
524
|
+
# dr_exprs = []
|
525
|
+
# for prop, val in constraints.items():
|
526
|
+
# x = BNode()
|
527
|
+
# graph.add((x, prop, Literal(val)))
|
528
|
+
# dr_exprs.append(x)
|
529
|
+
# Collection(graph, listnode, dr_exprs)
|
530
|
+
# owl_exprs.append(dr)
|
531
|
+
this_expr = self._intersection_of(owl_exprs, owl_types=owl_types)
|
532
|
+
self.node_owltypes[this_expr].update(self._get_owltypes(owl_types, owl_exprs))
|
533
|
+
return this_expr
|
534
|
+
|
535
|
+
def add_constraints(
|
536
|
+
self,
|
537
|
+
element: Union[
|
538
|
+
SlotDefinition, AnonymousSlotExpression, TypeDefinition, AnonymousTypeExpression
|
539
|
+
],
|
540
|
+
) -> Tuple[List[BNode], Set[OWL_TYPE]]:
|
541
|
+
owl_types = set()
|
542
|
+
owl_exprs = []
|
543
|
+
graph = self.graph
|
544
|
+
constraints = {
|
545
|
+
XSD.minInclusive: element.minimum_value,
|
546
|
+
XSD.maxInclusive: element.maximum_value,
|
547
|
+
XSD.pattern: element.pattern, # TODO: map between ECMAScript and XSD regular expressions
|
548
|
+
}
|
549
|
+
for constraint_prop, constraint_val in constraints.items():
|
550
|
+
if constraint_val is not None:
|
551
|
+
owl_types.add(RDFS.Literal)
|
552
|
+
dr = BNode()
|
553
|
+
graph.add((dr, RDF.type, RDFS.Datatype))
|
554
|
+
if isinstance(constraint_val, float):
|
555
|
+
graph.add((dr, OWL.onDatatype, XSD.float))
|
556
|
+
elif isinstance(constraint_val, int):
|
557
|
+
graph.add((dr, OWL.onDatatype, XSD.integer))
|
558
|
+
else:
|
559
|
+
graph.add((dr, OWL.onDatatype, XSD.string))
|
560
|
+
listnode = BNode()
|
561
|
+
x = BNode()
|
562
|
+
Collection(graph, listnode, [x])
|
563
|
+
graph.add((dr, OWL.withRestrictions, listnode))
|
564
|
+
graph.add((x, constraint_prop, Literal(constraint_val)))
|
565
|
+
owl_exprs.append(dr)
|
566
|
+
return owl_exprs, owl_types
|
567
|
+
|
568
|
+
def add_slot(self, slot: SlotDefinition, attribute=False) -> None:
|
380
569
|
# determine if this is a slot that has been induced by slot_usage; if so
|
381
570
|
# the meaning of the slot is context-specific and should not be used for
|
382
571
|
# 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
572
|
|
389
|
-
slot_uri = self._prop_uri(slot
|
390
|
-
# logging.error(f'SLOT_URI={slot_uri}')
|
573
|
+
slot_uri = self._prop_uri(slot)
|
391
574
|
|
392
575
|
# Slots may be modeled as Object or Datatype Properties
|
393
576
|
# if type_objects is True, then ALL slots are ObjectProperties
|
@@ -398,53 +581,54 @@ class OwlSchemaGenerator(Generator):
|
|
398
581
|
(
|
399
582
|
slot_uri,
|
400
583
|
RDF.type,
|
401
|
-
|
402
|
-
camelcase("slot definition")
|
403
|
-
],
|
584
|
+
SlotDefinition.class_class_uri,
|
404
585
|
)
|
405
586
|
)
|
406
587
|
|
407
|
-
|
408
|
-
|
409
|
-
for
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
588
|
+
if attribute:
|
589
|
+
n = 0
|
590
|
+
for c in self.schemaview.all_classes().values():
|
591
|
+
for a in c.attributes.values():
|
592
|
+
att_uri = self.schemaview.get_uri(a, native=False, expand=True)
|
593
|
+
if slot_uri == URIRef(att_uri):
|
594
|
+
n += 1
|
595
|
+
if n > 1:
|
596
|
+
logging.warning(f"Ambiguous attribute: {slot.name} {slot_uri}")
|
597
|
+
return
|
417
598
|
|
418
|
-
self.add_mappings(slot)
|
419
599
|
self.add_metadata(slot, slot_uri)
|
420
|
-
self._add_element_properties(slot_uri, slot)
|
421
600
|
|
422
|
-
|
601
|
+
if attribute:
|
602
|
+
return
|
603
|
+
|
604
|
+
range_expr = self.transform_class_slot_expression(None, slot)
|
605
|
+
if range_expr:
|
606
|
+
self.graph.add((slot_uri, RDFS.range, range_expr))
|
423
607
|
if slot.domain:
|
424
608
|
self.graph.add((slot_uri, RDFS.domain, self._class_uri(slot.domain)))
|
425
609
|
if slot.inverse:
|
426
610
|
self.graph.add((slot_uri, OWL.inverseOf, self._prop_uri(slot.inverse)))
|
427
|
-
|
428
|
-
|
611
|
+
characteristics = {
|
612
|
+
"symmetric": OWL.SymmetricProperty,
|
613
|
+
"asymmetric": OWL.AsymmetricProperty,
|
614
|
+
"transitive": OWL.TransitiveProperty,
|
615
|
+
"reflexive": OWL.ReflexiveProperty,
|
616
|
+
"irreflexive": OWL.IrreflexiveProperty,
|
617
|
+
}
|
618
|
+
for metaslot, uri in characteristics.items():
|
619
|
+
if getattr(slot, metaslot, False):
|
620
|
+
self.graph.add((slot_uri, RDF.type, uri))
|
429
621
|
|
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
622
|
if slot.is_a:
|
434
623
|
self.graph.add((slot_uri, RDFS.subPropertyOf, self._prop_uri(slot.is_a)))
|
435
624
|
for mixin in slot.mixins:
|
436
625
|
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
626
|
|
441
|
-
def
|
627
|
+
def add_type(self, typ: TypeDefinition) -> None:
|
442
628
|
type_uri = self._type_uri(typ.name)
|
443
|
-
if not self.type_objects:
|
444
|
-
return False
|
445
629
|
if typ.from_schema == "https://w3id.org/linkml/types":
|
446
630
|
return
|
447
|
-
|
631
|
+
|
448
632
|
if self.metaclasses:
|
449
633
|
self.graph.add(
|
450
634
|
(
|
@@ -455,18 +639,43 @@ class OwlSchemaGenerator(Generator):
|
|
455
639
|
],
|
456
640
|
)
|
457
641
|
)
|
458
|
-
self._add_element_properties(type_uri, typ)
|
459
|
-
if
|
460
|
-
self.graph.add((type_uri,
|
642
|
+
# self._add_element_properties(type_uri, typ)
|
643
|
+
if self.type_objects:
|
644
|
+
self.graph.add((type_uri, RDF.type, OWL.Class))
|
645
|
+
if typ.typeof:
|
646
|
+
self.graph.add((type_uri, RDFS.subClassOf, self._type_uri(typ.typeof)))
|
647
|
+
else:
|
648
|
+
if not self.top_value_uri:
|
649
|
+
self.top_value_uri = self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][
|
650
|
+
"topValue"
|
651
|
+
]
|
652
|
+
self.graph.add((self.top_value_uri, RDF.type, OWL.DatatypeProperty))
|
653
|
+
self.graph.add((self.top_value_uri, RDFS.label, Literal("value")))
|
654
|
+
restr = BNode()
|
655
|
+
self.graph.add((restr, RDF.type, OWL.Restriction))
|
656
|
+
self.graph.add((restr, OWL.qualifiedCardinality, Literal(1)))
|
657
|
+
self.graph.add((restr, OWL.onProperty, self.top_value_uri))
|
658
|
+
self.graph.add((restr, OWL.onDataRange, self._type_uri(typ.name)))
|
659
|
+
self.graph.add((type_uri, RDFS.subClassOf, restr))
|
461
660
|
else:
|
462
|
-
|
463
|
-
|
464
|
-
self.
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
661
|
+
self.graph.add((type_uri, RDF.type, RDFS.Datatype))
|
662
|
+
eq_conjunctions = []
|
663
|
+
if typ.typeof and type_uri != self._type_uri(typ.typeof):
|
664
|
+
# self.graph.add((type_uri, OWL.equivalentClass, self._type_uri(typ.typeof)))
|
665
|
+
eq_conjunctions.append(self._type_uri(typ.typeof))
|
666
|
+
# self.graph.add((type_uri, RDFS.subClassOf, self._type_uri(typ.typeof)))
|
667
|
+
if typ.uri and type_uri != URIRef(self.schemaview.expand_curie(typ.uri)):
|
668
|
+
eq_conjunctions.append(URIRef(self.schemaview.expand_curie(typ.uri)))
|
669
|
+
# self.graph.add(
|
670
|
+
# (type_uri, OWL.equivalentClass, URIRef(self.schemaview.expand_curie(typ.uri)))
|
671
|
+
# )
|
672
|
+
constraints_exprs, _ = self.add_constraints(typ)
|
673
|
+
eq_conjunctions.extend(constraints_exprs)
|
674
|
+
ixn = self._intersection_of(eq_conjunctions)
|
675
|
+
if ixn:
|
676
|
+
self.graph.add((type_uri, OWL.equivalentClass, ixn))
|
677
|
+
|
678
|
+
def add_enum(self, e: EnumDefinition) -> None:
|
470
679
|
g = self.graph
|
471
680
|
enum_uri = self._enum_uri(e.name)
|
472
681
|
g.add((enum_uri, RDF.type, OWL.Class))
|
@@ -480,15 +689,12 @@ class OwlSchemaGenerator(Generator):
|
|
480
689
|
],
|
481
690
|
)
|
482
691
|
)
|
483
|
-
self._add_element_properties(enum_uri, e)
|
484
692
|
pv_uris = []
|
485
693
|
for pv in e.permissible_values.values():
|
486
694
|
if pv.meaning:
|
487
|
-
pv_uri = self.
|
695
|
+
pv_uri = URIRef(self.schemaview.expand_curie(pv.meaning))
|
488
696
|
else:
|
489
697
|
pv_uri = enum_uri + "#" + pv.text.replace(" ", "+")
|
490
|
-
# pv_uri = self.namespaces.uri_for(downcase(pv.text))
|
491
|
-
# pv_uri = None
|
492
698
|
pv_uris.append(pv_uri)
|
493
699
|
if pv_uri:
|
494
700
|
g.add((pv_uri, RDF.type, OWL.Class))
|
@@ -500,71 +706,120 @@ class OwlSchemaGenerator(Generator):
|
|
500
706
|
pv_uri,
|
501
707
|
)
|
502
708
|
)
|
503
|
-
self._add_element_properties(pv_uri, pv)
|
709
|
+
# self._add_element_properties(pv_uri, pv)
|
504
710
|
if self.metaclasses:
|
505
711
|
g.add((pv_uri, RDF.type, enum_uri))
|
506
712
|
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
|
-
|
713
|
+
self._union_of(pv_uris, node=enum_uri)
|
714
|
+
|
715
|
+
def has_profile(self, profile: MetadataProfile, default=False) -> bool:
|
716
|
+
"""
|
717
|
+
Determine if a metadata profile is active.
|
718
|
+
|
719
|
+
:param profile: profile to check
|
720
|
+
:param default: True if the configured profiles include the specified profile
|
721
|
+
:return:
|
722
|
+
"""
|
723
|
+
if default and not self.metadata_profile and not self.metadata_profiles:
|
724
|
+
return True
|
725
|
+
return profile in self.metadata_profiles or profile == self.metadata_profile
|
726
|
+
|
727
|
+
def _get_owltypes(
|
728
|
+
self, current: Set[OWL_TYPE], exprs: List[Union[BNode, URIRef]]
|
729
|
+
) -> Set[OWL_TYPE]:
|
730
|
+
"""
|
731
|
+
Gets the OWL types of specified expressions plus current owl types.
|
732
|
+
|
733
|
+
:param current:
|
734
|
+
:param exprs:
|
735
|
+
:return:
|
736
|
+
"""
|
737
|
+
owltypes = set()
|
738
|
+
for x in exprs:
|
739
|
+
x_owltypes = self.node_owltypes.get(x, None)
|
740
|
+
if x_owltypes:
|
741
|
+
owltypes.update(x_owltypes)
|
742
|
+
owltypes.update(current)
|
743
|
+
if len(owltypes) > 1:
|
744
|
+
logging.warning(f"Multiple owl types {owltypes}")
|
745
|
+
# if self.target_profile == OWLProfile.dl:
|
746
|
+
return owltypes
|
747
|
+
|
748
|
+
def _remove_list(self, listnode: BNode) -> None:
|
749
|
+
graph = self.graph
|
750
|
+
triples = list(graph.triples((listnode, None, None)))
|
751
|
+
while triples:
|
752
|
+
t = triples.pop(0)
|
753
|
+
subj, pred, obj = t
|
754
|
+
if pred not in [RDF.first, RDF.rest]:
|
755
|
+
continue
|
756
|
+
graph.remove(t)
|
757
|
+
if isinstance(obj, BNode):
|
758
|
+
triples.extend(graph.triples((obj, None, None)))
|
759
|
+
|
760
|
+
def _some_values_from(self, property: URIRef, filler: Union[URIRef, BNode]) -> BNode:
|
761
|
+
node = BNode()
|
762
|
+
self.graph.add((node, RDF.type, OWL.Restriction))
|
763
|
+
self.graph.add((node, OWL.onProperty, property))
|
764
|
+
self.graph.add((node, OWL.someValuesFrom, filler))
|
765
|
+
return node
|
766
|
+
|
767
|
+
def _complement_of_union_of(
|
768
|
+
self, exprs: List[Union[BNode, URIRef]], **kwargs
|
769
|
+
) -> Optional[Union[BNode, URIRef]]:
|
770
|
+
if not exprs:
|
771
|
+
raise ValueError("Must pass at least one")
|
772
|
+
neg_expr = BNode()
|
773
|
+
self.graph.add((neg_expr, OWL.complementOf, self._union_of(exprs)), **kwargs)
|
774
|
+
return neg_expr
|
775
|
+
|
776
|
+
def _intersection_of(
|
777
|
+
self, exprs: List[Union[BNode, URIRef]], **kwargs
|
778
|
+
) -> Optional[Union[BNode, URIRef]]:
|
779
|
+
return self._boolean_expression(exprs, OWL.intersectionOf, **kwargs)
|
780
|
+
|
781
|
+
def _union_of(
|
782
|
+
self, exprs: List[Union[BNode, URIRef]], **kwargs
|
783
|
+
) -> Optional[Union[BNode, URIRef]]:
|
784
|
+
return self._boolean_expression(exprs, OWL.unionOf, **kwargs)
|
785
|
+
|
786
|
+
def _exactly_one_of(self, exprs: List[Union[BNode, URIRef]]) -> Optional[Union[BNode, URIRef]]:
|
787
|
+
if not exprs:
|
788
|
+
raise ValueError("Must pass at least one")
|
789
|
+
if len(exprs) == 1:
|
790
|
+
return exprs[0]
|
791
|
+
sub_exprs = []
|
792
|
+
for i, x in enumerate(exprs):
|
793
|
+
rest = exprs[0:i] + exprs[i + 1 :]
|
794
|
+
neg_expr = self._complement_of_union_of(rest)
|
795
|
+
sub_exprs.append(self._intersection_of([x, neg_expr]))
|
796
|
+
return self._union_of(sub_exprs)
|
797
|
+
|
798
|
+
def _boolean_expression(
|
799
|
+
self,
|
800
|
+
exprs: List[Union[BNode, URIRef]],
|
801
|
+
predicate: URIRef,
|
802
|
+
node: Optional[URIRef] = None,
|
803
|
+
owl_types: Set[OWL_TYPE] = None,
|
804
|
+
) -> Optional[Union[BNode, URIRef]]:
|
805
|
+
graph = self.graph
|
806
|
+
if len(exprs) == 0:
|
567
807
|
return None
|
808
|
+
elif len(exprs) == 1:
|
809
|
+
return exprs[0]
|
810
|
+
else:
|
811
|
+
if node is None:
|
812
|
+
node = BNode()
|
813
|
+
listnode = BNode()
|
814
|
+
Collection(graph, listnode, exprs)
|
815
|
+
graph.add((node, predicate, listnode))
|
816
|
+
if owl_types is None:
|
817
|
+
owl_types = set()
|
818
|
+
owl_types = owl_types.union(self._get_owltypes(set(), exprs))
|
819
|
+
if len(owl_types) == 1:
|
820
|
+
if RDFS.Literal in owl_types:
|
821
|
+
graph.add((node, RDF.type, RDFS.Datatype))
|
822
|
+
return node
|
568
823
|
|
569
824
|
def _range_is_datatype(self, slot: SlotDefinition) -> bool:
|
570
825
|
if self.type_objects:
|
@@ -585,26 +840,53 @@ class OwlSchemaGenerator(Generator):
|
|
585
840
|
else:
|
586
841
|
return self._class_uri(ClassDefinitionName(slot.range))
|
587
842
|
|
588
|
-
def _class_uri(self, cn: ClassDefinitionName) -> URIRef:
|
589
|
-
c = self.
|
590
|
-
return URIRef(c.
|
843
|
+
def _class_uri(self, cn: Union[str, ClassDefinitionName]) -> URIRef:
|
844
|
+
c = self.schemaview.get_class(cn)
|
845
|
+
return URIRef(self.schemaview.get_uri(c, expand=True, native=self.use_native_uris))
|
591
846
|
|
592
|
-
def _enum_uri(self, en: EnumDefinitionName) -> URIRef:
|
593
|
-
|
847
|
+
def _enum_uri(self, en: Union[str, EnumDefinitionName]) -> URIRef:
|
848
|
+
sv = self.schemaview
|
849
|
+
e = sv.get_enum(en)
|
850
|
+
uri = e.enum_uri
|
851
|
+
if not uri:
|
852
|
+
uri = f"{sv.schema.default_prefix}:{camelcase(en)}"
|
594
853
|
# TODO: allow control over URIs
|
595
|
-
return URIRef(
|
854
|
+
return URIRef(self.schemaview.expand_curie(uri))
|
596
855
|
|
597
|
-
def _prop_uri(self, pn: SlotDefinitionName) -> URIRef:
|
598
|
-
|
599
|
-
|
600
|
-
return self.namespaces.uri_for(p.slot_uri)
|
856
|
+
def _prop_uri(self, pn: Union[SlotDefinition, SlotDefinitionName]) -> URIRef:
|
857
|
+
if isinstance(pn, SlotDefinition):
|
858
|
+
p = pn
|
601
859
|
else:
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
860
|
+
p = self.schemaview.get_slot(pn, attributes=True)
|
861
|
+
if not p:
|
862
|
+
raise ValueError(f"No such slot or attribute: {pn}")
|
863
|
+
try:
|
864
|
+
return URIRef(self.schemaview.get_uri(p, expand=True, native=self.use_native_uris))
|
865
|
+
except (KeyError, ValueError):
|
866
|
+
# TODO: fix this upstream in schemaview
|
867
|
+
default_prefix = self.schemaview.schema.default_prefix or ""
|
868
|
+
return URIRef(self.schemaview.expand_curie(f"{default_prefix}:{underscore(p.name)}"))
|
869
|
+
|
870
|
+
def _type_uri(self, tn: TypeDefinitionName, native: bool = None) -> URIRef:
|
871
|
+
if native is None:
|
872
|
+
# never use native unless type shadowing with objects is enabled
|
873
|
+
native = self.type_objects
|
874
|
+
if native:
|
875
|
+
# UGLY HACK: Currently schemaview does not camelcase types
|
876
|
+
e = self.schemaview.get_element(tn, imports=True)
|
877
|
+
if e.from_schema is not None:
|
878
|
+
schema = next(
|
879
|
+
sc for sc in self.schemaview.schema_map.values() if sc.id == e.from_schema
|
880
|
+
)
|
881
|
+
pfx = schema.default_prefix
|
882
|
+
if pfx == "linkml":
|
883
|
+
return URIRef(self.schemaview.expand_curie(f"{pfx}:{camelcase(tn)}"))
|
884
|
+
t = self.schemaview.get_type(tn)
|
885
|
+
expanded = self.schemaview.get_uri(t, expand=True, native=native)
|
886
|
+
if expanded.startswith("xsd:"):
|
887
|
+
# TODO: fix upstream in schemaview; default_curi_maps is different on windows
|
888
|
+
return XSD[expanded[4:]]
|
889
|
+
return URIRef(expanded)
|
608
890
|
|
609
891
|
def _add_metamodel_class(self, cname: str) -> None:
|
610
892
|
metac = self.metamodel.schema.classes[cname]
|
@@ -613,30 +895,33 @@ class OwlSchemaGenerator(Generator):
|
|
613
895
|
# self._add_element_properties(metac_uri, metac)
|
614
896
|
|
615
897
|
def slot_owl_type(self, slot: SlotDefinition) -> URIRef:
|
898
|
+
sv = self.schemaview
|
899
|
+
if slot.range is None:
|
900
|
+
range = self.schemaview.schema.default_range
|
901
|
+
else:
|
902
|
+
range = slot.range
|
616
903
|
if self.type_objects:
|
617
904
|
return OWL.ObjectProperty
|
618
|
-
|
905
|
+
is_literal_vals = self.slot_is_literal_map[slot.name]
|
906
|
+
if len(is_literal_vals) > 1:
|
907
|
+
logging.warning(f"Ambiguous type for: {slot.name}")
|
908
|
+
if range is None:
|
909
|
+
if not is_literal_vals:
|
910
|
+
logging.warning(f"Guessing type for {slot.name}")
|
911
|
+
return OWL.ObjectProperty
|
912
|
+
if (list(is_literal_vals))[0]:
|
913
|
+
return OWL.DatatypeProperty
|
914
|
+
else:
|
915
|
+
return OWL.ObjectProperty
|
916
|
+
elif range in sv.all_classes():
|
619
917
|
return OWL.ObjectProperty
|
620
|
-
elif
|
918
|
+
elif range in sv.all_enums():
|
621
919
|
return OWL.ObjectProperty
|
622
|
-
elif
|
920
|
+
elif range in sv.all_types():
|
623
921
|
return OWL.DatatypeProperty
|
624
922
|
else:
|
625
923
|
raise Exception(f"Unknown range: {slot.range}")
|
626
924
|
|
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
925
|
|
641
926
|
@shared_arguments(OwlSchemaGenerator)
|
642
927
|
@click.command()
|
@@ -650,13 +935,13 @@ class OwlSchemaGenerator(Generator):
|
|
650
935
|
)
|
651
936
|
@click.option(
|
652
937
|
"--type-objects/--no-type-objects",
|
653
|
-
default=
|
938
|
+
default=False,
|
654
939
|
show_default=True,
|
655
940
|
help="If true, will model linkml types as objects, not literals",
|
656
941
|
)
|
657
942
|
@click.option(
|
658
943
|
"--metaclasses/--no-metaclasses",
|
659
|
-
default=
|
944
|
+
default=False,
|
660
945
|
show_default=True,
|
661
946
|
help="If true, include linkml metamodel classes as metaclasses. Note this introduces punning in OWL-DL",
|
662
947
|
)
|
@@ -695,11 +980,11 @@ def cli(yamlfile, metadata_profile: str, **kwargs):
|
|
695
980
|
For more info, see: https://linkml.io/linkml/generators/owl
|
696
981
|
"""
|
697
982
|
if metadata_profile is not None:
|
698
|
-
|
983
|
+
metadata_profiles = [MetadataProfile(metadata_profile)]
|
699
984
|
else:
|
700
|
-
|
985
|
+
metadata_profiles = [MetadataProfile.linkml]
|
701
986
|
print(
|
702
|
-
OwlSchemaGenerator(yamlfile,
|
987
|
+
OwlSchemaGenerator(yamlfile, metadata_profiles=metadata_profiles, **kwargs).serialize(
|
703
988
|
**kwargs
|
704
989
|
)
|
705
990
|
)
|