@skaterqiang/protege-js 0.1.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +48 -0
  3. package/README.md +287 -0
  4. package/docs/API.md +654 -0
  5. package/docs/design/OWL2-/347/274/272/345/217/243/345/210/206/346/236/220.md +140 -0
  6. package/docs/owl2-rl/OWL2-RL-/350/247/204/345/210/231/347/273/206/345/210/231.md +168 -0
  7. package/docs/owl2-rl/owl2-profiles-spec-zh.html +1914 -0
  8. package/docs/owl2-rl/owl2-profiles-spec.html +1916 -0
  9. package/docs/owl2-rl/rl_rules.json +392 -0
  10. package/main.js +46 -0
  11. package/package.json +76 -0
  12. package/sample/README.md +74 -0
  13. package/sample/case1-ecommerce-risk.js +170 -0
  14. package/sample/case10-ecommerce-customer-service.js +454 -0
  15. package/sample/case2-family-swrl.js +147 -0
  16. package/sample/case3-pharma-safety.js +132 -0
  17. package/sample/case4-knowledge-publishing.js +111 -0
  18. package/sample/case5-obda-profiles.js +153 -0
  19. package/sample/case6-medical-ontology.js +152 -0
  20. package/sample/case7-manufacturing-ppr.js +155 -0
  21. package/sample/case8-real-medical-bfo-ogms.js +353 -0
  22. package/sample/case9-real-manufacturing-iao.js +360 -0
  23. package/sample/ontologies/bfo.owl +1715 -0
  24. package/sample/ontologies/iao.owl +8532 -0
  25. package/sample/ontologies/ogms.owl +4320 -0
  26. package/sample/ontologies/ro-core.owl +1010 -0
  27. package/scripts/verify-api.js +53 -0
  28. package/src/index.js +176 -0
  29. package/src/inference/OWL2ProfileReasoners.js +67 -0
  30. package/src/inference/OWL2RLReasoner.js +65 -0
  31. package/src/inference/ReasonerQueries.js +111 -0
  32. package/src/inference/SWRLReasoner.js +409 -0
  33. package/src/inference/TripleStore.js +82 -0
  34. package/src/inference/rdf.js +83 -0
  35. package/src/inference/rules/owl2el.js +33 -0
  36. package/src/inference/rules/owl2ql.js +29 -0
  37. package/src/inference/rules/owl2rl.js +823 -0
  38. package/src/io/FunctionalSyntaxParser.js +399 -0
  39. package/src/io/FunctionalSyntaxWriter.js +180 -0
  40. package/src/io/ManchesterSyntaxParser.js +398 -0
  41. package/src/io/OWLXMLParser.js +356 -0
  42. package/src/io/OntologyLoader.js +125 -0
  43. package/src/io/RDFGraphToOntology.js +702 -0
  44. package/src/io/RDFXMLParser.js +319 -0
  45. package/src/io/RDFXMLWriter.js +196 -0
  46. package/src/io/SWRLParser.js +150 -0
  47. package/src/io/TurtleParser.js +363 -0
  48. package/src/io/TurtleWriter.js +393 -0
  49. package/src/model/IRI.js +62 -0
  50. package/src/model/OWLAxiom.js +507 -0
  51. package/src/model/OWLClassExpression.js +242 -0
  52. package/src/model/OWLEntity.js +142 -0
  53. package/src/model/OWLLiteral.js +30 -0
  54. package/src/model/OWLModelManager.js +106 -0
  55. package/src/model/OWLOntology.js +119 -0
  56. package/src/model/SWRL.js +152 -0
  57. package/src/model/event/OWLModelManagerEvent.js +33 -0
  58. package/src/model/hierarchy/HierarchyProvider.js +214 -0
  59. package/src/profiles/OWL2Profiles.js +185 -0
  60. package/src/server/public/app.js +97 -0
  61. package/src/server/public/index.html +49 -0
  62. package/src/server/webServer.js +138 -0
  63. package/src/validation/GlobalRestrictionsValidator.js +172 -0
  64. package/test/core.test.js +111 -0
  65. package/test/exports-map.test.js +165 -0
  66. package/test/owl2/axioms.test.js +160 -0
  67. package/test/owl2/classExpressions.test.js +99 -0
  68. package/test/owl2/functional-syntax-writer.test.js +65 -0
  69. package/test/owl2/functional.test.js +113 -0
  70. package/test/owl2/global-restrictions.test.js +76 -0
  71. package/test/owl2/longtail.test.js +74 -0
  72. package/test/owl2/manchester-syntax.test.js +100 -0
  73. package/test/owl2/ontology-loader-imports.test.js +62 -0
  74. package/test/owl2/owlxml-parser.test.js +112 -0
  75. package/test/owl2/profile-reasoners.test.js +108 -0
  76. package/test/owl2/profiles.test.js +66 -0
  77. package/test/owl2/rdf-graph-to-ontology.test.js +177 -0
  78. package/test/owl2/rdfxml-punning.test.js +25 -0
  79. package/test/owl2/rdfxml-writer.test.js +55 -0
  80. package/test/owl2/rdfxml.test.js +91 -0
  81. package/test/owl2/reasoner-queries.test.js +58 -0
  82. package/test/owl2/sample-e2e.test.js +195 -0
  83. package/test/owl2/swrl-parser.test.js +60 -0
  84. package/test/owl2/swrl.test.js +131 -0
  85. package/test/owl2/turtle-writer.test.js +84 -0
  86. package/test/owl2/turtle.test.js +124 -0
  87. package/test/owl2rl/cax.test.js +57 -0
  88. package/test/owl2rl/cls.test.js +210 -0
  89. package/test/owl2rl/dt.test.js +72 -0
  90. package/test/owl2rl/eq.test.js +95 -0
  91. package/test/owl2rl/prp.test.js +198 -0
  92. package/test/owl2rl/scm.test.js +203 -0
