rdf-construct 0.2.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.
Files changed (88) hide show
  1. rdf_construct/__init__.py +12 -0
  2. rdf_construct/__main__.py +0 -0
  3. rdf_construct/cli.py +1762 -0
  4. rdf_construct/core/__init__.py +33 -0
  5. rdf_construct/core/config.py +116 -0
  6. rdf_construct/core/ordering.py +219 -0
  7. rdf_construct/core/predicate_order.py +212 -0
  8. rdf_construct/core/profile.py +157 -0
  9. rdf_construct/core/selector.py +64 -0
  10. rdf_construct/core/serialiser.py +232 -0
  11. rdf_construct/core/utils.py +89 -0
  12. rdf_construct/cq/__init__.py +77 -0
  13. rdf_construct/cq/expectations.py +365 -0
  14. rdf_construct/cq/formatters/__init__.py +45 -0
  15. rdf_construct/cq/formatters/json.py +104 -0
  16. rdf_construct/cq/formatters/junit.py +104 -0
  17. rdf_construct/cq/formatters/text.py +146 -0
  18. rdf_construct/cq/loader.py +300 -0
  19. rdf_construct/cq/runner.py +321 -0
  20. rdf_construct/diff/__init__.py +59 -0
  21. rdf_construct/diff/change_types.py +214 -0
  22. rdf_construct/diff/comparator.py +338 -0
  23. rdf_construct/diff/filters.py +133 -0
  24. rdf_construct/diff/formatters/__init__.py +71 -0
  25. rdf_construct/diff/formatters/json.py +192 -0
  26. rdf_construct/diff/formatters/markdown.py +210 -0
  27. rdf_construct/diff/formatters/text.py +195 -0
  28. rdf_construct/docs/__init__.py +60 -0
  29. rdf_construct/docs/config.py +238 -0
  30. rdf_construct/docs/extractors.py +603 -0
  31. rdf_construct/docs/generator.py +360 -0
  32. rdf_construct/docs/renderers/__init__.py +7 -0
  33. rdf_construct/docs/renderers/html.py +803 -0
  34. rdf_construct/docs/renderers/json.py +390 -0
  35. rdf_construct/docs/renderers/markdown.py +628 -0
  36. rdf_construct/docs/search.py +278 -0
  37. rdf_construct/docs/templates/html/base.html.jinja +44 -0
  38. rdf_construct/docs/templates/html/class.html.jinja +152 -0
  39. rdf_construct/docs/templates/html/hierarchy.html.jinja +28 -0
  40. rdf_construct/docs/templates/html/index.html.jinja +110 -0
  41. rdf_construct/docs/templates/html/instance.html.jinja +90 -0
  42. rdf_construct/docs/templates/html/namespaces.html.jinja +37 -0
  43. rdf_construct/docs/templates/html/property.html.jinja +124 -0
  44. rdf_construct/docs/templates/html/single_page.html.jinja +169 -0
  45. rdf_construct/lint/__init__.py +75 -0
  46. rdf_construct/lint/config.py +214 -0
  47. rdf_construct/lint/engine.py +396 -0
  48. rdf_construct/lint/formatters.py +327 -0
  49. rdf_construct/lint/rules.py +692 -0
  50. rdf_construct/main.py +6 -0
  51. rdf_construct/puml2rdf/__init__.py +103 -0
  52. rdf_construct/puml2rdf/config.py +230 -0
  53. rdf_construct/puml2rdf/converter.py +420 -0
  54. rdf_construct/puml2rdf/merger.py +200 -0
  55. rdf_construct/puml2rdf/model.py +202 -0
  56. rdf_construct/puml2rdf/parser.py +565 -0
  57. rdf_construct/puml2rdf/validators.py +451 -0
  58. rdf_construct/shacl/__init__.py +56 -0
  59. rdf_construct/shacl/config.py +166 -0
  60. rdf_construct/shacl/converters.py +520 -0
  61. rdf_construct/shacl/generator.py +364 -0
  62. rdf_construct/shacl/namespaces.py +93 -0
  63. rdf_construct/stats/__init__.py +29 -0
  64. rdf_construct/stats/collector.py +178 -0
  65. rdf_construct/stats/comparator.py +298 -0
  66. rdf_construct/stats/formatters/__init__.py +83 -0
  67. rdf_construct/stats/formatters/json.py +38 -0
  68. rdf_construct/stats/formatters/markdown.py +153 -0
  69. rdf_construct/stats/formatters/text.py +186 -0
  70. rdf_construct/stats/metrics/__init__.py +26 -0
  71. rdf_construct/stats/metrics/basic.py +147 -0
  72. rdf_construct/stats/metrics/complexity.py +137 -0
  73. rdf_construct/stats/metrics/connectivity.py +130 -0
  74. rdf_construct/stats/metrics/documentation.py +128 -0
  75. rdf_construct/stats/metrics/hierarchy.py +207 -0
  76. rdf_construct/stats/metrics/properties.py +88 -0
  77. rdf_construct/uml/__init__.py +22 -0
  78. rdf_construct/uml/context.py +194 -0
  79. rdf_construct/uml/mapper.py +371 -0
  80. rdf_construct/uml/odm_renderer.py +789 -0
  81. rdf_construct/uml/renderer.py +684 -0
  82. rdf_construct/uml/uml_layout.py +393 -0
  83. rdf_construct/uml/uml_style.py +613 -0
  84. rdf_construct-0.2.0.dist-info/METADATA +431 -0
  85. rdf_construct-0.2.0.dist-info/RECORD +88 -0
  86. rdf_construct-0.2.0.dist-info/WHEEL +4 -0
  87. rdf_construct-0.2.0.dist-info/entry_points.txt +3 -0
  88. rdf_construct-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,520 @@
