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.
- rdf_construct/__init__.py +12 -0
- rdf_construct/__main__.py +0 -0
- rdf_construct/cli.py +1762 -0
- rdf_construct/core/__init__.py +33 -0
- rdf_construct/core/config.py +116 -0
- rdf_construct/core/ordering.py +219 -0
- rdf_construct/core/predicate_order.py +212 -0
- rdf_construct/core/profile.py +157 -0
- rdf_construct/core/selector.py +64 -0
- rdf_construct/core/serialiser.py +232 -0
- rdf_construct/core/utils.py +89 -0
- rdf_construct/cq/__init__.py +77 -0
- rdf_construct/cq/expectations.py +365 -0
- rdf_construct/cq/formatters/__init__.py +45 -0
- rdf_construct/cq/formatters/json.py +104 -0
- rdf_construct/cq/formatters/junit.py +104 -0
- rdf_construct/cq/formatters/text.py +146 -0
- rdf_construct/cq/loader.py +300 -0
- rdf_construct/cq/runner.py +321 -0
- rdf_construct/diff/__init__.py +59 -0
- rdf_construct/diff/change_types.py +214 -0
- rdf_construct/diff/comparator.py +338 -0
- rdf_construct/diff/filters.py +133 -0
- rdf_construct/diff/formatters/__init__.py +71 -0
- rdf_construct/diff/formatters/json.py +192 -0
- rdf_construct/diff/formatters/markdown.py +210 -0
- rdf_construct/diff/formatters/text.py +195 -0
- rdf_construct/docs/__init__.py +60 -0
- rdf_construct/docs/config.py +238 -0
- rdf_construct/docs/extractors.py +603 -0
- rdf_construct/docs/generator.py +360 -0
- rdf_construct/docs/renderers/__init__.py +7 -0
- rdf_construct/docs/renderers/html.py +803 -0
- rdf_construct/docs/renderers/json.py +390 -0
- rdf_construct/docs/renderers/markdown.py +628 -0
- rdf_construct/docs/search.py +278 -0
- rdf_construct/docs/templates/html/base.html.jinja +44 -0
- rdf_construct/docs/templates/html/class.html.jinja +152 -0
- rdf_construct/docs/templates/html/hierarchy.html.jinja +28 -0
- rdf_construct/docs/templates/html/index.html.jinja +110 -0
- rdf_construct/docs/templates/html/instance.html.jinja +90 -0
- rdf_construct/docs/templates/html/namespaces.html.jinja +37 -0
- rdf_construct/docs/templates/html/property.html.jinja +124 -0
- rdf_construct/docs/templates/html/single_page.html.jinja +169 -0
- rdf_construct/lint/__init__.py +75 -0
- rdf_construct/lint/config.py +214 -0
- rdf_construct/lint/engine.py +396 -0
- rdf_construct/lint/formatters.py +327 -0
- rdf_construct/lint/rules.py +692 -0
- rdf_construct/main.py +6 -0
- rdf_construct/puml2rdf/__init__.py +103 -0
- rdf_construct/puml2rdf/config.py +230 -0
- rdf_construct/puml2rdf/converter.py +420 -0
- rdf_construct/puml2rdf/merger.py +200 -0
- rdf_construct/puml2rdf/model.py +202 -0
- rdf_construct/puml2rdf/parser.py +565 -0
- rdf_construct/puml2rdf/validators.py +451 -0
- rdf_construct/shacl/__init__.py +56 -0
- rdf_construct/shacl/config.py +166 -0
- rdf_construct/shacl/converters.py +520 -0
- rdf_construct/shacl/generator.py +364 -0
- rdf_construct/shacl/namespaces.py +93 -0
- rdf_construct/stats/__init__.py +29 -0
- rdf_construct/stats/collector.py +178 -0
- rdf_construct/stats/comparator.py +298 -0
- rdf_construct/stats/formatters/__init__.py +83 -0
- rdf_construct/stats/formatters/json.py +38 -0
- rdf_construct/stats/formatters/markdown.py +153 -0
- rdf_construct/stats/formatters/text.py +186 -0
- rdf_construct/stats/metrics/__init__.py +26 -0
- rdf_construct/stats/metrics/basic.py +147 -0
- rdf_construct/stats/metrics/complexity.py +137 -0
- rdf_construct/stats/metrics/connectivity.py +130 -0
- rdf_construct/stats/metrics/documentation.py +128 -0
- rdf_construct/stats/metrics/hierarchy.py +207 -0
- rdf_construct/stats/metrics/properties.py +88 -0
- rdf_construct/uml/__init__.py +22 -0
- rdf_construct/uml/context.py +194 -0
- rdf_construct/uml/mapper.py +371 -0
- rdf_construct/uml/odm_renderer.py +789 -0
- rdf_construct/uml/renderer.py +684 -0
- rdf_construct/uml/uml_layout.py +393 -0
- rdf_construct/uml/uml_style.py +613 -0
- rdf_construct-0.2.0.dist-info/METADATA +431 -0
- rdf_construct-0.2.0.dist-info/RECORD +88 -0
- rdf_construct-0.2.0.dist-info/WHEEL +4 -0
- rdf_construct-0.2.0.dist-info/entry_points.txt +3 -0
- rdf_construct-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""JSON documentation renderer for structured data output."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..config import DocsConfig
|
|
11
|
+
from ..extractors import ClassInfo, ExtractedEntities, InstanceInfo, PropertyInfo
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JSONRenderer:
|
|
15
|
+
"""Renders ontology documentation as structured JSON files.
|
|
16
|
+
|
|
17
|
+
Produces machine-readable JSON that can be consumed by custom
|
|
18
|
+
renderers, APIs, or documentation systems.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, config: "DocsConfig") -> None:
|
|
22
|
+
"""Initialise the JSON renderer.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
config: Documentation configuration.
|
|
26
|
+
"""
|
|
27
|
+
self.config = config
|
|
28
|
+
|
|
29
|
+
def _get_output_path(self, filename: str, subdir: str | None = None) -> Path:
|
|
30
|
+
"""Get the full output path for a file.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
filename: Name of the file.
|
|
34
|
+
subdir: Optional subdirectory.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Full output path.
|
|
38
|
+
"""
|
|
39
|
+
if subdir:
|
|
40
|
+
path = self.config.output_dir / subdir / filename
|
|
41
|
+
else:
|
|
42
|
+
path = self.config.output_dir / filename
|
|
43
|
+
|
|
44
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
return path
|
|
46
|
+
|
|
47
|
+
def _write_json(self, path: Path, data: Any) -> Path:
|
|
48
|
+
"""Write JSON data to a file.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
path: Output path.
|
|
52
|
+
data: Data to serialise.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Path to the written file.
|
|
56
|
+
"""
|
|
57
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
59
|
+
json.dump(data, f, indent=2, default=str)
|
|
60
|
+
return path
|
|
61
|
+
|
|
62
|
+
def _class_to_dict(self, class_info: "ClassInfo") -> dict[str, Any]:
|
|
63
|
+
"""Convert a ClassInfo to a dictionary.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
class_info: Class to convert.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Dictionary representation.
|
|
70
|
+
"""
|
|
71
|
+
return {
|
|
72
|
+
"uri": str(class_info.uri),
|
|
73
|
+
"qname": class_info.qname,
|
|
74
|
+
"label": class_info.label,
|
|
75
|
+
"definition": class_info.definition,
|
|
76
|
+
"superclasses": [str(uri) for uri in class_info.superclasses],
|
|
77
|
+
"subclasses": [str(uri) for uri in class_info.subclasses],
|
|
78
|
+
"domain_of": [self._property_to_dict(p) for p in class_info.domain_of],
|
|
79
|
+
"range_of": [self._property_to_dict(p) for p in class_info.range_of],
|
|
80
|
+
"instances": [str(uri) for uri in class_info.instances],
|
|
81
|
+
"disjoint_with": [str(uri) for uri in class_info.disjoint_with],
|
|
82
|
+
"equivalent_to": [str(uri) for uri in class_info.equivalent_to],
|
|
83
|
+
"annotations": class_info.annotations,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def _property_to_dict(self, prop_info: "PropertyInfo") -> dict[str, Any]:
|
|
87
|
+
"""Convert a PropertyInfo to a dictionary.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
prop_info: Property to convert.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dictionary representation.
|
|
94
|
+
"""
|
|
95
|
+
return {
|
|
96
|
+
"uri": str(prop_info.uri),
|
|
97
|
+
"qname": prop_info.qname,
|
|
98
|
+
"label": prop_info.label,
|
|
99
|
+
"definition": prop_info.definition,
|
|
100
|
+
"property_type": prop_info.property_type,
|
|
101
|
+
"domain": [str(uri) for uri in prop_info.domain],
|
|
102
|
+
"range": [str(uri) for uri in prop_info.range],
|
|
103
|
+
"superproperties": [str(uri) for uri in prop_info.superproperties],
|
|
104
|
+
"subproperties": [str(uri) for uri in prop_info.subproperties],
|
|
105
|
+
"is_functional": prop_info.is_functional,
|
|
106
|
+
"is_inverse_functional": prop_info.is_inverse_functional,
|
|
107
|
+
"inverse_of": str(prop_info.inverse_of) if prop_info.inverse_of else None,
|
|
108
|
+
"annotations": prop_info.annotations,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def _instance_to_dict(self, instance_info: "InstanceInfo") -> dict[str, Any]:
|
|
112
|
+
"""Convert an InstanceInfo to a dictionary.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
instance_info: Instance to convert.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Dictionary representation.
|
|
119
|
+
"""
|
|
120
|
+
# Convert properties to serialisable format
|
|
121
|
+
properties: dict[str, list[str]] = {}
|
|
122
|
+
for pred, values in instance_info.properties.items():
|
|
123
|
+
pred_str = str(pred)
|
|
124
|
+
properties[pred_str] = [str(v) for v in values]
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
"uri": str(instance_info.uri),
|
|
128
|
+
"qname": instance_info.qname,
|
|
129
|
+
"label": instance_info.label,
|
|
130
|
+
"definition": instance_info.definition,
|
|
131
|
+
"types": [str(uri) for uri in instance_info.types],
|
|
132
|
+
"properties": properties,
|
|
133
|
+
"annotations": instance_info.annotations,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def _ontology_to_dict(self, entities: "ExtractedEntities") -> dict[str, Any]:
|
|
137
|
+
"""Convert ontology info to a dictionary.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
entities: All extracted entities.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Dictionary representation.
|
|
144
|
+
"""
|
|
145
|
+
onto = entities.ontology
|
|
146
|
+
return {
|
|
147
|
+
"uri": str(onto.uri) if onto.uri else None,
|
|
148
|
+
"title": onto.title,
|
|
149
|
+
"description": onto.description,
|
|
150
|
+
"version": onto.version,
|
|
151
|
+
"creators": onto.creators,
|
|
152
|
+
"contributors": onto.contributors,
|
|
153
|
+
"imports": [str(uri) for uri in onto.imports],
|
|
154
|
+
"namespaces": onto.namespaces,
|
|
155
|
+
"annotations": onto.annotations,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
def render_index(self, entities: "ExtractedEntities") -> Path:
|
|
159
|
+
"""Render the main index as JSON.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
entities: All extracted entities.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Path to the rendered file.
|
|
166
|
+
"""
|
|
167
|
+
data = {
|
|
168
|
+
"ontology": self._ontology_to_dict(entities),
|
|
169
|
+
"statistics": {
|
|
170
|
+
"classes": len(entities.classes),
|
|
171
|
+
"object_properties": len(entities.object_properties),
|
|
172
|
+
"datatype_properties": len(entities.datatype_properties),
|
|
173
|
+
"annotation_properties": len(entities.annotation_properties),
|
|
174
|
+
"instances": len(entities.instances),
|
|
175
|
+
},
|
|
176
|
+
"classes": [
|
|
177
|
+
{
|
|
178
|
+
"uri": str(c.uri),
|
|
179
|
+
"qname": c.qname,
|
|
180
|
+
"label": c.label,
|
|
181
|
+
}
|
|
182
|
+
for c in entities.classes
|
|
183
|
+
],
|
|
184
|
+
"object_properties": [
|
|
185
|
+
{
|
|
186
|
+
"uri": str(p.uri),
|
|
187
|
+
"qname": p.qname,
|
|
188
|
+
"label": p.label,
|
|
189
|
+
}
|
|
190
|
+
for p in entities.object_properties
|
|
191
|
+
],
|
|
192
|
+
"datatype_properties": [
|
|
193
|
+
{
|
|
194
|
+
"uri": str(p.uri),
|
|
195
|
+
"qname": p.qname,
|
|
196
|
+
"label": p.label,
|
|
197
|
+
}
|
|
198
|
+
for p in entities.datatype_properties
|
|
199
|
+
],
|
|
200
|
+
"annotation_properties": [
|
|
201
|
+
{
|
|
202
|
+
"uri": str(p.uri),
|
|
203
|
+
"qname": p.qname,
|
|
204
|
+
"label": p.label,
|
|
205
|
+
}
|
|
206
|
+
for p in entities.annotation_properties
|
|
207
|
+
],
|
|
208
|
+
"instances": [
|
|
209
|
+
{
|
|
210
|
+
"uri": str(i.uri),
|
|
211
|
+
"qname": i.qname,
|
|
212
|
+
"label": i.label,
|
|
213
|
+
}
|
|
214
|
+
for i in entities.instances
|
|
215
|
+
],
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return self._write_json(self._get_output_path("index.json"), data)
|
|
219
|
+
|
|
220
|
+
def render_hierarchy(self, entities: "ExtractedEntities") -> Path:
|
|
221
|
+
"""Render the class hierarchy as JSON.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
entities: All extracted entities.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Path to the rendered file.
|
|
228
|
+
"""
|
|
229
|
+
hierarchy = self._build_hierarchy_tree(entities.classes)
|
|
230
|
+
|
|
231
|
+
def tree_to_json(nodes: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
232
|
+
return [
|
|
233
|
+
{
|
|
234
|
+
"uri": str(node["class"].uri),
|
|
235
|
+
"qname": node["class"].qname,
|
|
236
|
+
"label": node["class"].label,
|
|
237
|
+
"children": tree_to_json(node["children"]),
|
|
238
|
+
}
|
|
239
|
+
for node in nodes
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
data = {"hierarchy": tree_to_json(hierarchy)}
|
|
243
|
+
return self._write_json(self._get_output_path("hierarchy.json"), data)
|
|
244
|
+
|
|
245
|
+
def _build_hierarchy_tree(
|
|
246
|
+
self,
|
|
247
|
+
classes: list["ClassInfo"],
|
|
248
|
+
) -> list[dict[str, Any]]:
|
|
249
|
+
"""Build a tree structure for the class hierarchy.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
classes: List of all classes.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Nested list structure representing the hierarchy.
|
|
256
|
+
"""
|
|
257
|
+
class_by_uri = {str(c.uri): c for c in classes}
|
|
258
|
+
internal_uris = set(class_by_uri.keys())
|
|
259
|
+
root_classes = []
|
|
260
|
+
|
|
261
|
+
for c in classes:
|
|
262
|
+
has_internal_parent = any(
|
|
263
|
+
str(parent) in internal_uris for parent in c.superclasses
|
|
264
|
+
)
|
|
265
|
+
if not has_internal_parent:
|
|
266
|
+
root_classes.append(c)
|
|
267
|
+
|
|
268
|
+
def build_node(class_info: "ClassInfo") -> dict[str, Any]:
|
|
269
|
+
children = []
|
|
270
|
+
for child_uri in class_info.subclasses:
|
|
271
|
+
child_key = str(child_uri)
|
|
272
|
+
if child_key in class_by_uri:
|
|
273
|
+
children.append(build_node(class_by_uri[child_key]))
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
"class": class_info,
|
|
277
|
+
"children": sorted(children, key=lambda n: n["class"].qname),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return sorted(
|
|
281
|
+
[build_node(c) for c in root_classes],
|
|
282
|
+
key=lambda n: n["class"].qname,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def render_class(
|
|
286
|
+
self,
|
|
287
|
+
class_info: "ClassInfo",
|
|
288
|
+
entities: "ExtractedEntities",
|
|
289
|
+
) -> Path:
|
|
290
|
+
"""Render a class as JSON.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
class_info: Class to render.
|
|
294
|
+
entities: All extracted entities.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Path to the rendered file.
|
|
298
|
+
"""
|
|
299
|
+
data = self._class_to_dict(class_info)
|
|
300
|
+
|
|
301
|
+
from ..config import entity_to_path
|
|
302
|
+
rel_path = entity_to_path(class_info.qname, "class", self.config, extension=".json")
|
|
303
|
+
return self._write_json(self.config.output_dir / rel_path, data)
|
|
304
|
+
|
|
305
|
+
def render_property(
|
|
306
|
+
self,
|
|
307
|
+
prop_info: "PropertyInfo",
|
|
308
|
+
entities: "ExtractedEntities",
|
|
309
|
+
) -> Path:
|
|
310
|
+
"""Render a property as JSON.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
prop_info: Property to render.
|
|
314
|
+
entities: All extracted entities.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Path to the rendered file.
|
|
318
|
+
"""
|
|
319
|
+
data = self._property_to_dict(prop_info)
|
|
320
|
+
|
|
321
|
+
entity_type = f"{prop_info.property_type}_property"
|
|
322
|
+
from ..config import entity_to_path
|
|
323
|
+
rel_path = entity_to_path(prop_info.qname, entity_type, self.config, extension=".json")
|
|
324
|
+
return self._write_json(self.config.output_dir / rel_path, data)
|
|
325
|
+
|
|
326
|
+
def render_instance(
|
|
327
|
+
self,
|
|
328
|
+
instance_info: "InstanceInfo",
|
|
329
|
+
entities: "ExtractedEntities",
|
|
330
|
+
) -> Path:
|
|
331
|
+
"""Render an instance as JSON.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
instance_info: Instance to render.
|
|
335
|
+
entities: All extracted entities.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Path to the rendered file.
|
|
339
|
+
"""
|
|
340
|
+
data = self._instance_to_dict(instance_info)
|
|
341
|
+
|
|
342
|
+
from ..config import entity_to_path
|
|
343
|
+
rel_path = entity_to_path(instance_info.qname, "instance", self.config, extension=".json")
|
|
344
|
+
return self._write_json(self.config.output_dir / rel_path, data)
|
|
345
|
+
|
|
346
|
+
def render_namespaces(self, entities: "ExtractedEntities") -> Path:
|
|
347
|
+
"""Render namespaces as JSON.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
entities: All extracted entities.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
Path to the rendered file.
|
|
354
|
+
"""
|
|
355
|
+
data = {
|
|
356
|
+
"namespaces": entities.ontology.namespaces,
|
|
357
|
+
}
|
|
358
|
+
return self._write_json(self._get_output_path("namespaces.json"), data)
|
|
359
|
+
|
|
360
|
+
def render_single_page(self, entities: "ExtractedEntities") -> Path:
|
|
361
|
+
"""Render all documentation as a single JSON file.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
entities: All extracted entities.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Path to the rendered file.
|
|
368
|
+
"""
|
|
369
|
+
data = {
|
|
370
|
+
"ontology": self._ontology_to_dict(entities),
|
|
371
|
+
"classes": [self._class_to_dict(c) for c in entities.classes],
|
|
372
|
+
"object_properties": [
|
|
373
|
+
self._property_to_dict(p) for p in entities.object_properties
|
|
374
|
+
],
|
|
375
|
+
"datatype_properties": [
|
|
376
|
+
self._property_to_dict(p) for p in entities.datatype_properties
|
|
377
|
+
],
|
|
378
|
+
"annotation_properties": [
|
|
379
|
+
self._property_to_dict(p) for p in entities.annotation_properties
|
|
380
|
+
],
|
|
381
|
+
"instances": [
|
|
382
|
+
self._instance_to_dict(i) for i in entities.instances
|
|
383
|
+
],
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return self._write_json(self._get_output_path("ontology.json"), data)
|
|
387
|
+
|
|
388
|
+
def copy_assets(self) -> None:
|
|
389
|
+
"""Copy static assets. No assets needed for JSON."""
|
|
390
|
+
pass
|