@glossarist/concept-browser 0.4.11 → 0.4.14
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.
- package/package.json +2 -2
- package/scripts/generate-ontology-schema.mjs +312 -10
- package/src/adapters/ontology-schema.ts +89 -4
- package/src/components/AppSidebar.vue +281 -43
- package/src/components/ConceptDetail.vue +17 -9
- package/src/components/ConceptRdfView.vue +3 -3
- package/src/components/GraphPanel.vue +19 -0
- package/src/composables/use-ontology-nav.ts +183 -13
- package/src/config/types.ts +1 -0
- package/src/config/use-site-config.ts +1 -1
- package/src/data/ontology-schema.json +1721 -153
- package/src/router/index.ts +10 -0
- package/src/views/AboutView.vue +1 -1
- package/src/views/ContributorsView.vue +1 -1
- package/src/views/NewsView.vue +1 -1
- package/src/views/OntologySchemaView.vue +331 -14
- package/src/views/PageView.vue +1 -1
- package/src/views/ResolveView.vue +1 -1
- package/src/views/StatsView.vue +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.14",
|
|
4
4
|
"description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -78,4 +78,4 @@
|
|
|
78
78
|
"postcss.config.js",
|
|
79
79
|
"env.d.ts"
|
|
80
80
|
]
|
|
81
|
-
}
|
|
81
|
+
}
|
|
@@ -13,7 +13,9 @@ import { fileURLToPath } from 'url';
|
|
|
13
13
|
|
|
14
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
const ROOT = resolve(__dirname, '..');
|
|
16
|
-
const
|
|
16
|
+
const CONCEPT_MODEL = resolve(ROOT, '..', 'concept-model', 'ontologies');
|
|
17
|
+
const ONTOLOGY_TTL = resolve(CONCEPT_MODEL, 'glossarist.ttl');
|
|
18
|
+
const SHACL_TTL = resolve(CONCEPT_MODEL, 'shapes', 'glossarist.shacl.ttl');
|
|
17
19
|
const OUTPUT = resolve(ROOT, 'src', 'data', 'ontology-schema.json');
|
|
18
20
|
|
|
19
21
|
const KNOWN_PREFIXES = {
|
|
@@ -27,6 +29,8 @@ const KNOWN_PREFIXES = {
|
|
|
27
29
|
dcterms: 'http://purl.org/dc/terms/',
|
|
28
30
|
prov: 'http://www.w3.org/ns/prov#',
|
|
29
31
|
xsd: 'http://www.w3.org/2001/XMLSchema#',
|
|
32
|
+
sh: 'http://www.w3.org/ns/shacl#',
|
|
33
|
+
vann: 'http://purl.org/vocab/vann/',
|
|
30
34
|
};
|
|
31
35
|
|
|
32
36
|
function expandPrefixed(term) {
|
|
@@ -49,12 +53,14 @@ function compactIri(iri) {
|
|
|
49
53
|
|
|
50
54
|
/**
|
|
51
55
|
* Minimal TTL subject-block splitter. Handles nested [] and () and quoted strings.
|
|
56
|
+
* Tracks <...> URI references to avoid splitting on dots inside URIs.
|
|
52
57
|
*/
|
|
53
58
|
function splitSubjectBlocks(text) {
|
|
54
59
|
const blocks = [];
|
|
55
60
|
let depth = 0;
|
|
56
61
|
let start = -1;
|
|
57
62
|
let inTripleQuote = false;
|
|
63
|
+
let inUri = false;
|
|
58
64
|
|
|
59
65
|
for (let i = 0; i < text.length; i++) {
|
|
60
66
|
const ch = text[i];
|
|
@@ -82,10 +88,20 @@ function splitSubjectBlocks(text) {
|
|
|
82
88
|
continue;
|
|
83
89
|
}
|
|
84
90
|
|
|
91
|
+
if (inUri) {
|
|
92
|
+
if (ch === '>') inUri = false;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (ch === '<') {
|
|
96
|
+
inUri = true;
|
|
97
|
+
if (start < 0) start = i;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
85
101
|
if (ch === '[' || ch === '(') depth++;
|
|
86
102
|
if (ch === ']' || ch === ')') depth--;
|
|
87
103
|
|
|
88
|
-
if (depth === 0 && ch === '.') {
|
|
104
|
+
if (!inUri && depth === 0 && ch === '.') {
|
|
89
105
|
if (start >= 0) {
|
|
90
106
|
blocks.push(text.slice(start, i));
|
|
91
107
|
start = -1;
|
|
@@ -141,10 +157,81 @@ function extractAllResources(block, predicate) {
|
|
|
141
157
|
return [...new Set(results)];
|
|
142
158
|
}
|
|
143
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Strip @prefix and @base lines. Strip comments (#...) but preserve # inside <...> URIs
|
|
162
|
+
* and inside quoted strings.
|
|
163
|
+
*/
|
|
164
|
+
function preprocessTtl(ttlText) {
|
|
165
|
+
const lines = ttlText.split('\n');
|
|
166
|
+
const filtered = lines.filter(l => !l.trimStart().startsWith('@prefix') && !l.trimStart().startsWith('@base'));
|
|
167
|
+
const text = filtered.join('\n');
|
|
168
|
+
|
|
169
|
+
// Character-by-character comment stripping
|
|
170
|
+
let result = '';
|
|
171
|
+
let inTriple = false;
|
|
172
|
+
let inSingle = false;
|
|
173
|
+
let inUri = false;
|
|
174
|
+
|
|
175
|
+
for (let i = 0; i < text.length; i++) {
|
|
176
|
+
const ch = text[i];
|
|
177
|
+
|
|
178
|
+
if (inTriple) {
|
|
179
|
+
result += ch;
|
|
180
|
+
if (ch === '"' && text.slice(i, i + 3) === '"""') {
|
|
181
|
+
result += text.slice(i + 1, i + 3);
|
|
182
|
+
i += 2;
|
|
183
|
+
inTriple = false;
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (inSingle) {
|
|
189
|
+
result += ch;
|
|
190
|
+
if (ch === '\\') { result += text[i + 1]; i++; continue; }
|
|
191
|
+
if (ch === '"') inSingle = false;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (inUri) {
|
|
196
|
+
result += ch;
|
|
197
|
+
if (ch === '>') inUri = false;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (ch === '"' && text.slice(i, i + 3) === '"""') {
|
|
202
|
+
result += '"""';
|
|
203
|
+
inTriple = true;
|
|
204
|
+
i += 2;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (ch === '"') {
|
|
209
|
+
result += ch;
|
|
210
|
+
inSingle = true;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (ch === '<') {
|
|
215
|
+
result += ch;
|
|
216
|
+
inUri = true;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (ch === '#') {
|
|
221
|
+
// Skip until end of line
|
|
222
|
+
while (i < text.length && text[i] !== '\n') i++;
|
|
223
|
+
if (i < text.length) result += '\n';
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
result += ch;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
|
|
144
233
|
function parseOntology(ttlText) {
|
|
145
|
-
const
|
|
146
|
-
// Remove comment lines but keep content
|
|
147
|
-
const cleaned = rawLines.map(l => l.replace(/#[^\n]*/g, '')).join('\n');
|
|
234
|
+
const cleaned = preprocessTtl(ttlText);
|
|
148
235
|
|
|
149
236
|
const blocks = splitSubjectBlocks(cleaned);
|
|
150
237
|
|
|
@@ -160,8 +247,7 @@ function parseOntology(ttlText) {
|
|
|
160
247
|
if (!subjectMatch) continue;
|
|
161
248
|
const subject = subjectMatch[1];
|
|
162
249
|
|
|
163
|
-
// Skip ontology declaration
|
|
164
|
-
if (subject === '@prefix' || subject.startsWith('@')) continue;
|
|
250
|
+
// Skip ontology declaration
|
|
165
251
|
if (subject.includes('glossarist>') && !subject.startsWith('gloss:')) continue;
|
|
166
252
|
|
|
167
253
|
// Determine type
|
|
@@ -278,6 +364,176 @@ function groupPropertiesByDomain(properties) {
|
|
|
278
364
|
return groups;
|
|
279
365
|
}
|
|
280
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Extract individual sh:property [...] constraint blocks from a shape block.
|
|
369
|
+
* Returns an array of strings, each being the inner content of one [ ... ].
|
|
370
|
+
*/
|
|
371
|
+
function extractPropertyBlocks(shapeBlock) {
|
|
372
|
+
const blocks = [];
|
|
373
|
+
const re = /sh:property\s+\[/g;
|
|
374
|
+
let match;
|
|
375
|
+
while ((match = re.exec(shapeBlock)) !== null) {
|
|
376
|
+
const start = match.index + match[0].length - 1; // position of '['
|
|
377
|
+
let depth = 1;
|
|
378
|
+
let i = start + 1;
|
|
379
|
+
while (i < shapeBlock.length && depth > 0) {
|
|
380
|
+
if (shapeBlock[i] === '[') depth++;
|
|
381
|
+
else if (shapeBlock[i] === ']') depth--;
|
|
382
|
+
i++;
|
|
383
|
+
}
|
|
384
|
+
blocks.push(shapeBlock.slice(start + 1, i - 1));
|
|
385
|
+
}
|
|
386
|
+
return blocks;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function parseConstraint(propBlock) {
|
|
390
|
+
const c = {};
|
|
391
|
+
const path = extractResource(propBlock, 'sh:path');
|
|
392
|
+
c.path = path ? compactIri(expandPrefixed(path)) : null;
|
|
393
|
+
|
|
394
|
+
const dt = extractResource(propBlock, 'sh:datatype');
|
|
395
|
+
c.datatype = dt ? compactIri(expandPrefixed(dt)) : null;
|
|
396
|
+
|
|
397
|
+
const cls = extractResource(propBlock, 'sh:class');
|
|
398
|
+
c.class = cls ? compactIri(expandPrefixed(cls)) : null;
|
|
399
|
+
|
|
400
|
+
const vf = extractResource(propBlock, 'sh:valuesFrom');
|
|
401
|
+
c.valuesFrom = vf ? compactIri(expandPrefixed(vf)) : null;
|
|
402
|
+
|
|
403
|
+
const nk = extractResource(propBlock, 'sh:nodeKind');
|
|
404
|
+
c.nodeKind = nk ? compactIri(expandPrefixed(nk)) : null;
|
|
405
|
+
|
|
406
|
+
const minMatch = propBlock.match(/sh:minCount\s+(\d+)/);
|
|
407
|
+
c.minCount = minMatch ? parseInt(minMatch[1], 10) : null;
|
|
408
|
+
|
|
409
|
+
const maxMatch = propBlock.match(/sh:maxCount\s+(\d+)/);
|
|
410
|
+
c.maxCount = maxMatch ? parseInt(maxMatch[1], 10) : null;
|
|
411
|
+
|
|
412
|
+
// sh:in ( "val1" "val2" ... )
|
|
413
|
+
const inMatch = propBlock.match(/sh:in\s*\(([^)]+)\)/);
|
|
414
|
+
if (inMatch) {
|
|
415
|
+
c.in = inMatch[1].match(/"([^"]+)"/g)?.map(s => s.replace(/"/g, '')) || null;
|
|
416
|
+
} else {
|
|
417
|
+
c.in = null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return c;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function parseShaclShapes(ttlText) {
|
|
424
|
+
const cleaned = preprocessTtl(ttlText);
|
|
425
|
+
const blocks = splitSubjectBlocks(cleaned);
|
|
426
|
+
|
|
427
|
+
const shapes = [];
|
|
428
|
+
for (const block of blocks) {
|
|
429
|
+
const trimmed = block.trim();
|
|
430
|
+
if (!trimmed) continue;
|
|
431
|
+
|
|
432
|
+
const subjectMatch = trimmed.match(/^([^\s]+)/);
|
|
433
|
+
if (!subjectMatch) continue;
|
|
434
|
+
const subject = subjectMatch[1];
|
|
435
|
+
|
|
436
|
+
// Check if this is a sh:NodeShape
|
|
437
|
+
if (!/\ba\s+sh:NodeShape\b/.test(trimmed)) continue;
|
|
438
|
+
|
|
439
|
+
const iri = expandPrefixed(subject);
|
|
440
|
+
const compact = compactIri(iri);
|
|
441
|
+
const label = extractLiteral(trimmed, 'rdfs:label');
|
|
442
|
+
const comment = extractLiteral(trimmed, 'rdfs:comment');
|
|
443
|
+
const targetClass = extractResource(trimmed, 'sh:targetClass');
|
|
444
|
+
|
|
445
|
+
const propBlocks = extractPropertyBlocks(trimmed);
|
|
446
|
+
const constraints = propBlocks.map(parseConstraint).filter(c => c.path);
|
|
447
|
+
|
|
448
|
+
// sh:class at shape level (not inside sh:property blocks)
|
|
449
|
+
const shapeClassMatch = trimmed.match(/^\s*[^[]*?\ba\s+sh:NodeShape\s[^[]*?sh:class\s+([^\s,;]+)/);
|
|
450
|
+
const shapeClass = shapeClassMatch ? shapeClassMatch[1].replace(/[;.]+$/, '') : null;
|
|
451
|
+
|
|
452
|
+
shapes.push({
|
|
453
|
+
iri,
|
|
454
|
+
compact,
|
|
455
|
+
label: label || subject.replace('gloss:', '').replace('Shape', ''),
|
|
456
|
+
comment,
|
|
457
|
+
targetClass: targetClass ? compactIri(expandPrefixed(targetClass)) : null,
|
|
458
|
+
shapeClass: shapeClass ? compactIri(expandPrefixed(shapeClass)) : null,
|
|
459
|
+
constraints,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return shapes;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const IMPORT_LABELS = {
|
|
467
|
+
'http://www.w3.org/2004/02/skos/core': 'SKOS',
|
|
468
|
+
'http://www.w3.org/2004/02/skos/core#': 'SKOS',
|
|
469
|
+
'http://www.w3.org/2008/05/skos-xl': 'SKOS-XL',
|
|
470
|
+
'http://www.w3.org/2008/05/skos-xl#': 'SKOS-XL',
|
|
471
|
+
'http://purl.org/iso25964/skos-thes': 'ISO 25964',
|
|
472
|
+
'http://purl.org/iso25964/skos-thes#': 'ISO 25964',
|
|
473
|
+
'http://www.w3.org/ns/prov#': 'PROV-O',
|
|
474
|
+
'http://purl.org/dc/terms/': 'Dublin Core Terms',
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
function parseOntologyDeclaration(ttlText) {
|
|
478
|
+
const cleaned = preprocessTtl(ttlText);
|
|
479
|
+
const blocks = splitSubjectBlocks(cleaned);
|
|
480
|
+
|
|
481
|
+
for (const block of blocks) {
|
|
482
|
+
const trimmed = block.trim();
|
|
483
|
+
if (!trimmed) continue;
|
|
484
|
+
|
|
485
|
+
// Match the owl:Ontology declaration
|
|
486
|
+
if (!/\ba\s+owl:Ontology\b/.test(trimmed)) continue;
|
|
487
|
+
|
|
488
|
+
const subjectMatch = trimmed.match(/^<([^>]+)>/);
|
|
489
|
+
const iri = subjectMatch ? subjectMatch[1] : null;
|
|
490
|
+
const label = extractLiteral(trimmed, 'rdfs:label') || extractLiteral(trimmed, 'dcterms:title');
|
|
491
|
+
const comment = extractLiteral(trimmed, 'rdfs:comment') || extractLiteral(trimmed, 'dcterms:description');
|
|
492
|
+
const prefix = extractLiteral(trimmed, 'vann:preferredNamespacePrefix');
|
|
493
|
+
const nsUri = extractLiteral(trimmed, 'vann:preferredNamespaceUri');
|
|
494
|
+
const license = extractResource(trimmed, 'dcterms:license')?.replace(/[<>]/g, '') || null;
|
|
495
|
+
const created = extractLiteral(trimmed, 'dcterms:created');
|
|
496
|
+
|
|
497
|
+
const imports = [];
|
|
498
|
+
const importRe = /owl:imports\s+<([^>]+)>/g;
|
|
499
|
+
let im;
|
|
500
|
+
while ((im = importRe.exec(trimmed)) !== null) {
|
|
501
|
+
const iri = im[1];
|
|
502
|
+
imports.push({
|
|
503
|
+
iri,
|
|
504
|
+
label: IMPORT_LABELS[iri] || IMPORT_LABELS[iri.replace(/#?$/, '#')] || compactIri(iri),
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return { iri, label, comment, prefix, namespaceUri: nsUri, imports, license, created };
|
|
509
|
+
}
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function parseAnnotationProperties(ttlText) {
|
|
514
|
+
const cleaned = preprocessTtl(ttlText);
|
|
515
|
+
const blocks = splitSubjectBlocks(cleaned);
|
|
516
|
+
|
|
517
|
+
const props = [];
|
|
518
|
+
for (const block of blocks) {
|
|
519
|
+
const trimmed = block.trim();
|
|
520
|
+
if (!trimmed) continue;
|
|
521
|
+
|
|
522
|
+
if (!/\ba\s+owl:AnnotationProperty\b/.test(trimmed)) continue;
|
|
523
|
+
|
|
524
|
+
const subjectMatch = trimmed.match(/^([^\s]+)/);
|
|
525
|
+
if (!subjectMatch) continue;
|
|
526
|
+
const subject = subjectMatch[1];
|
|
527
|
+
const iri = expandPrefixed(subject);
|
|
528
|
+
const compact = compactIri(iri);
|
|
529
|
+
const label = extractLiteral(trimmed, 'rdfs:label');
|
|
530
|
+
|
|
531
|
+
props.push({ iri, compact, label: label || compact });
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return props;
|
|
535
|
+
}
|
|
536
|
+
|
|
281
537
|
function main() {
|
|
282
538
|
if (!existsSync(ONTOLOGY_TTL)) {
|
|
283
539
|
console.error(`Ontology file not found: ${ONTOLOGY_TTL}`);
|
|
@@ -291,24 +547,70 @@ function main() {
|
|
|
291
547
|
const hierarchy = buildClassHierarchy(classes);
|
|
292
548
|
const propsByDomain = groupPropertiesByDomain(properties);
|
|
293
549
|
|
|
550
|
+
let shapes = [];
|
|
551
|
+
if (existsSync(SHACL_TTL)) {
|
|
552
|
+
const shaclText = readFileSync(SHACL_TTL, 'utf-8');
|
|
553
|
+
shapes = parseShaclShapes(shaclText);
|
|
554
|
+
} else {
|
|
555
|
+
console.warn(`SHACL shapes file not found: ${SHACL_TTL}`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const ontologyDecl = parseOntologyDeclaration(ttlText);
|
|
559
|
+
let annotationProps = parseAnnotationProperties(ttlText);
|
|
560
|
+
|
|
561
|
+
// Hardcode standard annotation properties used in the ontology (not declared as owl:AnnotationProperty)
|
|
562
|
+
if (annotationProps.length === 0) {
|
|
563
|
+
annotationProps = [
|
|
564
|
+
{ iri: 'http://www.w3.org/2000/01/rdf-schema#label', compact: 'rdfs:label', label: 'label' },
|
|
565
|
+
{ iri: 'http://www.w3.org/2000/01/rdf-schema#comment', compact: 'rdfs:comment', label: 'comment' },
|
|
566
|
+
{ iri: 'http://www.w3.org/2000/01/rdf-schema#seeAlso', compact: 'rdfs:seeAlso', label: 'seeAlso' },
|
|
567
|
+
{ iri: 'http://www.w3.org/2000/01/rdf-schema#isDefinedBy', compact: 'rdfs:isDefinedBy', label: 'isDefinedBy' },
|
|
568
|
+
{ iri: 'http://purl.org/dc/terms/title', compact: 'dcterms:title', label: 'title' },
|
|
569
|
+
{ iri: 'http://purl.org/dc/terms/description', compact: 'dcterms:description', label: 'description' },
|
|
570
|
+
{ iri: 'http://purl.org/dc/terms/source', compact: 'dcterms:source', label: 'source' },
|
|
571
|
+
{ iri: 'http://purl.org/dc/terms/license', compact: 'dcterms:license', label: 'license' },
|
|
572
|
+
{ iri: 'http://purl.org/dc/terms/created', compact: 'dcterms:created', label: 'created' },
|
|
573
|
+
{ iri: 'http://purl.org/vocab/vann/preferredNamespacePrefix', compact: 'vann:preferredNamespacePrefix', label: 'preferredNamespacePrefix' },
|
|
574
|
+
{ iri: 'http://purl.org/vocab/vann/preferredNamespaceUri', compact: 'vann:preferredNamespaceUri', label: 'preferredNamespaceUri' },
|
|
575
|
+
];
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Group shapes by targetClass
|
|
579
|
+
const shapesByTargetClass = {};
|
|
580
|
+
for (const s of shapes) {
|
|
581
|
+
const tc = s.targetClass || '(unspecified)';
|
|
582
|
+
if (!shapesByTargetClass[tc]) shapesByTargetClass[tc] = [];
|
|
583
|
+
shapesByTargetClass[tc].push(s.compact);
|
|
584
|
+
}
|
|
585
|
+
|
|
294
586
|
const output = {
|
|
295
|
-
|
|
296
|
-
|
|
587
|
+
ontology: ontologyDecl,
|
|
588
|
+
ontologyIri: ontologyDecl?.iri || 'https://www.glossarist.org/ontologies/glossarist',
|
|
589
|
+
ontologyLabel: ontologyDecl?.label || 'Glossarist Ontology',
|
|
297
590
|
classes: hierarchy.map,
|
|
298
591
|
classHierarchyRoots: hierarchy.roots,
|
|
299
592
|
properties: Object.fromEntries(properties.map(p => [p.compact, p])),
|
|
300
593
|
propertiesByDomain: propsByDomain,
|
|
594
|
+
shapes: Object.fromEntries(shapes.map(s => [s.compact, s])),
|
|
595
|
+
shapesByTargetClass,
|
|
596
|
+
annotationProperties: annotationProps,
|
|
301
597
|
stats: {
|
|
302
598
|
classCount: classes.length,
|
|
303
599
|
objectPropertyCount: properties.filter(p => p.type === 'object').length,
|
|
304
600
|
datatypePropertyCount: properties.filter(p => p.type === 'datatype').length,
|
|
601
|
+
shapeCount: shapes.length,
|
|
602
|
+
annotationPropertyCount: annotationProps.length,
|
|
305
603
|
},
|
|
306
604
|
};
|
|
307
605
|
|
|
308
606
|
mkdirSync(dirname(OUTPUT), { recursive: true });
|
|
309
607
|
writeFileSync(OUTPUT, JSON.stringify(output, null, 2) + '\n');
|
|
310
608
|
|
|
311
|
-
console.log(`Parsed ${output.stats.classCount} classes, ${output.stats.objectPropertyCount} object properties, ${output.stats.datatypePropertyCount} datatype properties`);
|
|
609
|
+
console.log(`Parsed ${output.stats.classCount} classes, ${output.stats.objectPropertyCount} object properties, ${output.stats.datatypePropertyCount} datatype properties, ${output.stats.shapeCount} SHACL shapes, ${output.stats.annotationPropertyCount} annotation properties`);
|
|
610
|
+
if (ontologyDecl) {
|
|
611
|
+
console.log(`Ontology: ${ontologyDecl.iri}`);
|
|
612
|
+
console.log(` imports: ${ontologyDecl.imports.map(i => i.label).join(', ')}`);
|
|
613
|
+
}
|
|
312
614
|
console.log(`Wrote ${OUTPUT}`);
|
|
313
615
|
}
|
|
314
616
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ontology schema loader — provides class/property definitions parsed from
|
|
2
|
+
* Ontology schema loader — provides class/property/shape definitions parsed from
|
|
3
3
|
* the Glossarist OWL ontology for the Ontospy-style concept view.
|
|
4
4
|
*/
|
|
5
5
|
import schemaData from '../data/ontology-schema.json';
|
|
@@ -28,14 +28,72 @@ export interface OwlProperty {
|
|
|
28
28
|
inverseOf: string | null;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export interface ShaclConstraint {
|
|
32
|
+
path: string | null;
|
|
33
|
+
datatype: string | null;
|
|
34
|
+
class: string | null;
|
|
35
|
+
valuesFrom: string | null;
|
|
36
|
+
nodeKind: string | null;
|
|
37
|
+
minCount: number | null;
|
|
38
|
+
maxCount: number | null;
|
|
39
|
+
in: string[] | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OwlShape {
|
|
43
|
+
iri: string;
|
|
44
|
+
compact: string;
|
|
45
|
+
label: string;
|
|
46
|
+
comment: string | null;
|
|
47
|
+
targetClass: string | null;
|
|
48
|
+
shapeClass: string | null;
|
|
49
|
+
constraints: ShaclConstraint[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface OwlOntology {
|
|
53
|
+
iri: string;
|
|
54
|
+
label: string;
|
|
55
|
+
comment: string | null;
|
|
56
|
+
prefix: string | null;
|
|
57
|
+
namespaceUri: string | null;
|
|
58
|
+
imports: { iri: string; label: string }[];
|
|
59
|
+
license: string | null;
|
|
60
|
+
created: string | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AnnotationProperty {
|
|
64
|
+
iri: string;
|
|
65
|
+
compact: string;
|
|
66
|
+
label: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type EntityType = 'class' | 'objectProperty' | 'datatypeProperty' | 'shape' | 'annotationProperty';
|
|
70
|
+
|
|
71
|
+
export const ENTITY_TYPE_META: Record<EntityType, { label: string; color: string }> = {
|
|
72
|
+
class: { label: 'Classes', color: 'blue' },
|
|
73
|
+
objectProperty: { label: 'Object Properties', color: 'emerald' },
|
|
74
|
+
datatypeProperty: { label: 'Datatype Properties', color: 'amber' },
|
|
75
|
+
shape: { label: 'SHACL Shapes', color: 'purple' },
|
|
76
|
+
annotationProperty: { label: 'Annotation Properties', color: 'pink' },
|
|
77
|
+
};
|
|
78
|
+
|
|
31
79
|
interface OntologySchema {
|
|
80
|
+
ontology: OwlOntology | null;
|
|
32
81
|
ontologyIri: string;
|
|
33
82
|
ontologyLabel: string;
|
|
34
83
|
classes: Record<string, OwlClass>;
|
|
35
84
|
classHierarchyRoots: string[];
|
|
36
85
|
properties: Record<string, OwlProperty>;
|
|
37
86
|
propertiesByDomain: Record<string, { object: string[]; datatype: string[] }>;
|
|
38
|
-
|
|
87
|
+
shapes: Record<string, OwlShape>;
|
|
88
|
+
shapesByTargetClass: Record<string, string[]>;
|
|
89
|
+
annotationProperties: AnnotationProperty[];
|
|
90
|
+
stats: {
|
|
91
|
+
classCount: number;
|
|
92
|
+
objectPropertyCount: number;
|
|
93
|
+
datatypePropertyCount: number;
|
|
94
|
+
shapeCount: number;
|
|
95
|
+
annotationPropertyCount: number;
|
|
96
|
+
};
|
|
39
97
|
}
|
|
40
98
|
|
|
41
99
|
const data = schemaData as unknown as OntologySchema;
|
|
@@ -48,6 +106,10 @@ export function getProperty(id: string): OwlProperty | null {
|
|
|
48
106
|
return data.properties[id] ?? null;
|
|
49
107
|
}
|
|
50
108
|
|
|
109
|
+
export function getShape(id: string): OwlShape | null {
|
|
110
|
+
return data.shapes[id] ?? null;
|
|
111
|
+
}
|
|
112
|
+
|
|
51
113
|
export function getPropertiesForDomain(domain: string): { object: OwlProperty[]; datatype: OwlProperty[] } {
|
|
52
114
|
const group = data.propertiesByDomain[domain];
|
|
53
115
|
if (!group) return { object: [], datatype: [] };
|
|
@@ -57,7 +119,6 @@ export function getPropertiesForDomain(domain: string): { object: OwlProperty[];
|
|
|
57
119
|
};
|
|
58
120
|
}
|
|
59
121
|
|
|
60
|
-
/** Get all properties applicable to a class, including inherited ones. */
|
|
61
122
|
export function getAllPropertiesForClass(classId: string): { object: OwlProperty[]; datatype: OwlProperty[] } {
|
|
62
123
|
const cls = data.classes[classId];
|
|
63
124
|
if (!cls) return { object: [], datatype: [] };
|
|
@@ -80,7 +141,11 @@ export function getAllPropertiesForClass(classId: string): { object: OwlProperty
|
|
|
80
141
|
return { object: objectProps, datatype: datatypeProps };
|
|
81
142
|
}
|
|
82
143
|
|
|
83
|
-
|
|
144
|
+
export function getShapesForClass(classId: string): OwlShape[] {
|
|
145
|
+
const shapeIds = data.shapesByTargetClass[classId] ?? [];
|
|
146
|
+
return shapeIds.map(id => data.shapes[id]).filter(Boolean);
|
|
147
|
+
}
|
|
148
|
+
|
|
84
149
|
export function getClassTree(): OwlClass[] {
|
|
85
150
|
return data.classHierarchyRoots
|
|
86
151
|
.map(id => data.classes[id])
|
|
@@ -95,6 +160,26 @@ export function getAllProperties(): OwlProperty[] {
|
|
|
95
160
|
return Object.values(data.properties);
|
|
96
161
|
}
|
|
97
162
|
|
|
163
|
+
export function getObjectProperties(): OwlProperty[] {
|
|
164
|
+
return Object.values(data.properties).filter(p => p.type === 'object');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function getDatatypeProperties(): OwlProperty[] {
|
|
168
|
+
return Object.values(data.properties).filter(p => p.type === 'datatype');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function getAllShapes(): OwlShape[] {
|
|
172
|
+
return Object.values(data.shapes);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function getAnnotationProperties(): AnnotationProperty[] {
|
|
176
|
+
return data.annotationProperties;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function getOntology(): OwlOntology | null {
|
|
180
|
+
return data.ontology;
|
|
181
|
+
}
|
|
182
|
+
|
|
98
183
|
export function getStats() {
|
|
99
184
|
return data.stats;
|
|
100
185
|
}
|