1
+ """OWL to SHACL conversion rules.
2
+
3
+ Each converter handles a specific OWL pattern and produces equivalent SHACL
4
+ constraints. Converters are composable and applied by the generator.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from dataclasses import dataclass, field
9
+ from typing import TYPE_CHECKING
10
+
11
+ from rdflib import BNode, Graph, Literal, Namespace, RDF, RDFS, URIRef, XSD
12
+ from rdflib.namespace import OWL
13
+
14
+ from .namespaces import SH
15
+
16
+ if TYPE_CHECKING:
17
+ from .config import ShaclConfig
18
+
19
+
20
+ @dataclass
21
+ class PropertyConstraint:
22
+ """Represents a property constraint to be added to a shape.
23
+
24
+ Accumulates constraints that will become a sh:property blank node.
25
+
26
+ Attributes:
27
+ path: The property path (usually the property URI).
28
+ node_class: Expected class for object values (sh:class).
29
+ datatype: Expected datatype for literal values (sh:datatype).
30
+ min_count: Minimum cardinality (sh:minCount).
31
+ max_count: Maximum cardinality (sh:maxCount).
32
+ node_kind: Node kind constraint (sh:nodeKind).
33
+ name: Human-readable name (sh:name).
34
+ description: Description (sh:description).
35
+ in_values: Enumeration of allowed values (sh:in).
36
+ pattern: Regex pattern for string values (sh:pattern).
37
+ min_inclusive: Minimum value inclusive (sh:minInclusive).
38
+ max_inclusive: Maximum value inclusive (sh:maxInclusive).
39
+ order: Property display order (sh:order).
40
+ """
41
+
42
+ path: URIRef
43
+ node_class: URIRef | None = None
44
+ datatype: URIRef | None = None
45
+ min_count: int | None = None
46
+ max_count: int | None = None
47
+ node_kind: URIRef | None = None
48
+ name: str | None = None
49
+ description: str | None = None
50
+ in_values: list[URIRef | Literal] = field(default_factory=list)
51
+ pattern: str | None = None
52
+ min_inclusive: Literal | None = None
53
+ max_inclusive: Literal | None = None
54
+ order: int | None = None
55
+
56
+ def merge(self, other: "PropertyConstraint") -> "PropertyConstraint":
57
+ """Merge another constraint into this one.
58
+
59
+ Values from other take precedence for single-value fields.
60
+ Lists are combined.
61
+
62
+ Args:
63
+ other: Constraint to merge from.
64
+
65
+ Returns:
66
+ New merged PropertyConstraint.
67
+ """
68
+ return PropertyConstraint(
69
+ path=self.path,
70
+ node_class=other.node_class or self.node_class,
71
+ datatype=other.datatype or self.datatype,
72
+ min_count=max(
73
+ filter(None, [self.min_count, other.min_count]), default=None
74
+ ),
75
+ max_count=min(
76
+ filter(None, [self.max_count, other.max_count]), default=None
77
+ ) if self.max_count is not None or other.max_count is not None else None,
78
+ node_kind=other.node_kind or self.node_kind,
79
+ name=other.name or self.name,
80
+ description=other.description or self.description,
81
+ in_values=list(set(self.in_values + other.in_values)),
82
+ pattern=other.pattern or self.pattern,
83
+ min_inclusive=other.min_inclusive or self.min_inclusive,
84
+ max_inclusive=other.max_inclusive or self.max_inclusive,
85
+ order=other.order or self.order,
86
+ )
87
+
88
+ def to_rdf(self, shapes_graph: Graph) -> BNode:
89
+ """Convert constraint to RDF representation.
90
+
91
+ Creates a blank node with sh:property predicates.
92
+
93
+ Args:
94
+ shapes_graph: Graph to add triples to.
95
+
96
+ Returns:
97
+ Blank node representing the property shape.
98
+ """
99
+ prop_shape = BNode()
100
+
101
+ shapes_graph.add((prop_shape, SH.path, self.path))
102
+
103
+ if self.node_class:
104
+ shapes_graph.add((prop_shape, SH["class"], self.node_class))
105
+
106
+ if self.datatype:
107
+ shapes_graph.add((prop_shape, SH.datatype, self.datatype))
108
+
109
+ if self.min_count is not None:
110
+ shapes_graph.add((prop_shape, SH.minCount, Literal(self.min_count)))
111
+
112
+ if self.max_count is not None:
113
+ shapes_graph.add((prop_shape, SH.maxCount, Literal(self.max_count)))
114
+
115
+ if self.node_kind:
116
+ shapes_graph.add((prop_shape, SH.nodeKind, self.node_kind))
117
+
118
+ if self.name:
119
+ shapes_graph.add((prop_shape, SH.name, Literal(self.name)))
120
+
121
+ if self.description:
122
+ shapes_graph.add((prop_shape, SH.description, Literal(self.description)))
123
+
124
+ if self.in_values:
125
+ # Create an RDF list for sh:in
126
+ in_list = _create_rdf_list(shapes_graph, self.in_values)
127
+ shapes_graph.add((prop_shape, SH["in"], in_list))
128
+
129
+ if self.pattern:
130
+ shapes_graph.add((prop_shape, SH.pattern, Literal(self.pattern)))
131
+
132
+ if self.min_inclusive is not None:
133
+ shapes_graph.add((prop_shape, SH.minInclusive, self.min_inclusive))
134
+
135
+ if self.max_inclusive is not None:
136
+ shapes_graph.add((prop_shape, SH.maxInclusive, self.max_inclusive))
137
+
138
+ if self.order is not None:
139
+ shapes_graph.add((prop_shape, SH.order, Literal(self.order)))
140
+
141
+ return prop_shape
142
+
143
+
144
+ def _create_rdf_list(graph: Graph, items: list) -> BNode:
145
+ """Create an RDF list from Python list.
146
+
147
+ Args:
148
+ graph: Graph to add list triples to.
149
+ items: Items to include in list.
150
+
151
+ Returns:
152
+ Head node of the RDF list.
153
+ """
154
+ if not items:
155
+ return RDF.nil
156
+
157
+ head = BNode()
158
+ current = head
159
+
160
+ for i, item in enumerate(items):
161
+ graph.add((current, RDF.first, item))
162
+
163
+ if i < len(items) - 1:
164
+ next_node = BNode()
165
+ graph.add((current, RDF.rest, next_node))
166
+ current = next_node
167
+ else:
168
+ graph.add((current, RDF.rest, RDF.nil))
169
+
170
+ return head
171
+
172
+
173
+ class Converter(ABC):
174
+ """Base class for OWL to SHACL converters.
175
+
176
+ Each converter handles a specific OWL pattern and produces
177
+ property constraints or modifies the shape graph directly.
178
+ """
179
+
180
+ @abstractmethod
181
+ def convert_for_class(
182
+ self,
183
+ cls: URIRef,
184
+ source_graph: Graph,
185
+ config: "ShaclConfig",
186
+ ) -> list[PropertyConstraint]:
187
+ """Extract constraints for a given class.
188
+
189
+ Args:
190
+ cls: The class to extract constraints for.
191
+ source_graph: The OWL ontology graph.
192
+ config: Generation configuration.
193
+
194
+ Returns:
195
+ List of property constraints for this class.
196
+ """
197
+ pass
198
+
199
+
200
+ class DomainRangeConverter(Converter):
201
+ """Convert rdfs:domain/range to sh:property with sh:class/sh:datatype.
202
+
203
+ For each property with rdfs:domain pointing to this class, creates
204
+ a property constraint. The rdfs:range determines whether sh:class
205
+ or sh:datatype is used.
206
+ """
207
+
208
+ # Common XSD datatypes
209
+ XSD_DATATYPES = {
210
+ XSD.string, XSD.integer, XSD.int, XSD.long, XSD.short, XSD.byte,
211
+ XSD.decimal, XSD.float, XSD.double, XSD.boolean, XSD.date,
212
+ XSD.dateTime, XSD.time, XSD.duration, XSD.gYear, XSD.gMonth,
213
+ XSD.gDay, XSD.gYearMonth, XSD.gMonthDay, XSD.anyURI, XSD.base64Binary,
214
+ XSD.hexBinary, XSD.normalizedString, XSD.token, XSD.language,
215
+ XSD.nonPositiveInteger, XSD.negativeInteger, XSD.nonNegativeInteger,
216
+ XSD.positiveInteger, XSD.unsignedLong, XSD.unsignedInt,
217
+ XSD.unsignedShort, XSD.unsignedByte,
218
+ }
219
+
220
+ def convert_for_class(
221
+ self,
222
+ cls: URIRef,
223
+ source_graph: Graph,
224
+ config: "ShaclConfig",
225
+ ) -> list[PropertyConstraint]:
226
+ """Find properties with domain of this class and create constraints."""
227
+ constraints: list[PropertyConstraint] = []
228
+
229
+ # Find all properties with this class as domain
230
+ for prop in source_graph.subjects(RDFS.domain, cls):
231
+ if not isinstance(prop, URIRef):
232
+ continue
233
+
234
+ constraint = PropertyConstraint(path=prop)
235
+
236
+ # Get range if defined
237
+ range_value = source_graph.value(prop, RDFS.range)
238
+ if range_value:
239
+ if self._is_datatype(range_value, source_graph):
240
+ constraint.datatype = range_value
241
+ else:
242
+ constraint.node_class = range_value
243
+
244
+ # Add label as name if configured
245
+ if config.include_labels:
246
+ label = source_graph.value(prop, RDFS.label)
247
+ if label:
248
+ constraint.name = str(label)
249
+
250
+ # Add comment as description if configured
251
+ if config.include_descriptions:
252
+ comment = source_graph.value(prop, RDFS.comment)
253
+ if comment:
254
+ constraint.description = str(comment)
255
+
256
+ constraints.append(constraint)
257
+
258
+ return constraints
259
+
260
+ def _is_datatype(self, uri: URIRef, graph: Graph) -> bool:
261
+ """Check if URI represents a datatype."""
262
+ if uri in self.XSD_DATATYPES:
263
+ return True
264
+
265
+ # Check if it's declared as rdfs:Datatype
266
+ if (uri, RDF.type, RDFS.Datatype) in graph:
267
+ return True
268
+
269
+ # Check namespace - XSD URIs are datatypes
270
+ return str(uri).startswith(str(XSD))
271
+
272
+
273
+ class CardinalityConverter(Converter):
274
+ """Convert OWL cardinality restrictions to sh:minCount/sh:maxCount.
275
+
276
+ Handles:
277
+ - owl:cardinality → sh:minCount + sh:maxCount
278
+ - owl:minCardinality → sh:minCount
279
+ - owl:maxCardinality → sh:maxCount
280
+ - owl:qualifiedCardinality (with owl:onClass/owl:onDataRange)
281
+ - owl:someValuesFrom → sh:minCount 1
282
+ """
283
+
284
+ def convert_for_class(
285
+ self,
286
+ cls: URIRef,
287
+ source_graph: Graph,
288
+ config: "ShaclConfig",
289
+ ) -> list[PropertyConstraint]:
290
+ """Extract cardinality restrictions from class definition."""
291
+ constraints: list[PropertyConstraint] = []
292
+
293
+ # Find restrictions that this class is a subclass of
294
+ for superclass in source_graph.objects(cls, RDFS.subClassOf):
295
+ if not isinstance(superclass, BNode):
296
+ continue
297
+
298
+ # Check if it's an owl:Restriction
299
+ if (superclass, RDF.type, OWL.Restriction) not in source_graph:
300
+ continue
301
+
302
+ on_prop = source_graph.value(superclass, OWL.onProperty)
303
+ if not isinstance(on_prop, URIRef):
304
+ continue
305
+
306
+ constraint = PropertyConstraint(path=on_prop)
307
+ has_constraint = False
308
+
309
+ # Exact cardinality
310
+ exact = source_graph.value(superclass, OWL.cardinality)
311
+ if exact:
312
+ constraint.min_count = int(exact)
313
+ constraint.max_count = int(exact)
314
+ has_constraint = True
315
+
316
+ # Minimum cardinality
317
+ min_card = source_graph.value(superclass, OWL.minCardinality)
318
+ if min_card:
319
+ constraint.min_count = int(min_card)
320
+ has_constraint = True
321
+
322
+ # Maximum cardinality
323
+ max_card = source_graph.value(superclass, OWL.maxCardinality)
324
+ if max_card:
325
+ constraint.max_count = int(max_card)
326
+ has_constraint = True
327
+
328
+ # Qualified cardinality
329
+ qual_card = source_graph.value(superclass, OWL.qualifiedCardinality)
330
+ if qual_card:
331
+ constraint.min_count = int(qual_card)
332
+ constraint.max_count = int(qual_card)
333
+ # Also get the qualification
334
+ on_class = source_graph.value(superclass, OWL.onClass)
335
+ if on_class:
336
+ constraint.node_class = on_class
337
+ on_data = source_graph.value(superclass, OWL.onDataRange)
338
+ if on_data:
339
+ constraint.datatype = on_data
340
+ has_constraint = True
341
+
342
+ # Qualified min cardinality
343
+ qual_min = source_graph.value(superclass, OWL.minQualifiedCardinality)
344
+ if qual_min:
345
+ constraint.min_count = int(qual_min)
346
+ on_class = source_graph.value(superclass, OWL.onClass)
347
+ if on_class:
348
+ constraint.node_class = on_class
349
+ has_constraint = True
350
+
351
+ # Qualified max cardinality
352
+ qual_max = source_graph.value(superclass, OWL.maxQualifiedCardinality)
353
+ if qual_max:
354
+ constraint.max_count = int(qual_max)
355
+ on_class = source_graph.value(superclass, OWL.onClass)
356
+ if on_class:
357
+ constraint.node_class = on_class
358
+ has_constraint = True
359
+
360
+ # someValuesFrom implies at least one value
361
+ some_from = source_graph.value(superclass, OWL.someValuesFrom)
362
+ if some_from:
363
+ constraint.min_count = 1
364
+ if isinstance(some_from, URIRef):
365
+ # Could be a class or datatype
366
+ if self._is_datatype(some_from, source_graph):
367
+ constraint.datatype = some_from
368
+ else:
369
+ constraint.node_class = some_from
370
+ has_constraint = True
371
+
372
+ # allValuesFrom constrains the type but not cardinality
373
+ all_from = source_graph.value(superclass, OWL.allValuesFrom)
374
+ if all_from and isinstance(all_from, URIRef):
375
+ if self._is_datatype(all_from, source_graph):
376
+ constraint.datatype = all_from
377
+ else:
378
+ constraint.node_class = all_from
379
+ has_constraint = True
380
+
381
+ if has_constraint:
382
+ constraints.append(constraint)
383
+
384
+ return constraints
385
+
386
+ def _is_datatype(self, uri: URIRef, graph: Graph) -> bool:
387
+ """Check if URI represents a datatype."""
388
+ return str(uri).startswith(str(XSD)) or (uri, RDF.type, RDFS.Datatype) in graph
389
+
390
+
391
+ class FunctionalPropertyConverter(Converter):
392
+ """Convert owl:FunctionalProperty to sh:maxCount 1.
393
+
394
+ Also handles owl:InverseFunctionalProperty (adds maxCount 1 for the property).
395
+ """
396
+
397
+ def convert_for_class(
398
+ self,
399
+ cls: URIRef,
400
+ source_graph: Graph,
401
+ config: "ShaclConfig",
402
+ ) -> list[PropertyConstraint]:
403
+ """Find functional properties with domain of this class."""
404
+ constraints: list[PropertyConstraint] = []
405
+
406
+ # Get all functional properties
407
+ functional_props = set(source_graph.subjects(RDF.type, OWL.FunctionalProperty))
408
+
409
+ # Find properties with this class as domain
410
+ for prop in source_graph.subjects(RDFS.domain, cls):
411
+ if prop in functional_props and isinstance(prop, URIRef):
412
+ constraints.append(PropertyConstraint(path=prop, max_count=1))
413
+
414
+ return constraints
415
+
416
+
417
+ class EnumerationConverter(Converter):
418
+ """Convert owl:oneOf to sh:in constraint.
419
+
420
+ When a property's range is a class defined with owl:oneOf,
421
+ creates a sh:in constraint with the enumerated values.
422
+ """
423
+
424
+ def convert_for_class(
425
+ self,
426
+ cls: URIRef,
427
+ source_graph: Graph,
428
+ config: "ShaclConfig",
429
+ ) -> list[PropertyConstraint]:
430
+ """Find properties with enumerated ranges."""
431
+ constraints: list[PropertyConstraint] = []
432
+
433
+ # Find properties with domain of this class
434
+ for prop in source_graph.subjects(RDFS.domain, cls):
435
+ if not isinstance(prop, URIRef):
436
+ continue
437
+
438
+ range_value = source_graph.value(prop, RDFS.range)
439
+ if not range_value:
440
+ continue
441
+
442
+ # Check if range has owl:oneOf
443
+ one_of = source_graph.value(range_value, OWL.oneOf)
444
+ if one_of:
445
+ values = self._extract_list(source_graph, one_of)
446
+ if values:
447
+ constraints.append(PropertyConstraint(path=prop, in_values=values))
448
+
449
+ return constraints
450
+
451
+ def _extract_list(self, graph: Graph, head: BNode | URIRef) -> list:
452
+ """Extract values from RDF list."""
453
+ values = []
454
+ current = head
455
+
456
+ while current and current != RDF.nil:
457
+ first = graph.value(current, RDF.first)
458
+ if first:
459
+ values.append(first)
460
+ current = graph.value(current, RDF.rest)
461
+
462
+ return values
463
+
464
+
465
+ class SymmetricPropertyConverter(Converter):
466
+ """Handle symmetric properties.
467
+
468
+ For symmetric properties, if domain is defined, the property
469
+ should also be valid in the reverse direction.
470
+ """
471
+
472
+ def convert_for_class(
473
+ self,
474
+ cls: URIRef,
475
+ source_graph: Graph,
476
+ config: "ShaclConfig",
477
+ ) -> list[PropertyConstraint]:
478
+ """Handle symmetric properties - no additional SHACL needed."""
479
+ # Symmetric properties don't need special SHACL handling
480
+ # beyond what domain/range provides
481
+ return []
482
+
483
+
484
+ # All converters in order of application
485
+ ALL_CONVERTERS: list[type[Converter]] = [
486
+ DomainRangeConverter,
487
+ CardinalityConverter,
488
+ FunctionalPropertyConverter,
489
+ EnumerationConverter,
490
+ ]
491
+
492
+
493
+ def get_converters_for_level(level: "StrictnessLevel") -> list[Converter]:
494
+ """Get converter instances appropriate for strictness level.
495
+
496
+ Args:
497
+ level: The strictness level.
498
+
499
+ Returns:
500
+ List of instantiated converters.
501
+ """
502
+ from .config import StrictnessLevel
503
+
504
+ if level == StrictnessLevel.MINIMAL:
505
+ return [DomainRangeConverter()]
506
+
507
+ elif level == StrictnessLevel.STANDARD:
508
+ return [
509
+ DomainRangeConverter(),
510
+ CardinalityConverter(),
511
+ FunctionalPropertyConverter(),
512
+ ]
513
+
514
+ else: # STRICT
515
+ return [
516
+ DomainRangeConverter(),
517
+ CardinalityConverter(),
518
+ FunctionalPropertyConverter(),
519
+ EnumerationConverter(),
520
+ ]