@@ -0,0 +1,702 @@
1
+ 'use strict';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // io/RDFGraphToOntology — convert a TripleStore (raw RDF triples) into a
5
+ // structured OWLOntology. This is the W3C "Mapping to RDF Graphs" inverse.
6
+ //
7
+ // Handles:
8
+ // - Named classes / properties / individuals (owl:Class, owl:ObjectProperty,
9
+ // owl:DatatypeProperty, owl:NamedIndividual)
10
+ // - Anonymous class expressions encoded as RDF blank nodes:
11
+ // owl:Restriction (someValuesFrom / allValuesFrom / hasValue / hasSelf /
12
+ // min/max/exact cardinality, qualified + unqualified)
13
+ // owl:intersectionOf / owl:unionOf / owl:complementOf / owl:oneOf
14
+ // - Data ranges: owl:onDatatype + owl:withRestrictions (facet lists),
15
+ // owl:intersectionOf / unionOf / complementOf / oneOf
16
+ // - Class axioms: rdfs:subClassOf, owl:equivalentClass, owl:disjointWith,
17
+ // owl:disjointUnionOf, owl:hasKey
18
+ // - Property axioms: rdfs:subPropertyOf, owl:inverseOf, rdfs:domain,
19
+ // rdfs:range, owl:propertyChainAxiom
20
+ // - Property characteristics via rdf:type (Functional/InverseFunctional/
21
+ // Transitive/Symmetric/Asymmetric/Reflexive/Irreflexive)
22
+ // - Assertions: rdf:type, owl:sameAs, owl:differentFrom,
23
+ // owl:NegativePropertyAssertion (reified)
24
+ // - N-ary constructs: owl:AllDisjointClasses / owl:AllDifferent /
25
+ // owl:AllDisjointProperties (owl:members / owl:distinctMembers)
26
+ // - Axiom annotations via owl:Axiom reification (owl:annotatedSource /
27
+ // annotatedProperty / annotatedTarget)
28
+ // - Ontology header: owl:Ontology with owl:imports, owl:versionIRI,
29
+ // owl:priorVersion, owl:backwardCompatibleWith, owl:incompatibleWith,
30
+ // plus arbitrary annotation properties on the ontology node itself
31
+ // ---------------------------------------------------------------------------
32
+
33
+ const { OWLOntology, OWLOntologyID } = require('../model/OWLOntology');
34
+ const {
35
+ OWLClass, OWLObjectProperty, OWLDataProperty, OWLNamedIndividual,
36
+ OWLAnonymousIndividual, OWLDatatype, OWLAnnotationProperty
37
+ } = require('../model/OWLEntity');
38
+ const CE = require('../model/OWLClassExpression');
39
+ const AX = require('../model/OWLAxiom');
40
+ const { OWLLiteral } = require('../model/OWLLiteral');
41
+ const { IRI } = require('../model/IRI');
42
+ const { NS, parseLiteral } = require('../inference/rdf');
43
+
44
+ const RDF = NS.RDF, RDFS = NS.RDFS, OWL = NS.OWL, XSD = NS.XSD;
45
+
46
+ // Facet IRI → name
47
+ const FACET_NAMES = Object.freeze({
48
+ [XSD + 'minInclusive']: 'minInclusive',
49
+ [XSD + 'maxInclusive']: 'maxInclusive',
50
+ [XSD + 'minExclusive']: 'minExclusive',
51
+ [XSD + 'maxExclusive']: 'maxExclusive',
52
+ [XSD + 'length']: 'length',
53
+ [XSD + 'minLength']: 'minLength',
54
+ [XSD + 'maxLength']: 'maxLength',
55
+ [XSD + 'pattern']: 'pattern',
56
+ [XSD + 'totalDigits']: 'totalDigits',
57
+ [XSD + 'fractionDigits']: 'fractionDigits',
58
+ [XSD + 'whiteSpace']: 'whiteSpace'
59
+ });
60
+
61
+ const BUILTIN_ANNOTATION_PROPERTIES = new Set([
62
+ RDFS + 'label', RDFS + 'comment', RDFS + 'seeAlso', RDFS + 'isDefinedBy',
63
+ OWL + 'versionInfo', OWL + 'deprecated', OWL + 'priorVersion',
64
+ OWL + 'backwardCompatibleWith', OWL + 'incompatibleWith'
65
+ ]);
66
+
67
+ const CHARACTERISTIC_RDF_TYPES = {
68
+ [OWL + 'FunctionalProperty']: 'FUNCTIONAL',
69
+ [OWL + 'InverseFunctionalProperty']: 'INVERSE_FUNCTIONAL',
70
+ [OWL + 'TransitiveProperty']: 'TRANSITIVE',
71
+ [OWL + 'SymmetricProperty']: 'SYMMETRIC',
72
+ [OWL + 'AsymmetricProperty']: 'ASYMMETRIC',
73
+ [OWL + 'ReflexiveProperty']: 'REFLEXIVE',
74
+ [OWL + 'IrreflexiveProperty']: 'IRREFLEXIVE'
75
+ };
76
+
77
+ /**
78
+ * Convert a TripleStore into an OWLOntology.
79
+ * @param {TripleStore} store
80
+ * @param {{ontologyIRI?: string}} [opts]
81
+ * @returns {OWLOntology}
82
+ */
83
+ function triplesToOntology(store, opts = {}) {
84
+ const ont = new OWLOntology(new OWLOntologyID(opts.ontologyIRI || null));
85
+ const ctx = { store, ont, ceCache: new Map(), drCache: new Map(), indCache: new Map() };
86
+
87
+ parseOntologyHeader(ctx);
88
+ const namedClasses = parseEntities(ctx);
89
+ parseClassAxioms(ctx, namedClasses);
90
+ parsePropertyAxioms(ctx);
91
+ parseIndividualAxioms(ctx);
92
+ parseNAryConstructs(ctx);
93
+ parseNegativeAssertions(ctx);
94
+ parseAnnotations(ctx);
95
+ return ont;
96
+ }
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // Ontology header
100
+ // ---------------------------------------------------------------------------
101
+
102
+ function parseOntologyHeader(ctx) {
103
+ const { store, ont } = ctx;
104
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'Ontology')) {
105
+ if (isAnon(s)) continue;
106
+ if (!ont.getOntologyID().ontologyIRI) ont.id.ontologyIRI = s;
107
+ ont.headerSubject = s;
108
+ const v = store.objects(s, OWL + 'versionIRI')[0];
109
+ if (v) ont.id.versionIRI = v;
110
+ ont.imports = store.objects(s, OWL + 'imports').slice();
111
+ ont.priorVersion = store.objects(s, OWL + 'priorVersion').slice();
112
+ ont.backwardCompatibleWith = store.objects(s, OWL + 'backwardCompatibleWith').slice();
113
+ ont.incompatibleWith = store.objects(s, OWL + 'incompatibleWith').slice();
114
+ // Ontology-level annotations: any non-structural predicate on the ontology node
115
+ for (const [_, p, o] of store.match(s, null, null)) {
116
+ if (p.startsWith(RDF) || p.startsWith(OWL + 'imports') || p.startsWith(OWL + 'versionIRI')
117
+ || p.startsWith(OWL + 'priorVersion') || p.startsWith(OWL + 'backwardCompatibleWith')
118
+ || p.startsWith(OWL + 'incompatibleWith')) continue;
119
+ if (BUILTIN_ANNOTATION_PROPERTIES.has(p)) {
120
+ const val = literalFromEncoding(o) || IRI.create(o);
121
+ ont.addOntologyAnnotation(new AX.OWLAnnotationAssertionAxiom(
122
+ s, new OWLAnnotationProperty(p), val));
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Entity declarations
130
+ // ---------------------------------------------------------------------------
131
+
132
+ function parseEntities(ctx) {
133
+ const { store, ont } = ctx;
134
+ const namedClasses = new Set();
135
+
136
+ const declare = (iri, mk) => {
137
+ if (isAnon(iri)) return null;
138
+ ont.addAxiom(new AX.OWLDeclarationAxiom(mk(iri)));
139
+ return iri;
140
+ };
141
+
142
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'Class')) {
143
+ if (declare(s, (i) => new OWLClass(i))) namedClasses.add(s);
144
+ }
145
+ for (const [s] of store.match(null, RDF + 'type', RDFS + 'Class')) {
146
+ if (declare(s, (i) => new OWLClass(i))) namedClasses.add(s);
147
+ }
148
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'ObjectProperty')) {
149
+ declare(s, (i) => new OWLObjectProperty(i));
150
+ }
151
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'DatatypeProperty')) {
152
+ declare(s, (i) => new OWLDataProperty(i));
153
+ }
154
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'AnnotationProperty')) {
155
+ declare(s, (i) => new OWLAnnotationProperty(i));
156
+ }
157
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'NamedIndividual')) {
158
+ declare(s, (i) => new OWLNamedIndividual(i));
159
+ }
160
+ for (const [s] of store.match(null, RDF + 'type', RDFS + 'Datatype')) {
161
+ declare(s, (i) => new OWLDatatype(i));
162
+ }
163
+ return namedClasses;
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Class axioms (subClassOf / equivalentClass / disjointWith / disjointUnionOf
168
+ // / hasKey). Each super/equivalent side may be a named class or a blank node
169
+ // encoding a class expression.
170
+ // ---------------------------------------------------------------------------
171
+
172
+ function parseClassAxioms(ctx, namedClasses) {
173
+ const { store, ont } = ctx;
174
+
175
+ const clsOf = (node) => classExpression(ctx, node);
176
+
177
+ for (const c of namedClasses) {
178
+ const cls = new OWLClass(c);
179
+ for (const sup of store.objects(c, RDFS + 'subClassOf')) {
180
+ const ce = clsOf(sup);
181
+ if (ce) ont.addAxiom(new AX.OWLSubClassOfAxiom(cls, ce));
182
+ }
183
+ for (const eq of store.objects(c, OWL + 'equivalentClass')) {
184
+ const ce = clsOf(eq);
185
+ if (ce) ont.addAxiom(new AX.OWLEquivalentClassesAxiom([cls, ce]));
186
+ }
187
+ for (const d of store.objects(c, OWL + 'disjointWith')) {
188
+ const ce = clsOf(d);
189
+ if (ce) ont.addAxiom(new AX.OWLDisjointClassesAxiom([cls, ce]));
190
+ }
191
+ for (const du of store.objects(c, OWL + 'disjointUnionOf')) {
192
+ const members = listNodes(store, du).map(clsOf).filter(Boolean);
193
+ if (members.length >= 2) {
194
+ ont.addAxiom(new AX.OWLDisjointUnionAxiom(cls, members));
195
+ }
196
+ }
197
+ for (const k of store.objects(c, OWL + 'hasKey')) {
198
+ const props = listNodes(store, k).map(n => {
199
+ if (store.match(n, RDF + 'type', OWL + 'ObjectProperty').length) {
200
+ return new OWLObjectProperty(n);
201
+ }
202
+ return new OWLDataProperty(n);
203
+ });
204
+ if (props.length) ont.addAxiom(new AX.OWLHasKeyAxiom(cls, props));
205
+ }
206
+ }
207
+ }
208
+
209
+ // ---------------------------------------------------------------------------
210
+ // Property axioms + characteristics
211
+ // ---------------------------------------------------------------------------
212
+
213
+ function parsePropertyAxioms(ctx) {
214
+ const { store, ont } = ctx;
215
+
216
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'ObjectProperty')) {
217
+ if (isAnon(s)) continue;
218
+ const p = new OWLObjectProperty(s);
219
+ for (const sup of store.objects(s, RDFS + 'subPropertyOf')) {
220
+ ont.addAxiom(new AX.OWLSubObjectPropertyOfAxiom(p, new OWLObjectProperty(sup)));
221
+ }
222
+ for (const inv of store.objects(s, OWL + 'inverseOf')) {
223
+ ont.addAxiom(new AX.OWLInverseObjectPropertiesAxiom(p, new OWLObjectProperty(inv)));
224
+ }
225
+ for (const d of store.objects(s, RDFS + 'domain')) {
226
+ const ce = classExpression(ctx, d);
227
+ if (ce) ont.addAxiom(new AX.OWLObjectPropertyDomainAxiom(p, ce));
228
+ }
229
+ for (const r of store.objects(s, RDFS + 'range')) {
230
+ const ce = classExpression(ctx, r);
231
+ if (ce) ont.addAxiom(new AX.OWLObjectPropertyRangeAxiom(p, ce));
232
+ }
233
+ for (const ch of store.objects(s, OWL + 'propertyChainAxiom')) {
234
+ const chain = listNodes(store, ch).map(n => new OWLObjectProperty(n));
235
+ if (chain.length >= 2) {
236
+ ont.addAxiom(new AX.OWLSubPropertyChainOfAxiom(chain, p));
237
+ }
238
+ }
239
+ applyCharacteristics(ctx, s, p, true);
240
+ }
241
+
242
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'DatatypeProperty')) {
243
+ if (isAnon(s)) continue;
244
+ const p = new OWLDataProperty(s);
245
+ for (const sup of store.objects(s, RDFS + 'subPropertyOf')) {
246
+ ont.addAxiom(new AX.OWLSubDataPropertyOfAxiom(p, new OWLDataProperty(sup)));
247
+ }
248
+ for (const d of store.objects(s, RDFS + 'domain')) {
249
+ const ce = classExpression(ctx, d);
250
+ if (ce) ont.addAxiom(new AX.OWLDataPropertyDomainAxiom(p, ce));
251
+ }
252
+ for (const r of store.objects(s, RDFS + 'range')) {
253
+ const dr = dataRange(ctx, r);
254
+ if (dr) ont.addAxiom(new AX.OWLDataPropertyRangeAxiom(p, dr));
255
+ }
256
+ applyCharacteristics(ctx, s, p, false);
257
+ }
258
+ }
259
+
260
+ function applyCharacteristics(ctx, propIRI, prop, isObject) {
261
+ const { store, ont } = ctx;
262
+ for (const t of store.objects(propIRI, RDF + 'type')) {
263
+ const kind = CHARACTERISTIC_RDF_TYPES[t];
264
+ if (!kind) continue;
265
+ if (kind === 'FUNCTIONAL') {
266
+ ont.addAxiom(isObject
267
+ ? new AX.OWLFunctionalObjectPropertyAxiom(prop)
268
+ : new AX.OWLFunctionalDataPropertyAxiom(prop));
269
+ } else if (isObject) {
270
+ const map = {
271
+ INVERSE_FUNCTIONAL: AX.OWLInverseFunctionalObjectPropertyAxiom,
272
+ TRANSITIVE: AX.OWLTransitiveObjectPropertyAxiom,
273
+ SYMMETRIC: AX.OWLSymmetricObjectPropertyAxiom,
274
+ ASYMMETRIC: AX.OWLAsymmetricObjectPropertyAxiom,
275
+ REFLEXIVE: AX.OWLReflexiveObjectPropertyAxiom,
276
+ IRREFLEXIVE: AX.OWLIrreflexiveObjectPropertyAxiom
277
+ };
278
+ if (map[kind]) ont.addAxiom(new map[kind](prop));
279
+ }
280
+ }
281
+ }
282
+
283
+ // ---------------------------------------------------------------------------
284
+ // Individuals
285
+ // ---------------------------------------------------------------------------
286
+
287
+ function parseIndividualAxioms(ctx) {
288
+ const { store, ont } = ctx;
289
+ // owl:NamedIndividual declarations + type assertions
290
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'NamedIndividual')) {
291
+ const ind = individualOf(ctx, s);
292
+ for (const t of store.objects(s, RDF + 'type')) {
293
+ if (t === OWL + 'NamedIndividual') continue;
294
+ const ce = classExpression(ctx, t);
295
+ if (ce) ont.addAxiom(new AX.OWLClassAssertionAxiom(ind, ce));
296
+ }
297
+ for (const o of store.objects(s, OWL + 'sameAs')) {
298
+ ont.addAxiom(new AX.OWLSameIndividualAxiom([ind, individualOf(ctx, o)]));
299
+ }
300
+ for (const o of store.objects(s, OWL + 'differentFrom')) {
301
+ ont.addAxiom(new AX.OWLDifferentIndividualsAxiom([ind, individualOf(ctx, o)]));
302
+ }
303
+ // Property assertions: any non-structural triple
304
+ for (const [_, p, o] of store.match(s, null, null)) {
305
+ if (p.startsWith(RDF) || p.startsWith(OWL + 'sameAs') || p.startsWith(OWL + 'differentFrom')) continue;
306
+ if (BUILTIN_ANNOTATION_PROPERTIES.has(p)) continue;
307
+ // Object property?
308
+ if (store.match(p, RDF + 'type', OWL + 'ObjectProperty').length) {
309
+ ont.addAxiom(new AX.OWLObjectPropertyAssertionAxiom(ind, new OWLObjectProperty(p), individualOf(ctx, o)));
310
+ } else if (store.match(p, RDF + 'type', OWL + 'DatatypeProperty').length) {
311
+ const lit = literalFromEncoding(o);
312
+ if (lit) ont.addAxiom(new AX.OWLDataPropertyAssertionAxiom(ind, new OWLDataProperty(p), lit));
313
+ }
314
+ }
315
+ }
316
+ }
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // N-ary constructs (AllDisjointClasses / AllDifferent / AllDisjointProperties)
320
+ // ---------------------------------------------------------------------------
321
+
322
+ function parseNAryConstructs(ctx) {
323
+ const { store, ont } = ctx;
324
+
325
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'AllDisjointClasses')) {
326
+ const members = store.objects(s, OWL + 'members')[0];
327
+ if (!members) continue;
328
+ const ces = listNodes(store, members).map(n => classExpression(ctx, n)).filter(Boolean);
329
+ if (ces.length >= 2) ont.addAxiom(new AX.OWLDisjointClassesAxiom(ces));
330
+ }
331
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'AllDifferent')) {
332
+ const members = store.objects(s, OWL + 'distinctMembers')[0]
333
+ || store.objects(s, OWL + 'members')[0];
334
+ if (!members) continue;
335
+ const inds = listNodes(store, members).map(n => individualOf(ctx, n));
336
+ if (inds.length >= 2) ont.addAxiom(new AX.OWLDifferentIndividualsAxiom(inds));
337
+ }
338
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'AllDisjointProperties')) {
339
+ const members = store.objects(s, OWL + 'members')[0];
340
+ if (!members) continue;
341
+ const nodes = listNodes(store, members);
342
+ const objProps = nodes.filter(n => store.match(n, RDF + 'type', OWL + 'ObjectProperty').length);
343
+ const dataProps = nodes.filter(n => store.match(n, RDF + 'type', OWL + 'DatatypeProperty').length);
344
+ if (objProps.length >= 2) {
345
+ ont.addAxiom(new AX.OWLDisjointObjectPropertiesAxiom(objProps.map(n => new OWLObjectProperty(n))));
346
+ }
347
+ if (dataProps.length >= 2) {
348
+ ont.addAxiom(new AX.OWLDisjointDataPropertiesAxiom(dataProps.map(n => new OWLDataProperty(n))));
349
+ }
350
+ }
351
+ // Equivalent classes / properties (n-ary via owl:equivalentClass / owl:equivalentProperty pairs)
352
+ // Handled by binary axioms above; multi-node equivalentClasses constructs go through
353
+ // owl:equivalentClass between named classes.
354
+ }
355
+
356
+ // ---------------------------------------------------------------------------
357
+ // Negative property assertions (owl:NegativePropertyAssertion reification)
358
+ // ---------------------------------------------------------------------------
359
+
360
+ function parseNegativeAssertions(ctx) {
361
+ const { store, ont } = ctx;
362
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'NegativePropertyAssertion')) {
363
+ const src = store.objects(s, OWL + 'sourceIndividual')[0];
364
+ const prop = store.objects(s, OWL + 'assertionProperty')[0];
365
+ if (!src || !prop) continue;
366
+ const tgtInd = store.objects(s, OWL + 'targetIndividual')[0];
367
+ const tgtVal = store.objects(s, OWL + 'targetValue')[0];
368
+ const subj = individualOf(ctx, src);
369
+ if (tgtInd) {
370
+ ont.addAxiom(new AX.OWLNegativeObjectPropertyAssertionAxiom(
371
+ subj, new OWLObjectProperty(prop), individualOf(ctx, tgtInd)));
372
+ } else if (tgtVal) {
373
+ const lit = literalFromEncoding(tgtVal);
374
+ if (lit) {
375
+ ont.addAxiom(new AX.OWLNegativeDataPropertyAssertionAxiom(
376
+ subj, new OWLDataProperty(prop), lit));
377
+ }
378
+ }
379
+ }
380
+ }
381
+
382
+ // ---------------------------------------------------------------------------
383
+ // Annotations on entities + axiom annotations (owl:Axiom reification)
384
+ // ---------------------------------------------------------------------------
385
+
386
+ function parseAnnotations(ctx) {
387
+ const { store, ont } = ctx;
388
+
389
+ // Entity-level annotations: any triple (s, p, o) where p is an AnnotationProperty
390
+ const annProps = new Set(BUILTIN_ANNOTATION_PROPERTIES);
391
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'AnnotationProperty')) {
392
+ if (!isAnon(s)) annProps.add(s);
393
+ }
394
+ for (const p of annProps) {
395
+ for (const [s, , o] of store.match(null, p, null)) {
396
+ // Skip ontology header (already collected as ontology annotations)
397
+ if (ont.headerSubject === s) continue;
398
+ const val = literalFromEncoding(o) || IRI.create(o);
399
+ ont.addAxiom(new AX.OWLAnnotationAssertionAxiom(
400
+ s, new OWLAnnotationProperty(p), val));
401
+ }
402
+ }
403
+
404
+ // Axiom annotations via owl:Axiom reification
405
+ for (const [s] of store.match(null, RDF + 'type', OWL + 'Axiom')) {
406
+ const src = store.objects(s, OWL + 'annotatedSource')[0];
407
+ const prop = store.objects(s, OWL + 'annotatedProperty')[0];
408
+ const tgt = store.objects(s, OWL + 'annotatedTarget')[0];
409
+ if (!src || !prop || tgt === undefined) continue;
410
+ // Reconstruct the annotated axiom
411
+ const ax = reconstructAxiom(ctx, src, prop, tgt);
412
+ if (!ax) continue;
413
+ // Collect the reified node's other properties as annotations on that axiom
414
+ for (const [_, p, o] of store.match(s, null, null)) {
415
+ if (p === RDF + 'type' || p === OWL + 'annotatedSource'
416
+ || p === OWL + 'annotatedProperty' || p === OWL + 'annotatedTarget') continue;
417
+ if (BUILTIN_ANNOTATION_PROPERTIES.has(p) || annProps.has(p)) {
418
+ const val = literalFromEncoding(o) || IRI.create(o);
419
+ ax.annotations.push({
420
+ property: new OWLAnnotationProperty(p),
421
+ value: val
422
+ });
423
+ }
424
+ }
425
+ ont.addAxiom(ax);
426
+ }
427
+ }
428
+
429
+ function reconstructAxiom(ctx, src, prop, tgt) {
430
+ const { store } = ctx;
431
+ // SubClassOf
432
+ if (prop === RDFS + 'subClassOf') {
433
+ const sub = classExpression(ctx, src);
434
+ const sup = classExpression(ctx, tgt);
435
+ return sub && sup ? new AX.OWLSubClassOfAxiom(sub, sup) : null;
436
+ }
437
+ // ClassAssertion
438
+ if (prop === RDF + 'type') {
439
+ const ind = individualOf(ctx, src);
440
+ const ce = classExpression(ctx, tgt);
441
+ return ce ? new AX.OWLClassAssertionAxiom(ind, ce) : null;
442
+ }
443
+ // SubObjectPropertyOf
444
+ if (prop === RDFS + 'subPropertyOf') {
445
+ if (store.match(src, RDF + 'type', OWL + 'ObjectProperty').length
446
+ || store.match(tgt, RDF + 'type', OWL + 'ObjectProperty').length) {
447
+ return new AX.OWLSubObjectPropertyOfAxiom(new OWLObjectProperty(src), new OWLObjectProperty(tgt));
448
+ }
449
+ return new AX.OWLSubDataPropertyOfAxiom(new OWLDataProperty(src), new OWLDataProperty(tgt));
450
+ }
451
+ // ObjectPropertyAssertion
452
+ if (store.match(prop, RDF + 'type', OWL + 'ObjectProperty').length) {
453
+ return new AX.OWLObjectPropertyAssertionAxiom(
454
+ individualOf(ctx, src), new OWLObjectProperty(prop), individualOf(ctx, tgt));
455
+ }
456
+ if (store.match(prop, RDF + 'type', OWL + 'DatatypeProperty').length) {
457
+ const lit = literalFromEncoding(tgt);
458
+ if (lit) {
459
+ return new AX.OWLDataPropertyAssertionAxiom(
460
+ individualOf(ctx, src), new OWLDataProperty(prop), lit);
461
+ }
462
+ }
463
+ return null;
464
+ }
465
+
466
+ // ---------------------------------------------------------------------------
467
+ // Class expression reconstruction from blank nodes
468
+ // ---------------------------------------------------------------------------
469
+
470
+ function classExpression(ctx, node) {
471
+ if (node === null || node === undefined) return null;
472
+ if (typeof node !== 'string') return null;
473
+
474
+ // Named class IRI
475
+ if (!isAnon(node) && !isLiteralEncoding(node)) {
476
+ return new OWLClass(node);
477
+ }
478
+ if (isLiteralEncoding(node)) return null;
479
+
480
+ // Blank node — look up its defining shape
481
+ const { store, ceCache } = ctx;
482
+ if (ceCache.has(node)) return ceCache.get(node);
483
+
484
+ const mark = (ce) => { ceCache.set(node, ce); return ce; };
485
+
486
+ // owl:Restriction
487
+ if (store.match(node, RDF + 'type', OWL + 'Restriction').length
488
+ || store.objects(node, OWL + 'onProperty').length) {
489
+ const onProp = store.objects(node, OWL + 'onProperty')[0];
490
+ if (!onProp) return mark(null);
491
+ const isObjProp = store.match(onProp, RDF + 'type', OWL + 'ObjectProperty').length > 0
492
+ || store.match(onProp, RDF + 'type', OWL + 'DatatypeProperty').length === 0;
493
+ const prop = isObjProp ? new OWLObjectProperty(onProp) : new OWLDataProperty(onProp);
494
+
495
+ const some = store.objects(node, OWL + 'someValuesFrom')[0];
496
+ if (some) {
497
+ return mark(isObjProp
498
+ ? new CE.OWLObjectSomeValuesFrom(prop, classExpression(ctx, some))
499
+ : new CE.OWLDataSomeValuesFrom(prop, dataRange(ctx, some)));
500
+ }
501
+ const all = store.objects(node, OWL + 'allValuesFrom')[0];
502
+ if (all) {
503
+ return mark(isObjProp
504
+ ? new CE.OWLObjectAllValuesFrom(prop, classExpression(ctx, all))
505
+ : new CE.OWLDataAllValuesFrom(prop, dataRange(ctx, all)));
506
+ }
507
+ const hasV = store.objects(node, OWL + 'hasValue')[0];
508
+ if (hasV !== undefined) {
509
+ if (isObjProp) {
510
+ return mark(new CE.OWLObjectHasValue(prop, individualOf(ctx, hasV)));
511
+ }
512
+ const lit = literalFromEncoding(hasV);
513
+ if (lit) return mark(new CE.OWLDataHasValue(prop, lit));
514
+ }
515
+ const hasSelf = store.objects(node, OWL + 'hasSelf')[0];
516
+ if (hasSelf && isObjProp) {
517
+ const v = literalFromEncoding(hasSelf);
518
+ if (v && (v.lexicalValue === 'true' || v.lexicalValue === '1')) {
519
+ return mark(new CE.OWLObjectHasSelf(prop));
520
+ }
521
+ }
522
+ // Cardinalities
523
+ const minQ = store.objects(node, OWL + 'minQualifiedCardinality')[0];
524
+ const maxQ = store.objects(node, OWL + 'maxQualifiedCardinality')[0];
525
+ const exactQ = store.objects(node, OWL + 'qualifiedCardinality')[0];
526
+ const min = store.objects(node, OWL + 'minCardinality')[0];
527
+ const max = store.objects(node, OWL + 'maxCardinality')[0];
528
+ const exact = store.objects(node, OWL + 'cardinality')[0];
529
+ const onCls = store.objects(node, OWL + 'onClass')[0];
530
+ const onDR = store.objects(node, OWL + 'onDataRange')[0];
531
+
532
+ const numeric = (enc) => {
533
+ const lit = literalFromEncoding(enc);
534
+ return lit ? parseInt(lit.lexicalValue, 10) : null;
535
+ };
536
+
537
+ const mkCard = (qualOrPlain, nEnc, fillerNode, isDataSide) => {
538
+ // qualOrPlain: 'min' | 'max' | 'exact'
539
+ const n = numeric(nEnc);
540
+ if (n === null) return null;
541
+ const filler = fillerNode
542
+ ? (isDataSide ? dataRange(ctx, fillerNode) : classExpression(ctx, fillerNode))
543
+ : null;
544
+ const T = CE.ClassExpressionType;
545
+ const suffix = qualOrPlain === 'min' ? 'MIN_CARDINALITY'
546
+ : qualOrPlain === 'max' ? 'MAX_CARDINALITY' : 'EXACT_CARDINALITY';
547
+ const qualSuffix = filler ? 'QUALIFIED_CARDINALITY' : null;
548
+ let type;
549
+ if (isDataSide) {
550
+ type = filler
551
+ ? T['DATA_' + qualOrPlain.toUpperCase() + '_CARDINALITY']
552
+ : T['DATA_' + suffix];
553
+ return new CE.OWLDataCardinalityRestriction(type, n, prop, filler);
554
+ }
555
+ type = filler
556
+ ? T['OBJECT_' + qualOrPlain.toUpperCase() + '_QUALIFIED_CARDINALITY']
557
+ : T['OBJECT_' + suffix];
558
+ return new CE.OWLObjectCardinalityRestriction(type, n, prop, filler);
559
+ };
560
+
561
+ if (minQ) return mark(isObjProp ? mkCard('min', minQ, onCls, false) : mkCard('min', minQ, onDR, true));
562
+ if (maxQ) return mark(isObjProp ? mkCard('max', maxQ, onCls, false) : mkCard('max', maxQ, onDR, true));
563
+ if (exactQ) return mark(isObjProp ? mkCard('exact', exactQ, onCls, false) : mkCard('exact', exactQ, onDR, true));
564
+ if (min) return mark(isObjProp ? mkCard('min', min, null, false) : mkCard('min', min, null, true));
565
+ if (max) return mark(isObjProp ? mkCard('max', max, null, false) : mkCard('max', max, null, true));
566
+ if (exact) return mark(isObjProp ? mkCard('exact', exact, null, false) : mkCard('exact', exact, null, true));
567
+ return mark(null);
568
+ }
569
+
570
+ // owl:intersectionOf
571
+ const inter = store.objects(node, OWL + 'intersectionOf')[0];
572
+ if (inter) {
573
+ const ops = listNodes(store, inter).map(n => classExpression(ctx, n)).filter(Boolean);
574
+ if (ops.length) return mark(new CE.OWLObjectIntersectionOf(ops));
575
+ }
576
+ // owl:unionOf
577
+ const union = store.objects(node, OWL + 'unionOf')[0];
578
+ if (union) {
579
+ const ops = listNodes(store, union).map(n => classExpression(ctx, n)).filter(Boolean);
580
+ if (ops.length) return mark(new CE.OWLObjectUnionOf(ops));
581
+ }
582
+ // owl:complementOf
583
+ const compl = store.objects(node, OWL + 'complementOf')[0];
584
+ if (compl) {
585
+ const op = classExpression(ctx, compl);
586
+ if (op) return mark(new CE.OWLObjectComplementOf(op));
587
+ }
588
+ // owl:oneOf (class)
589
+ const oneOf = store.objects(node, OWL + 'oneOf')[0];
590
+ if (oneOf) {
591
+ const ops = listNodes(store, oneOf);
592
+ // If elements are literals, this is a DataOneOf not class
593
+ const anyLit = ops.some(o => isLiteralEncoding(o));
594
+ if (!anyLit) {
595
+ const inds = ops.map(n => individualOf(ctx, n));
596
+ if (inds.length) return mark(new CE.OWLObjectOneOf(inds));
597
+ }
598
+ }
599
+ return mark(null);
600
+ }
601
+
602
+ // ---------------------------------------------------------------------------
603
+ // Data range reconstruction from blank nodes
604
+ // ---------------------------------------------------------------------------
605
+
606
+ function dataRange(ctx, node) {
607
+ if (node === null || node === undefined) return null;
608
+ if (typeof node !== 'string') return null;
609
+ if (!isAnon(node)) return new OWLDatatype(node);
610
+
611
+ const { store, drCache } = ctx;
612
+ if (drCache.has(node)) return drCache.get(node);
613
+ const mark = (dr) => { drCache.set(node, dr); return dr; };
614
+
615
+ // owl:onDatatype + owl:withRestrictions → DatatypeRestriction
616
+ const onDt = store.objects(node, OWL + 'onDatatype')[0];
617
+ if (onDt) {
618
+ const dt = new OWLDatatype(onDt);
619
+ const withR = store.objects(node, OWL + 'withRestrictions')[0];
620
+ if (withR) {
621
+ const frNodes = listNodes(store, withR);
622
+ const frs = [];
623
+ for (const fr of frNodes) {
624
+ for (const [_, p, o] of store.match(fr, null, null)) {
625
+ if (p === RDF + 'type') continue;
626
+ const facetName = FACET_NAMES[p];
627
+ const lit = literalFromEncoding(o);
628
+ if (facetName && lit) frs.push({ facet: IRI.create(p), value: lit });
629
+ }
630
+ }
631
+ if (frs.length) return mark(new CE.OWLDatatypeRestriction(dt, frs));
632
+ }
633
+ return mark(dt);
634
+ }
635
+ // Data intersectionOf / unionOf / complementOf / oneOf
636
+ const inter = store.objects(node, OWL + 'intersectionOf')[0];
637
+ if (inter) {
638
+ const ops = listNodes(store, inter).map(n => dataRange(ctx, n)).filter(Boolean);
639
+ if (ops.length) return mark(new CE.OWLDataIntersectionOf(ops));
640
+ }
641
+ const union = store.objects(node, OWL + 'unionOf')[0];
642
+ if (union) {
643
+ const ops = listNodes(store, union).map(n => dataRange(ctx, n)).filter(Boolean);
644
+ if (ops.length) return mark(new CE.OWLDataUnionOf(ops));
645
+ }
646
+ const compl = store.objects(node, OWL + 'datatypeComplementOf')[0];
647
+ if (compl) {
648
+ const op = dataRange(ctx, compl);
649
+ if (op) return mark(new CE.OWLDataComplementOf(op));
650
+ }
651
+ const oneOf = store.objects(node, OWL + 'oneOf')[0];
652
+ if (oneOf) {
653
+ const lits = listNodes(store, oneOf).map(literalFromEncoding).filter(Boolean);
654
+ if (lits.length) return mark(new CE.OWLDataOneOf(lits));
655
+ }
656
+ return mark(null);
657
+ }
658
+
659
+ // ---------------------------------------------------------------------------
660
+ // Individual (named or anonymous)
661
+ // ---------------------------------------------------------------------------
662
+
663
+ function individualOf(ctx, node) {
664
+ const { indCache } = ctx;
665
+ if (indCache.has(node)) return indCache.get(node);
666
+ const ind = isAnon(node)
667
+ ? new OWLAnonymousIndividual(node)
668
+ : new OWLNamedIndividual(node);
669
+ indCache.set(node, ind);
670
+ return ind;
671
+ }
672
+
673
+ // ---------------------------------------------------------------------------
674
+ // Helpers
675
+ // ---------------------------------------------------------------------------
676
+
677
+ function isAnon(node) {
678
+ return typeof node === 'string' && node.startsWith('_:');
679
+ }
680
+
681
+ function isLiteralEncoding(node) {
682
+ return typeof node === 'string' && node.startsWith('"');
683
+ }
684
+
685
+ function literalFromEncoding(encoded) {
686
+ if (!isLiteralEncoding(encoded)) return null;
687
+ try {
688
+ const parsed = parseLiteral(encoded);
689
+ if (!parsed) return null;
690
+ const dt = parsed.dt ? new OWLDatatype(parsed.dt) : null;
691
+ return new OWLLiteral(parsed.lex, dt, parsed.lang || null);
692
+ } catch {
693
+ return null;
694
+ }
695
+ }
696
+
697
+ function listNodes(store, head) {
698
+ if (!head) return [];
699
+ return store.listElements(head);
700
+ }
701
+
702
+ module.exports = { triplesToOntology };