@oml/owl 0.7.0
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/README.md +46 -0
- package/out/index.d.ts +10 -0
- package/out/index.js +12 -0
- package/out/index.js.map +1 -0
- package/out/owl/owl-abox.d.ts +79 -0
- package/out/owl/owl-abox.js +765 -0
- package/out/owl/owl-abox.js.map +1 -0
- package/out/owl/owl-imports.d.ts +9 -0
- package/out/owl/owl-imports.js +102 -0
- package/out/owl/owl-imports.js.map +1 -0
- package/out/owl/owl-interfaces.d.ts +121 -0
- package/out/owl/owl-interfaces.js +3 -0
- package/out/owl/owl-interfaces.js.map +1 -0
- package/out/owl/owl-mapper.d.ts +80 -0
- package/out/owl/owl-mapper.js +1217 -0
- package/out/owl/owl-mapper.js.map +1 -0
- package/out/owl/owl-service.d.ts +65 -0
- package/out/owl/owl-service.js +552 -0
- package/out/owl/owl-service.js.map +1 -0
- package/out/owl/owl-shacl.d.ts +28 -0
- package/out/owl/owl-shacl.js +337 -0
- package/out/owl/owl-shacl.js.map +1 -0
- package/out/owl/owl-sparql.d.ts +71 -0
- package/out/owl/owl-sparql.js +260 -0
- package/out/owl/owl-sparql.js.map +1 -0
- package/out/owl/owl-store.d.ts +32 -0
- package/out/owl/owl-store.js +142 -0
- package/out/owl/owl-store.js.map +1 -0
- package/out/owl/owl-tbox.d.ts +98 -0
- package/out/owl/owl-tbox.js +575 -0
- package/out/owl/owl-tbox.js.map +1 -0
- package/out/owl-module.d.ts +15 -0
- package/out/owl-module.js +22 -0
- package/out/owl-module.js.map +1 -0
- package/package.json +52 -0
- package/src/index.ts +12 -0
- package/src/owl/owl-abox.ts +930 -0
- package/src/owl/owl-imports.ts +108 -0
- package/src/owl/owl-interfaces.ts +145 -0
- package/src/owl/owl-mapper.ts +1510 -0
- package/src/owl/owl-service.ts +642 -0
- package/src/owl/owl-shacl.ts +400 -0
- package/src/owl/owl-sparql.ts +317 -0
- package/src/owl/owl-store.ts +173 -0
- package/src/owl/owl-tbox.ts +727 -0
- package/src/owl-module.ts +52 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import { DataFactory } from 'n3';
|
|
4
|
+
import type { NamedNode, Quad, Store, Term } from 'n3';
|
|
5
|
+
import type { Ontology, Rule, Argument, Literal, Predicate, RelationEntity } from '@oml/language';
|
|
6
|
+
import type { OwlImportGraph, OwlStore, OwlTBoxIndexCache } from './owl-interfaces.js';
|
|
7
|
+
import {
|
|
8
|
+
isBooleanLiteral,
|
|
9
|
+
isBuiltInPredicate,
|
|
10
|
+
isDecimalLiteral,
|
|
11
|
+
isDescription,
|
|
12
|
+
isDifferentFromPredicate,
|
|
13
|
+
isDoubleLiteral,
|
|
14
|
+
isIntegerLiteral,
|
|
15
|
+
isPropertyPredicate,
|
|
16
|
+
isQuotedLiteral,
|
|
17
|
+
isRelationEntity,
|
|
18
|
+
isRelationEntityPredicate,
|
|
19
|
+
isRule,
|
|
20
|
+
isSameAsPredicate,
|
|
21
|
+
isTypePredicate,
|
|
22
|
+
isVocabulary,
|
|
23
|
+
} from '@oml/language';
|
|
24
|
+
|
|
25
|
+
const { namedNode, quad } = DataFactory;
|
|
26
|
+
|
|
27
|
+
const RDF = {
|
|
28
|
+
type: namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const RDFS = {
|
|
32
|
+
subClassOf: namedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf'),
|
|
33
|
+
subPropertyOf: namedNode('http://www.w3.org/2000/01/rdf-schema#subPropertyOf'),
|
|
34
|
+
Literal: namedNode('http://www.w3.org/2000/01/rdf-schema#Literal'),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const OWL = {
|
|
38
|
+
equivalentClass: namedNode('http://www.w3.org/2002/07/owl#equivalentClass'),
|
|
39
|
+
equivalentProperty: namedNode('http://www.w3.org/2002/07/owl#equivalentProperty'),
|
|
40
|
+
sameAs: namedNode('http://www.w3.org/2002/07/owl#sameAs'),
|
|
41
|
+
differentFrom: namedNode('http://www.w3.org/2002/07/owl#differentFrom'),
|
|
42
|
+
inverseOf: namedNode('http://www.w3.org/2002/07/owl#inverseOf'),
|
|
43
|
+
FunctionalProperty: namedNode('http://www.w3.org/2002/07/owl#FunctionalProperty'),
|
|
44
|
+
InverseFunctionalProperty: namedNode('http://www.w3.org/2002/07/owl#InverseFunctionalProperty'),
|
|
45
|
+
SymmetricProperty: namedNode('http://www.w3.org/2002/07/owl#SymmetricProperty'),
|
|
46
|
+
TransitiveProperty: namedNode('http://www.w3.org/2002/07/owl#TransitiveProperty'),
|
|
47
|
+
subPropertyOf: namedNode('http://www.w3.org/2002/07/owl#subPropertyOf'),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const OML = {
|
|
51
|
+
hasSource: namedNode('http://opencaesar.io/oml#hasSource'),
|
|
52
|
+
hasTarget: namedNode('http://opencaesar.io/oml#hasTarget'),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export interface TBoxIndex {
|
|
56
|
+
superClasses: Map<string, string[]>;
|
|
57
|
+
subProperties: Map<string, string[]>;
|
|
58
|
+
domain: Map<string, string[]>;
|
|
59
|
+
objectRange: Map<string, string[]>;
|
|
60
|
+
datatypeRange: Map<string, string[]>;
|
|
61
|
+
inverseRoles: Map<string, string[]>;
|
|
62
|
+
forwardRules: ForwardRuleDefinition[];
|
|
63
|
+
symmetricProperties: Set<string>;
|
|
64
|
+
transitiveProperties: Set<string>;
|
|
65
|
+
functionalProperties: Set<string>;
|
|
66
|
+
inverseFunctionalProperties: Set<string>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ForwardRuleDefinition {
|
|
70
|
+
iri: string;
|
|
71
|
+
body: RuleAtom[];
|
|
72
|
+
head: RuleAtom[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type RuleAtom =
|
|
76
|
+
| { kind: 'class'; classIri: string; argument: RuleTermPattern }
|
|
77
|
+
| { kind: 'property'; propertyIri: string; argument1: RuleTermPattern; argument2: RuleTermPattern };
|
|
78
|
+
|
|
79
|
+
export type RuleTermPattern =
|
|
80
|
+
| { kind: 'variable'; name: string }
|
|
81
|
+
| { kind: 'named'; iri: string }
|
|
82
|
+
| { kind: 'literal'; value: string; datatype: string; language: string };
|
|
83
|
+
|
|
84
|
+
export class TBoxIndexBuilder {
|
|
85
|
+
constructor(
|
|
86
|
+
private readonly reasoningStore: OwlStore,
|
|
87
|
+
private readonly tboxIndexCache: OwlTBoxIndexCache,
|
|
88
|
+
private readonly importGraph: OwlImportGraph,
|
|
89
|
+
) {}
|
|
90
|
+
|
|
91
|
+
buildOwn(modelUri: string, ontology?: Ontology): TBoxIndex {
|
|
92
|
+
const graphs = this.reasoningStore.graphs(modelUri);
|
|
93
|
+
const store = this.reasoningStore.getStore();
|
|
94
|
+
const quads = this.allQuads(store, graphs, null, null, null);
|
|
95
|
+
const index = this.emptyIndex();
|
|
96
|
+
|
|
97
|
+
for (const q of quads) {
|
|
98
|
+
if (q.subject.termType !== 'NamedNode') {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (q.predicate.value === RDF.type.value && q.object.termType === 'NamedNode') {
|
|
103
|
+
if (q.object.value === OWL.FunctionalProperty.value) {
|
|
104
|
+
index.functionalProperties.add(q.subject.value);
|
|
105
|
+
} else if (q.object.value === OWL.InverseFunctionalProperty.value) {
|
|
106
|
+
index.inverseFunctionalProperties.add(q.subject.value);
|
|
107
|
+
} else if (q.object.value === OWL.SymmetricProperty.value) {
|
|
108
|
+
index.symmetricProperties.add(q.subject.value);
|
|
109
|
+
} else if (q.object.value === OWL.TransitiveProperty.value) {
|
|
110
|
+
index.transitiveProperties.add(q.subject.value);
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (q.object.termType !== 'NamedNode') {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (q.predicate.value === RDFS.subClassOf.value) {
|
|
120
|
+
this.pushMapArrayUnique(index.superClasses, q.subject.value, q.object.value);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (q.predicate.value === OWL.equivalentClass.value) {
|
|
124
|
+
this.pushMapArrayUnique(index.superClasses, q.subject.value, q.object.value);
|
|
125
|
+
this.pushMapArrayUnique(index.superClasses, q.object.value, q.subject.value);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (q.predicate.value === RDFS.subPropertyOf.value || q.predicate.value === OWL.subPropertyOf.value) {
|
|
129
|
+
this.pushMapArrayUnique(index.subProperties, q.subject.value, q.object.value);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (q.predicate.value === OWL.equivalentProperty.value) {
|
|
133
|
+
this.pushMapArrayUnique(index.subProperties, q.subject.value, q.object.value);
|
|
134
|
+
this.pushMapArrayUnique(index.subProperties, q.object.value, q.subject.value);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (q.predicate.value === 'http://www.w3.org/2000/01/rdf-schema#domain') {
|
|
138
|
+
this.pushMapArrayUnique(index.domain, q.subject.value, q.object.value);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (q.predicate.value === 'http://www.w3.org/2000/01/rdf-schema#range') {
|
|
142
|
+
if (this.isDatatypeIri(q.object.value)) {
|
|
143
|
+
this.pushMapArrayUnique(index.datatypeRange, q.subject.value, q.object.value);
|
|
144
|
+
} else {
|
|
145
|
+
this.pushMapArrayUnique(index.objectRange, q.subject.value, q.object.value);
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (q.predicate.value === OWL.inverseOf.value) {
|
|
150
|
+
this.pushMapArrayUnique(index.inverseRoles, q.subject.value, q.object.value);
|
|
151
|
+
this.pushMapArrayUnique(index.inverseRoles, q.object.value, q.subject.value);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (ontology && isVocabulary(ontology)) {
|
|
156
|
+
index.forwardRules.push(...this.buildForwardRules(ontology));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.closeSuperClassesTransitively(index.superClasses);
|
|
160
|
+
this.closeSubPropertiesTransitively(index.subProperties);
|
|
161
|
+
return index;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
buildMerged(modelUri: string): TBoxIndex {
|
|
165
|
+
const closure = this.importGraph.dependenciesOf(modelUri);
|
|
166
|
+
const allUris = [modelUri, ...closure];
|
|
167
|
+
const visited = new Set<string>();
|
|
168
|
+
const merged = this.emptyIndex();
|
|
169
|
+
|
|
170
|
+
for (const uri of allUris) {
|
|
171
|
+
if (visited.has(uri)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
visited.add(uri);
|
|
175
|
+
|
|
176
|
+
const own = this.tboxIndexCache.getOwn(uri);
|
|
177
|
+
if (!own) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.mergeStringArrayMap(merged.superClasses, own.superClasses);
|
|
182
|
+
this.mergeStringArrayMap(merged.subProperties, own.subProperties);
|
|
183
|
+
this.mergeStringArrayMap(merged.domain, own.domain);
|
|
184
|
+
this.mergeStringArrayMap(merged.objectRange, own.objectRange);
|
|
185
|
+
this.mergeStringArrayMap(merged.datatypeRange, own.datatypeRange);
|
|
186
|
+
this.mergeStringArrayMap(merged.inverseRoles, own.inverseRoles);
|
|
187
|
+
this.mergeForwardRules(merged.forwardRules, own.forwardRules);
|
|
188
|
+
this.mergeSet(merged.symmetricProperties, own.symmetricProperties);
|
|
189
|
+
this.mergeSet(merged.transitiveProperties, own.transitiveProperties);
|
|
190
|
+
this.mergeSet(merged.functionalProperties, own.functionalProperties);
|
|
191
|
+
this.mergeSet(merged.inverseFunctionalProperties, own.inverseFunctionalProperties);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.closeSuperClassesTransitively(merged.superClasses);
|
|
195
|
+
this.closeSubPropertiesTransitively(merged.subProperties);
|
|
196
|
+
return merged;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private allQuads(store: Store, graphs: { own: NamedNode; entailments: NamedNode }, s: Term | null, p: Term | null, o: Term | null): Quad[] {
|
|
200
|
+
return [
|
|
201
|
+
...store.getQuads(s, p, o, graphs.own),
|
|
202
|
+
...store.getQuads(s, p, o, graphs.entailments),
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private pushMapArrayUnique(map: Map<string, string[]>, key: string, value: string): void {
|
|
207
|
+
const values = map.get(key) ?? [];
|
|
208
|
+
if (!values.includes(value)) {
|
|
209
|
+
values.push(value);
|
|
210
|
+
map.set(key, values);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private emptyIndex(): TBoxIndex {
|
|
215
|
+
return {
|
|
216
|
+
superClasses: new Map<string, string[]>(),
|
|
217
|
+
subProperties: new Map<string, string[]>(),
|
|
218
|
+
domain: new Map<string, string[]>(),
|
|
219
|
+
objectRange: new Map<string, string[]>(),
|
|
220
|
+
datatypeRange: new Map<string, string[]>(),
|
|
221
|
+
inverseRoles: new Map<string, string[]>(),
|
|
222
|
+
forwardRules: [],
|
|
223
|
+
symmetricProperties: new Set<string>(),
|
|
224
|
+
transitiveProperties: new Set<string>(),
|
|
225
|
+
functionalProperties: new Set<string>(),
|
|
226
|
+
inverseFunctionalProperties: new Set<string>(),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private mergeStringArrayMap(target: Map<string, string[]>, source: Map<string, string[]>): void {
|
|
231
|
+
for (const [key, values] of source.entries()) {
|
|
232
|
+
const existing = target.get(key) ?? [];
|
|
233
|
+
for (const value of values) {
|
|
234
|
+
if (!existing.includes(value)) {
|
|
235
|
+
existing.push(value);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
target.set(key, existing);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private mergeSet(target: Set<string>, source: Set<string>): void {
|
|
243
|
+
for (const value of source) {
|
|
244
|
+
target.add(value);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private mergeForwardRules(target: ForwardRuleDefinition[], source: ForwardRuleDefinition[]): void {
|
|
249
|
+
const merged = new Map<string, ForwardRuleDefinition>();
|
|
250
|
+
for (const rule of target) {
|
|
251
|
+
merged.set(rule.iri, rule);
|
|
252
|
+
}
|
|
253
|
+
for (const rule of source) {
|
|
254
|
+
merged.set(rule.iri, rule);
|
|
255
|
+
}
|
|
256
|
+
target.length = 0;
|
|
257
|
+
target.push(...merged.values());
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private buildForwardRules(ontology: Ontology & { ownedStatements?: unknown[] }): ForwardRuleDefinition[] {
|
|
261
|
+
const rules: ForwardRuleDefinition[] = [];
|
|
262
|
+
for (const statement of ontology.ownedStatements ?? []) {
|
|
263
|
+
if (isRule(statement) && !statement.ref) {
|
|
264
|
+
const lowered = this.lowerOmlRule(statement, ontology);
|
|
265
|
+
if (lowered) {
|
|
266
|
+
rules.push(lowered);
|
|
267
|
+
}
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (isRelationEntity(statement)) {
|
|
271
|
+
const lowered = this.lowerRelationEntityRule(statement, ontology);
|
|
272
|
+
if (lowered) {
|
|
273
|
+
rules.push(lowered);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return rules;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private lowerRelationEntityRule(relation: RelationEntity, ontology: Ontology): ForwardRuleDefinition | undefined {
|
|
281
|
+
const relationEntityIri = this.memberIri(relation, ontology);
|
|
282
|
+
const forwardName = relation.forwardRelation?.name;
|
|
283
|
+
const namespace = this.normalizeNamespace((ontology as any).namespace ?? '');
|
|
284
|
+
if (!relationEntityIri || !namespace || !forwardName) {
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
const forwardIri = `${namespace}${forwardName}`;
|
|
288
|
+
return {
|
|
289
|
+
iri: `${forwardIri}__derivation`,
|
|
290
|
+
body: [
|
|
291
|
+
{ kind: 'class', classIri: relationEntityIri, argument: { kind: 'variable', name: 'r' } },
|
|
292
|
+
{ kind: 'property', propertyIri: OML.hasSource.value, argument1: { kind: 'variable', name: 'r' }, argument2: { kind: 'variable', name: 's' } },
|
|
293
|
+
{ kind: 'property', propertyIri: OML.hasTarget.value, argument1: { kind: 'variable', name: 'r' }, argument2: { kind: 'variable', name: 't' } },
|
|
294
|
+
],
|
|
295
|
+
head: [
|
|
296
|
+
{ kind: 'property', propertyIri: forwardIri, argument1: { kind: 'variable', name: 's' }, argument2: { kind: 'variable', name: 't' } },
|
|
297
|
+
],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private lowerOmlRule(rule: Rule, ontology: Ontology): ForwardRuleDefinition | undefined {
|
|
302
|
+
const iri = this.memberIri(rule, ontology);
|
|
303
|
+
if (!iri) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
const body = this.lowerPredicates(rule.antecedent, ontology);
|
|
307
|
+
const head = this.lowerPredicates(rule.consequent, ontology);
|
|
308
|
+
if (!body || !head) {
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
return { iri, body, head };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private lowerPredicates(predicates: Predicate[], ontology: Ontology): RuleAtom[] | undefined {
|
|
315
|
+
const atoms: RuleAtom[] = [];
|
|
316
|
+
for (const predicate of predicates) {
|
|
317
|
+
if (isBuiltInPredicate(predicate)) {
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
if (isTypePredicate(predicate)) {
|
|
321
|
+
const classIri = this.resolveRefIri(predicate.type, ontology);
|
|
322
|
+
const argument = this.lowerArgument(predicate.argument, ontology);
|
|
323
|
+
if (!classIri || !argument) {
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
atoms.push({ kind: 'class', classIri, argument });
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (isRelationEntityPredicate(predicate)) {
|
|
330
|
+
const relationEntityIri = this.resolveRefIri(predicate.type, ontology);
|
|
331
|
+
const relationArg = this.lowerArgument(predicate.argument, ontology);
|
|
332
|
+
const sourceArg = this.lowerArgument(predicate.argument1, ontology);
|
|
333
|
+
const targetArg = this.lowerArgument(predicate.argument2, ontology);
|
|
334
|
+
if (!relationEntityIri || !relationArg || !sourceArg || !targetArg) {
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
337
|
+
atoms.push({ kind: 'class', classIri: relationEntityIri, argument: relationArg });
|
|
338
|
+
atoms.push({ kind: 'property', propertyIri: OML.hasSource.value, argument1: relationArg, argument2: sourceArg });
|
|
339
|
+
atoms.push({ kind: 'property', propertyIri: OML.hasTarget.value, argument1: relationArg, argument2: targetArg });
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (isPropertyPredicate(predicate)) {
|
|
343
|
+
const propertyIri = this.resolveRefIri(predicate.property, ontology);
|
|
344
|
+
const argument1 = this.lowerArgument(predicate.argument1, ontology);
|
|
345
|
+
const argument2 = this.lowerArgument(predicate.argument2, ontology);
|
|
346
|
+
if (!propertyIri || !argument1 || !argument2) {
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
atoms.push({ kind: 'property', propertyIri, argument1, argument2 });
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (isSameAsPredicate(predicate) || isDifferentFromPredicate(predicate)) {
|
|
353
|
+
const argument1 = this.lowerArgument(predicate.argument1, ontology);
|
|
354
|
+
const argument2 = this.lowerArgument(predicate.argument2, ontology);
|
|
355
|
+
if (!argument1 || !argument2) {
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
atoms.push({
|
|
359
|
+
kind: 'property',
|
|
360
|
+
propertyIri: isSameAsPredicate(predicate) ? OWL.sameAs.value : OWL.differentFrom.value,
|
|
361
|
+
argument1,
|
|
362
|
+
argument2,
|
|
363
|
+
});
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
return atoms;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private lowerArgument(argument: Argument, ontology: Ontology): RuleTermPattern | undefined {
|
|
372
|
+
if (argument.variable) {
|
|
373
|
+
return { kind: 'variable', name: argument.variable };
|
|
374
|
+
}
|
|
375
|
+
if (argument.instance) {
|
|
376
|
+
const iri = this.resolveRefIri(argument.instance, ontology);
|
|
377
|
+
return iri ? { kind: 'named', iri } : undefined;
|
|
378
|
+
}
|
|
379
|
+
if (argument.literal) {
|
|
380
|
+
return this.lowerLiteral(argument.literal, ontology);
|
|
381
|
+
}
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private lowerLiteral(literalValue: Literal, ontology: Ontology): RuleTermPattern | undefined {
|
|
386
|
+
if (isBooleanLiteral(literalValue)) {
|
|
387
|
+
return { kind: 'literal', value: String(literalValue.value), datatype: 'http://www.w3.org/2001/XMLSchema#boolean', language: '' };
|
|
388
|
+
}
|
|
389
|
+
if (isIntegerLiteral(literalValue)) {
|
|
390
|
+
return { kind: 'literal', value: String(literalValue.value), datatype: 'http://www.w3.org/2001/XMLSchema#integer', language: '' };
|
|
391
|
+
}
|
|
392
|
+
if (isDecimalLiteral(literalValue)) {
|
|
393
|
+
return { kind: 'literal', value: String(literalValue.value), datatype: 'http://www.w3.org/2001/XMLSchema#decimal', language: '' };
|
|
394
|
+
}
|
|
395
|
+
if (isDoubleLiteral(literalValue)) {
|
|
396
|
+
return { kind: 'literal', value: String(literalValue.value), datatype: 'http://www.w3.org/2001/XMLSchema#double', language: '' };
|
|
397
|
+
}
|
|
398
|
+
if (isQuotedLiteral(literalValue)) {
|
|
399
|
+
if (literalValue.type) {
|
|
400
|
+
const datatype = this.resolveRefIri(literalValue.type, ontology);
|
|
401
|
+
return datatype ? { kind: 'literal', value: literalValue.value, datatype, language: '' } : undefined;
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
kind: 'literal',
|
|
405
|
+
value: literalValue.value,
|
|
406
|
+
datatype: literalValue.langTag ? 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' : 'http://www.w3.org/2001/XMLSchema#string',
|
|
407
|
+
language: literalValue.langTag ?? '',
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private resolveRefIri(ref: { ref?: unknown; name?: string }, ontology: Ontology): string | undefined {
|
|
414
|
+
const target = (ref as any)?.ref ?? ref;
|
|
415
|
+
if (!target) {
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
418
|
+
return this.memberIri(target as { name?: string; $container?: unknown }, ontology);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private memberIri(member: { name?: string; $container?: unknown }, fallbackOntology: Ontology): string | undefined {
|
|
422
|
+
const name = member?.name;
|
|
423
|
+
if (!name) {
|
|
424
|
+
return undefined;
|
|
425
|
+
}
|
|
426
|
+
const ontology = this.ontologyOf(member) ?? fallbackOntology;
|
|
427
|
+
const namespace = this.normalizeNamespace((ontology as any).namespace ?? '');
|
|
428
|
+
return namespace ? `${namespace}${name.replace(/^\^/, '')}` : undefined;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private ontologyOf(node: { $container?: unknown }): Ontology | undefined {
|
|
432
|
+
let current: any = node;
|
|
433
|
+
while (current && !isVocabulary(current) && !isDescription(current)) {
|
|
434
|
+
current = current.$container;
|
|
435
|
+
}
|
|
436
|
+
return current as Ontology | undefined;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private normalizeNamespace(value: string): string {
|
|
440
|
+
return value.replace(/^<|>$/g, '');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private closeSuperClassesTransitively(superClasses: Map<string, string[]>): void {
|
|
444
|
+
for (const [clazz, directSupers] of superClasses.entries()) {
|
|
445
|
+
const visited = new Set<string>();
|
|
446
|
+
const worklist = [...directSupers];
|
|
447
|
+
const closure: string[] = [];
|
|
448
|
+
|
|
449
|
+
while (worklist.length > 0) {
|
|
450
|
+
const next = worklist.shift();
|
|
451
|
+
if (!next || visited.has(next)) {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
visited.add(next);
|
|
455
|
+
closure.push(next);
|
|
456
|
+
worklist.push(...(superClasses.get(next) ?? []));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
superClasses.set(clazz, closure);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private closeSubPropertiesTransitively(subProperties: Map<string, string[]>): void {
|
|
464
|
+
for (const [property, directSupers] of subProperties.entries()) {
|
|
465
|
+
const visited = new Set<string>();
|
|
466
|
+
const worklist = [...directSupers];
|
|
467
|
+
const closure: string[] = [property];
|
|
468
|
+
|
|
469
|
+
while (worklist.length > 0) {
|
|
470
|
+
const next = worklist.shift();
|
|
471
|
+
if (!next || visited.has(next)) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
visited.add(next);
|
|
475
|
+
closure.push(next);
|
|
476
|
+
worklist.push(...(subProperties.get(next) ?? []));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
subProperties.set(property, [...new Set(closure)].sort());
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private isDatatypeIri(iri: string): boolean {
|
|
484
|
+
return iri.startsWith('http://www.w3.org/2001/XMLSchema#') || iri === RDFS.Literal.value;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export class TBoxIndexCache {
|
|
489
|
+
private readonly ownIndexes = new Map<string, TBoxIndex>();
|
|
490
|
+
private readonly mergedIndexes = new Map<string, TBoxIndex>();
|
|
491
|
+
private readonly dirtyMerged = new Set<string>();
|
|
492
|
+
|
|
493
|
+
getOwn(modelUri: string): TBoxIndex | undefined {
|
|
494
|
+
return this.ownIndexes.get(modelUri);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
setOwn(modelUri: string, index: TBoxIndex): void {
|
|
498
|
+
this.ownIndexes.set(modelUri, index);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
hasOwn(modelUri: string): boolean {
|
|
502
|
+
return this.ownIndexes.has(modelUri);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
invalidateOwn(modelUri: string): void {
|
|
506
|
+
this.ownIndexes.delete(modelUri);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
getMerged(modelUri: string): TBoxIndex | undefined {
|
|
510
|
+
return this.mergedIndexes.get(modelUri);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
setMerged(modelUri: string, index: TBoxIndex): void {
|
|
514
|
+
this.mergedIndexes.set(modelUri, index);
|
|
515
|
+
this.dirtyMerged.delete(modelUri);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
hasMerged(modelUri: string): boolean {
|
|
519
|
+
return this.mergedIndexes.has(modelUri);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
isMergedDirty(modelUri: string): boolean {
|
|
523
|
+
return this.dirtyMerged.has(modelUri);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
invalidateMerged(modelUri: string): void {
|
|
527
|
+
this.dirtyMerged.add(modelUri);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
invalidateMergedAll(modelUris: string[]): void {
|
|
531
|
+
for (const modelUri of modelUris) {
|
|
532
|
+
this.invalidateMerged(modelUri);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export class TBoxChainer {
|
|
538
|
+
private static readonly MAX_CHAIN_ITERATIONS = 1000;
|
|
539
|
+
|
|
540
|
+
constructor(private readonly reasoningStore: OwlStore) {}
|
|
541
|
+
|
|
542
|
+
chain(modelUri: string): void {
|
|
543
|
+
const graphs = this.reasoningStore.graphs(modelUri);
|
|
544
|
+
const store = this.reasoningStore.getStore();
|
|
545
|
+
this.reasoningStore.clearEntailments(modelUri);
|
|
546
|
+
|
|
547
|
+
let iterations = 0;
|
|
548
|
+
while (true) {
|
|
549
|
+
iterations += 1;
|
|
550
|
+
if (iterations > TBoxChainer.MAX_CHAIN_ITERATIONS) {
|
|
551
|
+
throw new Error(
|
|
552
|
+
`TBox chaining did not converge for '${modelUri}' `
|
|
553
|
+
+ `after ${TBoxChainer.MAX_CHAIN_ITERATIONS} iterations.`,
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const pending: Quad[] = [];
|
|
558
|
+
const pendingKeys = new Set<string>();
|
|
559
|
+
const iteration = this.collectIterationData(store, graphs);
|
|
560
|
+
|
|
561
|
+
this.fireT1(store, graphs, iteration, pending, pendingKeys);
|
|
562
|
+
this.fireT2(store, graphs, iteration, pending, pendingKeys);
|
|
563
|
+
this.fireT3(store, graphs, iteration, pending, pendingKeys);
|
|
564
|
+
this.fireT4(store, graphs, iteration, pending, pendingKeys);
|
|
565
|
+
this.fireT5(store, graphs, iteration, pending, pendingKeys);
|
|
566
|
+
|
|
567
|
+
if (pending.length === 0) {
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
store.addQuads(pending);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
private fireT1(
|
|
575
|
+
store: Store,
|
|
576
|
+
graphs: { own: NamedNode; entailments: NamedNode },
|
|
577
|
+
iteration: TBoxIterationData,
|
|
578
|
+
pending: Quad[],
|
|
579
|
+
pendingKeys: Set<string>,
|
|
580
|
+
): void {
|
|
581
|
+
for (const edge of iteration.subClassEdges) {
|
|
582
|
+
for (const parent of iteration.subClassBySub.get(edge.object.value) ?? []) {
|
|
583
|
+
this.pushIfNew(store, quad(edge.subject, RDFS.subClassOf, parent, graphs.entailments), pending, pendingKeys);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
private fireT2(
|
|
589
|
+
store: Store,
|
|
590
|
+
graphs: { own: NamedNode; entailments: NamedNode },
|
|
591
|
+
iteration: TBoxIterationData,
|
|
592
|
+
pending: Quad[],
|
|
593
|
+
pendingKeys: Set<string>,
|
|
594
|
+
): void {
|
|
595
|
+
for (const edge of iteration.subPropertyEdges) {
|
|
596
|
+
for (const parent of iteration.subPropertyBySub.get(edge.object.value) ?? []) {
|
|
597
|
+
this.pushIfNew(store, quad(edge.subject, RDFS.subPropertyOf, parent, graphs.entailments), pending, pendingKeys);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
private fireT3(
|
|
603
|
+
store: Store,
|
|
604
|
+
graphs: { own: NamedNode; entailments: NamedNode },
|
|
605
|
+
iteration: TBoxIterationData,
|
|
606
|
+
pending: Quad[],
|
|
607
|
+
pendingKeys: Set<string>,
|
|
608
|
+
): void {
|
|
609
|
+
for (const [left, right] of iteration.equivalentClasses) {
|
|
610
|
+
this.pushIfNew(store, quad(namedNode(left), RDFS.subClassOf, namedNode(right), graphs.entailments), pending, pendingKeys);
|
|
611
|
+
this.pushIfNew(store, quad(namedNode(right), RDFS.subClassOf, namedNode(left), graphs.entailments), pending, pendingKeys);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private fireT4(
|
|
616
|
+
store: Store,
|
|
617
|
+
graphs: { own: NamedNode; entailments: NamedNode },
|
|
618
|
+
iteration: TBoxIterationData,
|
|
619
|
+
pending: Quad[],
|
|
620
|
+
pendingKeys: Set<string>,
|
|
621
|
+
): void {
|
|
622
|
+
for (const [left, right] of iteration.equivalentProperties) {
|
|
623
|
+
this.pushIfNew(store, quad(namedNode(left), RDFS.subPropertyOf, namedNode(right), graphs.entailments), pending, pendingKeys);
|
|
624
|
+
this.pushIfNew(store, quad(namedNode(right), RDFS.subPropertyOf, namedNode(left), graphs.entailments), pending, pendingKeys);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
private fireT5(
|
|
629
|
+
store: Store,
|
|
630
|
+
graphs: { own: NamedNode; entailments: NamedNode },
|
|
631
|
+
iteration: TBoxIterationData,
|
|
632
|
+
pending: Quad[],
|
|
633
|
+
pendingKeys: Set<string>,
|
|
634
|
+
): void {
|
|
635
|
+
for (const [role, inverseRoles] of iteration.inverseByRole.entries()) {
|
|
636
|
+
for (const inverseRole of inverseRoles) {
|
|
637
|
+
this.pushIfNew(store, quad(inverseRole, OWL.inverseOf, namedNode(role), graphs.entailments), pending, pendingKeys);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
private collectIterationData(store: Store, graphs: { own: NamedNode; entailments: NamedNode }): TBoxIterationData {
|
|
643
|
+
const quads = [
|
|
644
|
+
...store.getQuads(null, null, null, graphs.own),
|
|
645
|
+
...store.getQuads(null, null, null, graphs.entailments),
|
|
646
|
+
];
|
|
647
|
+
|
|
648
|
+
const subClassEdges: Array<{ subject: NamedNode; object: NamedNode }> = [];
|
|
649
|
+
const subClassBySub = new Map<string, NamedNode[]>();
|
|
650
|
+
const subPropertyEdges: Array<{ subject: NamedNode; object: NamedNode }> = [];
|
|
651
|
+
const subPropertyBySub = new Map<string, NamedNode[]>();
|
|
652
|
+
const equivalentClasses: Array<[string, string]> = [];
|
|
653
|
+
const equivalentProperties: Array<[string, string]> = [];
|
|
654
|
+
const inverseByRole = new Map<string, NamedNode[]>();
|
|
655
|
+
|
|
656
|
+
for (const q of quads) {
|
|
657
|
+
if (q.subject.termType !== 'NamedNode' || q.object.termType !== 'NamedNode') {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (q.predicate.value === RDFS.subClassOf.value) {
|
|
661
|
+
subClassEdges.push({ subject: q.subject, object: q.object });
|
|
662
|
+
const parents = subClassBySub.get(q.subject.value) ?? [];
|
|
663
|
+
parents.push(q.object);
|
|
664
|
+
subClassBySub.set(q.subject.value, parents);
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (q.predicate.value === RDFS.subPropertyOf.value || q.predicate.value === OWL.subPropertyOf.value) {
|
|
668
|
+
subPropertyEdges.push({ subject: q.subject, object: q.object });
|
|
669
|
+
const parents = subPropertyBySub.get(q.subject.value) ?? [];
|
|
670
|
+
parents.push(q.object);
|
|
671
|
+
subPropertyBySub.set(q.subject.value, parents);
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (q.predicate.value === OWL.equivalentClass.value) {
|
|
675
|
+
equivalentClasses.push([q.subject.value, q.object.value]);
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
if (q.predicate.value === OWL.equivalentProperty.value) {
|
|
679
|
+
equivalentProperties.push([q.subject.value, q.object.value]);
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (q.predicate.value === OWL.inverseOf.value) {
|
|
683
|
+
const entries = inverseByRole.get(q.subject.value) ?? [];
|
|
684
|
+
if (!entries.some((entry) => entry.value === q.object.value)) {
|
|
685
|
+
entries.push(q.object);
|
|
686
|
+
}
|
|
687
|
+
inverseByRole.set(q.subject.value, entries);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return {
|
|
692
|
+
subClassEdges,
|
|
693
|
+
subClassBySub,
|
|
694
|
+
subPropertyEdges,
|
|
695
|
+
subPropertyBySub,
|
|
696
|
+
equivalentClasses,
|
|
697
|
+
equivalentProperties,
|
|
698
|
+
inverseByRole,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private pushIfNew(store: Store, inferred: Quad, pending: Quad[], pendingKeys: Set<string>): void {
|
|
703
|
+
if (store.has(inferred)) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const key = this.quadKey(inferred);
|
|
707
|
+
if (pendingKeys.has(key)) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
pending.push(inferred);
|
|
711
|
+
pendingKeys.add(key);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
private quadKey(q: Quad): string {
|
|
715
|
+
return `${q.subject.id}|${q.predicate.id}|${q.object.id}|${q.graph.id}`;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
interface TBoxIterationData {
|
|
720
|
+
subClassEdges: Array<{ subject: NamedNode; object: NamedNode }>;
|
|
721
|
+
subClassBySub: Map<string, NamedNode[]>;
|
|
722
|
+
subPropertyEdges: Array<{ subject: NamedNode; object: NamedNode }>;
|
|
723
|
+
subPropertyBySub: Map<string, NamedNode[]>;
|
|
724
|
+
equivalentClasses: Array<[string, string]>;
|
|
725
|
+
equivalentProperties: Array<[string, string]>;
|
|
726
|
+
inverseByRole: Map<string, NamedNode[]>;
|
|
727
|
+
}
|