@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.
- package/CHANGELOG.md +65 -0
- package/LICENSE +48 -0
- package/README.md +287 -0
- package/docs/API.md +654 -0
- package/docs/design/OWL2-/347/274/272/345/217/243/345/210/206/346/236/220.md +140 -0
- package/docs/owl2-rl/OWL2-RL-/350/247/204/345/210/231/347/273/206/345/210/231.md +168 -0
- package/docs/owl2-rl/owl2-profiles-spec-zh.html +1914 -0
- package/docs/owl2-rl/owl2-profiles-spec.html +1916 -0
- package/docs/owl2-rl/rl_rules.json +392 -0
- package/main.js +46 -0
- package/package.json +76 -0
- package/sample/README.md +74 -0
- package/sample/case1-ecommerce-risk.js +170 -0
- package/sample/case10-ecommerce-customer-service.js +454 -0
- package/sample/case2-family-swrl.js +147 -0
- package/sample/case3-pharma-safety.js +132 -0
- package/sample/case4-knowledge-publishing.js +111 -0
- package/sample/case5-obda-profiles.js +153 -0
- package/sample/case6-medical-ontology.js +152 -0
- package/sample/case7-manufacturing-ppr.js +155 -0
- package/sample/case8-real-medical-bfo-ogms.js +353 -0
- package/sample/case9-real-manufacturing-iao.js +360 -0
- package/sample/ontologies/bfo.owl +1715 -0
- package/sample/ontologies/iao.owl +8532 -0
- package/sample/ontologies/ogms.owl +4320 -0
- package/sample/ontologies/ro-core.owl +1010 -0
- package/scripts/verify-api.js +53 -0
- package/src/index.js +176 -0
- package/src/inference/OWL2ProfileReasoners.js +67 -0
- package/src/inference/OWL2RLReasoner.js +65 -0
- package/src/inference/ReasonerQueries.js +111 -0
- package/src/inference/SWRLReasoner.js +409 -0
- package/src/inference/TripleStore.js +82 -0
- package/src/inference/rdf.js +83 -0
- package/src/inference/rules/owl2el.js +33 -0
- package/src/inference/rules/owl2ql.js +29 -0
- package/src/inference/rules/owl2rl.js +823 -0
- package/src/io/FunctionalSyntaxParser.js +399 -0
- package/src/io/FunctionalSyntaxWriter.js +180 -0
- package/src/io/ManchesterSyntaxParser.js +398 -0
- package/src/io/OWLXMLParser.js +356 -0
- package/src/io/OntologyLoader.js +125 -0
- package/src/io/RDFGraphToOntology.js +702 -0
- package/src/io/RDFXMLParser.js +319 -0
- package/src/io/RDFXMLWriter.js +196 -0
- package/src/io/SWRLParser.js +150 -0
- package/src/io/TurtleParser.js +363 -0
- package/src/io/TurtleWriter.js +393 -0
- package/src/model/IRI.js +62 -0
- package/src/model/OWLAxiom.js +507 -0
- package/src/model/OWLClassExpression.js +242 -0
- package/src/model/OWLEntity.js +142 -0
- package/src/model/OWLLiteral.js +30 -0
- package/src/model/OWLModelManager.js +106 -0
- package/src/model/OWLOntology.js +119 -0
- package/src/model/SWRL.js +152 -0
- package/src/model/event/OWLModelManagerEvent.js +33 -0
- package/src/model/hierarchy/HierarchyProvider.js +214 -0
- package/src/profiles/OWL2Profiles.js +185 -0
- package/src/server/public/app.js +97 -0
- package/src/server/public/index.html +49 -0
- package/src/server/webServer.js +138 -0
- package/src/validation/GlobalRestrictionsValidator.js +172 -0
- package/test/core.test.js +111 -0
- package/test/exports-map.test.js +165 -0
- package/test/owl2/axioms.test.js +160 -0
- package/test/owl2/classExpressions.test.js +99 -0
- package/test/owl2/functional-syntax-writer.test.js +65 -0
- package/test/owl2/functional.test.js +113 -0
- package/test/owl2/global-restrictions.test.js +76 -0
- package/test/owl2/longtail.test.js +74 -0
- package/test/owl2/manchester-syntax.test.js +100 -0
- package/test/owl2/ontology-loader-imports.test.js +62 -0
- package/test/owl2/owlxml-parser.test.js +112 -0
- package/test/owl2/profile-reasoners.test.js +108 -0
- package/test/owl2/profiles.test.js +66 -0
- package/test/owl2/rdf-graph-to-ontology.test.js +177 -0
- package/test/owl2/rdfxml-punning.test.js +25 -0
- package/test/owl2/rdfxml-writer.test.js +55 -0
- package/test/owl2/rdfxml.test.js +91 -0
- package/test/owl2/reasoner-queries.test.js +58 -0
- package/test/owl2/sample-e2e.test.js +195 -0
- package/test/owl2/swrl-parser.test.js +60 -0
- package/test/owl2/swrl.test.js +131 -0
- package/test/owl2/turtle-writer.test.js +84 -0
- package/test/owl2/turtle.test.js +124 -0
- package/test/owl2rl/cax.test.js +57 -0
- package/test/owl2rl/cls.test.js +210 -0
- package/test/owl2rl/dt.test.js +72 -0
- package/test/owl2rl/eq.test.js +95 -0
- package/test/owl2rl/prp.test.js +198 -0
- package/test/owl2rl/scm.test.js +203 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// SWRL built-in evaluator + forward-chaining rule engine over TripleStore.
|
|
4
|
+
// Applies each rule repeatedly: for each match of the antecedent, asserts the
|
|
5
|
+
// consequent as new triples. Continues to fixpoint.
|
|
6
|
+
|
|
7
|
+
const { SWRLB_NS, AtomType } = require('../model/SWRL');
|
|
8
|
+
const { NS } = require('./rdf');
|
|
9
|
+
const RDF = NS.RDF, RDFS = NS.RDFS, OWL = NS.OWL, XSD = NS.XSD;
|
|
10
|
+
|
|
11
|
+
function isVar(t) { return t && typeof t === 'object' && t.iri && t.iri.toString && t.iri.toString().startsWith('urn:swrl:var:'); }
|
|
12
|
+
function varName(t) { return t.iri.toString().slice('urn:swrl:var:'.length); }
|
|
13
|
+
|
|
14
|
+
function lit(value, datatype) {
|
|
15
|
+
if (typeof value === 'string' && value.startsWith('"')) return value; // already encoded
|
|
16
|
+
if (typeof value === 'number') {
|
|
17
|
+
const dt = Number.isInteger(value) ? 'integer' : 'double';
|
|
18
|
+
return `"${value}"^^<${XSD}${dt}>`;
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === 'boolean') return `"${value}"^^<${XSD}boolean>`;
|
|
21
|
+
return `"${String(value)}"^^<${XSD}string>`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseLiteral(lex) {
|
|
25
|
+
if (typeof lex !== 'string' || !lex.startsWith('"')) return null;
|
|
26
|
+
const end = lex.lastIndexOf('"');
|
|
27
|
+
const v = lex.slice(1, end);
|
|
28
|
+
const m = lex.slice(end + 1).match(/^\^\^<([^>]+)>/);
|
|
29
|
+
const dt = m ? m[1] : null;
|
|
30
|
+
if (dt === XSD + 'integer' || dt === XSD + 'int' || dt === XSD + 'long' || dt === XSD + 'short' || dt === XSD + 'byte') return parseInt(v, 10);
|
|
31
|
+
if (dt === XSD + 'decimal' || dt === XSD + 'double' || dt === XSD + 'float') return parseFloat(v);
|
|
32
|
+
if (dt === XSD + 'boolean') return v === 'true' || v === '1';
|
|
33
|
+
return v;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class SWRLBuiltins {
|
|
37
|
+
static call(name, args) {
|
|
38
|
+
// args already evaluated (JS values); return boolean or assignment value
|
|
39
|
+
const n = name.startsWith(SWRLB_NS) ? name.slice(SWRLB_NS.length) : name;
|
|
40
|
+
switch (n) {
|
|
41
|
+
case 'equal': return args[0] === args[1];
|
|
42
|
+
case 'notEqual': return args[0] !== args[1];
|
|
43
|
+
case 'lessThan': return args[0] < args[1];
|
|
44
|
+
case 'lessThanOrEqual': return args[0] <= args[1];
|
|
45
|
+
case 'greaterThan': return args[0] > args[1];
|
|
46
|
+
case 'greaterThanOrEqual': return args[0] >= args[1];
|
|
47
|
+
case 'add': return args[0] + args[1];
|
|
48
|
+
case 'subtract': return args[0] - args[1];
|
|
49
|
+
case 'multiply': return args[0] * args[1];
|
|
50
|
+
case 'divide': return args[1] === 0 ? null : args[0] / args[1];
|
|
51
|
+
case 'integerDivide': return args[1] === 0 ? null : Math.floor(args[0] / args[1]);
|
|
52
|
+
case 'mod': return args[1] === 0 ? null : args[0] % args[1];
|
|
53
|
+
case 'pow': return Math.pow(args[0], args[1]);
|
|
54
|
+
case 'abs': return Math.abs(args[0]);
|
|
55
|
+
case 'ceiling': return Math.ceil(args[0]);
|
|
56
|
+
case 'floor': return Math.floor(args[0]);
|
|
57
|
+
case 'round': return Math.round(args[0]);
|
|
58
|
+
case 'roundHalfToEven': {
|
|
59
|
+
const x = args[0]; const f = Math.floor(x); const diff = x - f;
|
|
60
|
+
if (diff > 0.5) return f + 1;
|
|
61
|
+
if (diff < 0.5) return f;
|
|
62
|
+
return f % 2 === 0 ? f : f + 1;
|
|
63
|
+
}
|
|
64
|
+
case 'sqrt': return Math.sqrt(args[0]);
|
|
65
|
+
case 'sin': return Math.sin(args[0]);
|
|
66
|
+
case 'cos': return Math.cos(args[0]);
|
|
67
|
+
case 'tan': return Math.tan(args[0]);
|
|
68
|
+
case 'stringConcat': return args.slice(0, -1).join('') === args[args.length - 1] ? true : args.join('');
|
|
69
|
+
case 'substring': {
|
|
70
|
+
const s = String(args[0]); const start = args[1]; const len = args[2];
|
|
71
|
+
return s.substr(start, len);
|
|
72
|
+
}
|
|
73
|
+
case 'stringLength': return String(args[0]).length;
|
|
74
|
+
case 'normalizeSpace': return String(args[0]).trim().replace(/\s+/g, ' ');
|
|
75
|
+
case 'upperCase': return String(args[0]).toUpperCase();
|
|
76
|
+
case 'lowerCase': return String(args[0]).toLowerCase();
|
|
77
|
+
case 'contains': return String(args[0]).includes(String(args[1]));
|
|
78
|
+
case 'startsWith': return String(args[0]).startsWith(String(args[1]));
|
|
79
|
+
case 'endsWith': return String(args[0]).endsWith(String(args[1]));
|
|
80
|
+
case 'matches': return new RegExp(args[1]).test(String(args[0]));
|
|
81
|
+
case 'replace': return String(args[0]).replace(new RegExp(args[1], 'g'), String(args[2]));
|
|
82
|
+
// --- string long-tail ---
|
|
83
|
+
case 'stringEqualIgnoreCase': return String(args[0]).toLowerCase() === String(args[1]).toLowerCase();
|
|
84
|
+
case 'translate': {
|
|
85
|
+
const s = String(args[0]), from = String(args[1]), to = String(args[2]);
|
|
86
|
+
let out = '';
|
|
87
|
+
for (const ch of s) {
|
|
88
|
+
const idx = from.indexOf(ch);
|
|
89
|
+
if (idx < 0) { out += ch; continue; }
|
|
90
|
+
if (idx < to.length) out += to[idx];
|
|
91
|
+
// if from longer than to at this idx, char is deleted
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
case 'substringBefore': {
|
|
96
|
+
const s = String(args[0]), m = String(args[1]);
|
|
97
|
+
const i = s.indexOf(m);
|
|
98
|
+
return i < 0 ? '' : s.slice(0, i);
|
|
99
|
+
}
|
|
100
|
+
case 'substringAfter': {
|
|
101
|
+
const s = String(args[0]), m = String(args[1]);
|
|
102
|
+
const i = s.indexOf(m);
|
|
103
|
+
return i < 0 ? '' : s.slice(i + m.length);
|
|
104
|
+
}
|
|
105
|
+
// --- boolean ---
|
|
106
|
+
case 'booleanNot': return !args[0];
|
|
107
|
+
// --- list operations (lists as JS arrays) ---
|
|
108
|
+
case 'listConcat': {
|
|
109
|
+
const lists = args.slice(0, -1);
|
|
110
|
+
const result = lists.reduce((acc, l) => acc.concat(l), []);
|
|
111
|
+
const last = args[args.length - 1];
|
|
112
|
+
if (Array.isArray(last)) return JSON.stringify(result) === JSON.stringify(last) ? true : result;
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
case 'listIntersection': {
|
|
116
|
+
const a = Array.isArray(args[0]) ? args[0] : [];
|
|
117
|
+
const b = Array.isArray(args[1]) ? args[1] : [];
|
|
118
|
+
const inter = a.filter(x => b.includes(x));
|
|
119
|
+
if (args.length > 2 && Array.isArray(args[2])) {
|
|
120
|
+
return JSON.stringify(inter) === JSON.stringify(args[2]) ? true : inter;
|
|
121
|
+
}
|
|
122
|
+
return inter;
|
|
123
|
+
}
|
|
124
|
+
case 'listSubtraction': {
|
|
125
|
+
const a = Array.isArray(args[0]) ? args[0] : [];
|
|
126
|
+
const b = Array.isArray(args[1]) ? args[1] : [];
|
|
127
|
+
const diff = a.filter(x => !b.includes(x));
|
|
128
|
+
if (args.length > 2 && Array.isArray(args[2])) {
|
|
129
|
+
return JSON.stringify(diff) === JSON.stringify(args[2]) ? true : diff;
|
|
130
|
+
}
|
|
131
|
+
return diff;
|
|
132
|
+
}
|
|
133
|
+
case 'member': return Array.isArray(args[1]) && args[1].includes(args[0]);
|
|
134
|
+
case 'length': return Array.isArray(args[0]) ? args[0].length : 0;
|
|
135
|
+
case 'first': return Array.isArray(args[0]) && args[0].length ? args[0][0] : null;
|
|
136
|
+
case 'rest': return Array.isArray(args[0]) ? args[0].slice(1) : [];
|
|
137
|
+
case 'sublist': {
|
|
138
|
+
const list = Array.isArray(args[0]) ? args[0] : [];
|
|
139
|
+
const sub = Array.isArray(args[1]) ? args[1] : [];
|
|
140
|
+
if (!sub.length) return true;
|
|
141
|
+
outer: for (let i = 0; i + sub.length <= list.length; i++) {
|
|
142
|
+
for (let j = 0; j < sub.length; j++) if (list[i + j] !== sub[j]) continue outer;
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
case 'empty': return Array.isArray(args[0]) && args[0].length === 0;
|
|
148
|
+
default: return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Forward-chaining SWRL execution. rule: SWRLRule; store: TripleStore.
|
|
154
|
+
// atom property/class references are OWL entities with .iri (IRI instance).
|
|
155
|
+
// arg values: SWRLVariable (iri urn:swrl:var:NAME), OWLNamedIndividual, or literal.
|
|
156
|
+
class SWRLReasoner {
|
|
157
|
+
constructor(store) {
|
|
158
|
+
this.store = store;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Run rules until fixpoint. Returns number of new facts added. */
|
|
162
|
+
run(rules) {
|
|
163
|
+
let totalNew = 0;
|
|
164
|
+
let changed = true;
|
|
165
|
+
let rounds = 0;
|
|
166
|
+
while (changed && rounds++ < 200) {
|
|
167
|
+
changed = false;
|
|
168
|
+
for (const rule of rules) {
|
|
169
|
+
const newFacts = this._applyRule(rule);
|
|
170
|
+
if (newFacts > 0) { changed = true; totalNew += newFacts; }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return totalNew;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_applyRule(rule) {
|
|
177
|
+
// Gather all body matches via backtracking over atoms.
|
|
178
|
+
const bindings = [{}];
|
|
179
|
+
for (const atom of rule.body) {
|
|
180
|
+
const next = [];
|
|
181
|
+
for (const b of bindings) {
|
|
182
|
+
for (const nb of this._matchAtom(atom, b)) next.push(nb);
|
|
183
|
+
}
|
|
184
|
+
bindings.length = 0;
|
|
185
|
+
bindings.push(...next);
|
|
186
|
+
if (bindings.length === 0) return 0;
|
|
187
|
+
}
|
|
188
|
+
let added = 0;
|
|
189
|
+
for (const b of bindings) {
|
|
190
|
+
for (const atom of rule.head) {
|
|
191
|
+
if (this._emitAtom(atom, b)) added++;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return added;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_resolve(t, binding) {
|
|
198
|
+
if (isVar(t)) {
|
|
199
|
+
const k = varName(t);
|
|
200
|
+
if (k in binding) return binding[k];
|
|
201
|
+
return { var: k };
|
|
202
|
+
}
|
|
203
|
+
// OWLNamedIndividual or literal
|
|
204
|
+
if (t && t.iri) return t.iri.toString();
|
|
205
|
+
if (t && typeof t === 'object' && 'lexicalValue' in t) {
|
|
206
|
+
const dt = t.datatype ? t.datatype.iri.toString() : XSD + 'string';
|
|
207
|
+
return `"${t.lexicalValue}"^^<${dt}>`;
|
|
208
|
+
}
|
|
209
|
+
return String(t);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
_bind(binding, varKey, value) {
|
|
213
|
+
if (varKey in binding) return binding[varKey] === value ? binding : null;
|
|
214
|
+
const nb = { ...binding, [varKey]: value };
|
|
215
|
+
return nb;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
_matchAtom(atom, binding) {
|
|
219
|
+
const S = this.store;
|
|
220
|
+
const results = [];
|
|
221
|
+
switch (atom.type) {
|
|
222
|
+
case AtomType.CLASS: {
|
|
223
|
+
const cls = atom.classExpression && atom.classExpression.iri
|
|
224
|
+
? atom.classExpression.iri.toString()
|
|
225
|
+
: String(atom.classExpression);
|
|
226
|
+
const arg = this._resolve(atom.arg, binding);
|
|
227
|
+
if (arg.var) {
|
|
228
|
+
// bind all instances
|
|
229
|
+
for (const [s] of S.match(null, RDF + 'type', cls)) {
|
|
230
|
+
const nb = this._bind(binding, arg.var, s);
|
|
231
|
+
if (nb) results.push(nb);
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
if (S.match(arg, RDF + 'type', cls).length > 0) results.push(binding);
|
|
235
|
+
}
|
|
236
|
+
return results;
|
|
237
|
+
}
|
|
238
|
+
case AtomType.OBJECT_PROPERTY: {
|
|
239
|
+
const p = atom.property.iri.toString();
|
|
240
|
+
const a1 = this._resolve(atom.arg1, binding);
|
|
241
|
+
const a2 = this._resolve(atom.arg2, binding);
|
|
242
|
+
const subjects = a1.var ? S.match(null, p, null).map(t => t[0]) : [a1];
|
|
243
|
+
for (const s of subjects) {
|
|
244
|
+
const objs = S.match(s, p, null).map(t => t[2]);
|
|
245
|
+
for (const o of objs) {
|
|
246
|
+
let nb = binding;
|
|
247
|
+
if (a1.var) { nb = this._bind(nb, a1.var, s); if (!nb) continue; }
|
|
248
|
+
if (a2.var) { nb = this._bind(nb, a2.var, o); if (!nb) continue; }
|
|
249
|
+
else if (o !== a2) continue;
|
|
250
|
+
results.push(nb);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return results;
|
|
254
|
+
}
|
|
255
|
+
case AtomType.DATA_PROPERTY: {
|
|
256
|
+
const p = atom.property.iri.toString();
|
|
257
|
+
const a1 = this._resolve(atom.arg1, binding);
|
|
258
|
+
const a2 = this._resolve(atom.arg2, binding);
|
|
259
|
+
const subjects = a1.var ? S.match(null, p, null).map(t => t[0]) : [a1];
|
|
260
|
+
for (const s of subjects) {
|
|
261
|
+
const objs = S.match(s, p, null).map(t => t[2]);
|
|
262
|
+
for (const o of objs) {
|
|
263
|
+
let nb = binding;
|
|
264
|
+
if (a1.var) { nb = this._bind(nb, a1.var, s); if (!nb) continue; }
|
|
265
|
+
if (a2.var) { nb = this._bind(nb, a2.var, o); if (!nb) continue; }
|
|
266
|
+
else if (o !== a2) continue;
|
|
267
|
+
results.push(nb);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return results;
|
|
271
|
+
}
|
|
272
|
+
case AtomType.SAME_AS: {
|
|
273
|
+
const a1 = this._resolve(atom.arg1, binding);
|
|
274
|
+
const a2 = this._resolve(atom.arg2, binding);
|
|
275
|
+
if (a1.var && a2.var) {
|
|
276
|
+
// try all sameAs pairs
|
|
277
|
+
for (const [s, , o] of S.match(null, OWL + 'sameAs', null)) {
|
|
278
|
+
const nb1 = this._bind(binding, a1.var, s);
|
|
279
|
+
if (nb1) { const nb2 = this._bind(nb1, a2.var, o); if (nb2) results.push(nb2); }
|
|
280
|
+
}
|
|
281
|
+
return results;
|
|
282
|
+
}
|
|
283
|
+
if (a1.var) {
|
|
284
|
+
const vals = S.match(null, OWL + 'sameAs', a2).map(t => t[0])
|
|
285
|
+
.concat(S.match(a2, OWL + 'sameAs', null).map(t => t[2]));
|
|
286
|
+
for (const v of vals) { const nb = this._bind(binding, a1.var, v); if (nb) results.push(nb); }
|
|
287
|
+
return results;
|
|
288
|
+
}
|
|
289
|
+
if (a2.var) {
|
|
290
|
+
const vals = S.match(null, OWL + 'sameAs', a1).map(t => t[0])
|
|
291
|
+
.concat(S.match(a1, OWL + 'sameAs', null).map(t => t[2]));
|
|
292
|
+
for (const v of vals) { const nb = this._bind(binding, a2.var, v); if (nb) results.push(nb); }
|
|
293
|
+
return results;
|
|
294
|
+
}
|
|
295
|
+
if (a1 === a2 || S.match(a1, OWL + 'sameAs', a2).length > 0 || S.match(a2, OWL + 'sameAs', a1).length > 0) {
|
|
296
|
+
results.push(binding);
|
|
297
|
+
}
|
|
298
|
+
return results;
|
|
299
|
+
}
|
|
300
|
+
case AtomType.DIFFERENT_FROM: {
|
|
301
|
+
const a1 = this._resolve(atom.arg1, binding);
|
|
302
|
+
const a2 = this._resolve(atom.arg2, binding);
|
|
303
|
+
if (a1.var || a2.var) return results; // open world: skip
|
|
304
|
+
if (a1 !== a2 && (S.match(a1, OWL + 'differentFrom', a2).length > 0 || S.match(a2, OWL + 'differentFrom', a1).length > 0)) {
|
|
305
|
+
results.push(binding);
|
|
306
|
+
}
|
|
307
|
+
return results;
|
|
308
|
+
}
|
|
309
|
+
case AtomType.BUILTIN: {
|
|
310
|
+
// Evaluate: if last arg is a variable, bind result; else check truthiness.
|
|
311
|
+
const args = atom.args.map(a => {
|
|
312
|
+
const r = this._resolve(a, binding);
|
|
313
|
+
return r;
|
|
314
|
+
});
|
|
315
|
+
// find first unbound
|
|
316
|
+
let unboundIdx = -1;
|
|
317
|
+
const values = [];
|
|
318
|
+
for (let i = 0; i < args.length; i++) {
|
|
319
|
+
if (args[i] && args[i].var) { unboundIdx = i; values.push(null); }
|
|
320
|
+
else values.push(parseLiteral(args[i]) !== null ? parseLiteral(args[i]) : args[i]);
|
|
321
|
+
}
|
|
322
|
+
if (unboundIdx === -1) {
|
|
323
|
+
// all bound: predicate form (equal, lessThan, ...)
|
|
324
|
+
const res = SWRLBuiltins.call(atom.builtin.toString(), values);
|
|
325
|
+
if (res === true) results.push(binding);
|
|
326
|
+
} else {
|
|
327
|
+
// compute builtin with all-but-last bound, bind last
|
|
328
|
+
const vals = values.slice();
|
|
329
|
+
vals.splice(unboundIdx, 1);
|
|
330
|
+
const res = SWRLBuiltins.call(atom.builtin.toString(), vals);
|
|
331
|
+
if (res !== null && res !== false && res !== true) {
|
|
332
|
+
const key = args[unboundIdx].var;
|
|
333
|
+
const dt = typeof res === 'number' ? (Number.isInteger(res) ? XSD + 'integer' : XSD + 'double') : XSD + 'string';
|
|
334
|
+
const enc = `"${res}"^^<${dt}>`;
|
|
335
|
+
const nb = this._bind(binding, key, enc);
|
|
336
|
+
if (nb) results.push(nb);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return results;
|
|
340
|
+
}
|
|
341
|
+
default:
|
|
342
|
+
return results;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
_emitAtom(atom, binding) {
|
|
347
|
+
const S = this.store;
|
|
348
|
+
switch (atom.type) {
|
|
349
|
+
case AtomType.CLASS: {
|
|
350
|
+
const cls = atom.classExpression && atom.classExpression.iri
|
|
351
|
+
? atom.classExpression.iri.toString()
|
|
352
|
+
: String(atom.classExpression);
|
|
353
|
+
const arg = this._resolve(atom.arg, binding);
|
|
354
|
+
if (arg.var) return false; // cannot assert unbound
|
|
355
|
+
if (S.match(arg, RDF + 'type', cls).length === 0) {
|
|
356
|
+
S.add(arg, RDF + 'type', cls);
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
case AtomType.OBJECT_PROPERTY: {
|
|
362
|
+
const p = atom.property.iri.toString();
|
|
363
|
+
const s = this._resolve(atom.arg1, binding);
|
|
364
|
+
const o = this._resolve(atom.arg2, binding);
|
|
365
|
+
if (s.var || o.var) return false;
|
|
366
|
+
if (S.match(s, p, o).length === 0) {
|
|
367
|
+
S.add(s, p, o);
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
case AtomType.DATA_PROPERTY: {
|
|
373
|
+
const p = atom.property.iri.toString();
|
|
374
|
+
const s = this._resolve(atom.arg1, binding);
|
|
375
|
+
const o = this._resolve(atom.arg2, binding);
|
|
376
|
+
if (s.var || o.var) return false;
|
|
377
|
+
if (S.match(s, p, o).length === 0) {
|
|
378
|
+
S.add(s, p, o);
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
case AtomType.SAME_AS: {
|
|
384
|
+
const a = this._resolve(atom.arg1, binding);
|
|
385
|
+
const b = this._resolve(atom.arg2, binding);
|
|
386
|
+
if (a.var || b.var) return false;
|
|
387
|
+
if (S.match(a, OWL + 'sameAs', b).length === 0) {
|
|
388
|
+
S.add(a, OWL + 'sameAs', b);
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
case AtomType.DIFFERENT_FROM: {
|
|
394
|
+
const a = this._resolve(atom.arg1, binding);
|
|
395
|
+
const b = this._resolve(atom.arg2, binding);
|
|
396
|
+
if (a.var || b.var) return false;
|
|
397
|
+
if (S.match(a, OWL + 'differentFrom', b).length === 0) {
|
|
398
|
+
S.add(a, OWL + 'differentFrom', b);
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
default:
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
module.exports = { SWRLReasoner, SWRLBuiltins };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// TripleStore — an in-memory RDF triple store with simple pattern matching.
|
|
5
|
+
// Triples are [subject, predicate, object] string arrays. Literals are kept
|
|
6
|
+
// in a canonical lexical form like "value"^^<datatype> or "value"@lang .
|
|
7
|
+
// Blank nodes (anonymous class expressions) use _:bN identifiers.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
const SEP = String.fromCharCode(1); // field separator safe for IRIs/literals
|
|
11
|
+
|
|
12
|
+
class TripleStore {
|
|
13
|
+
constructor() {
|
|
14
|
+
this._triples = new Set(); // canonical key s+SEP+p+SEP+o
|
|
15
|
+
this._byPredicate = new Map(); // p -> Set(tripleKey)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static key(s, p, o) { return s + SEP + p + SEP + o; }
|
|
19
|
+
|
|
20
|
+
add(s, p, o) {
|
|
21
|
+
const k = TripleStore.key(String(s), String(p), String(o));
|
|
22
|
+
if (this._triples.has(k)) return false;
|
|
23
|
+
this._triples.add(k);
|
|
24
|
+
if (!this._byPredicate.has(p)) this._byPredicate.set(p, new Set());
|
|
25
|
+
this._byPredicate.get(p).add(k);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
has(s, p, o) {
|
|
30
|
+
return this._triples.has(TripleStore.key(String(s), String(p), String(o)));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
size() { return this._triples.size; }
|
|
34
|
+
|
|
35
|
+
/** All triples as [s,p,o] arrays. */
|
|
36
|
+
all() {
|
|
37
|
+
return [...this._triples].map(k => k.split(SEP));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Match a pattern; null fields are wildcards. Returns array of [s,p,o]. */
|
|
41
|
+
match(s = null, p = null, o = null) {
|
|
42
|
+
let candidates;
|
|
43
|
+
if (p !== null) {
|
|
44
|
+
if (!this._byPredicate.has(p)) return [];
|
|
45
|
+
candidates = [...this._byPredicate.get(p)].map(k => k.split(SEP));
|
|
46
|
+
} else {
|
|
47
|
+
candidates = this.all();
|
|
48
|
+
}
|
|
49
|
+
return candidates.filter(([ts, tp, to]) =>
|
|
50
|
+
(s === null || ts === s) && (o === null || to === o));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Distinct objects for a given subject+predicate. */
|
|
54
|
+
objects(s, p) {
|
|
55
|
+
return this.match(s, p, null).map(t => t[2]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Distinct subjects for a given predicate+object. */
|
|
59
|
+
subjects(p, o) {
|
|
60
|
+
return this.match(null, p, o).map(t => t[0]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Resolve an RDF list head to its element array (rdf:first/rdf:rest chain). */
|
|
64
|
+
listElements(head) {
|
|
65
|
+
const RDF_FIRST = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first';
|
|
66
|
+
const RDF_REST = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest';
|
|
67
|
+
const RDF_NIL = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil';
|
|
68
|
+
const out = [];
|
|
69
|
+
let cur = head;
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
while (cur && cur !== RDF_NIL && !seen.has(cur)) {
|
|
72
|
+
seen.add(cur);
|
|
73
|
+
const first = this.objects(cur, RDF_FIRST)[0];
|
|
74
|
+
if (first === undefined) break;
|
|
75
|
+
out.push(first);
|
|
76
|
+
cur = this.objects(cur, RDF_REST)[0];
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { TripleStore };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Shared RDF/OWL namespace constants and small literal helpers used by rules.
|
|
4
|
+
|
|
5
|
+
const NS = Object.freeze({
|
|
6
|
+
RDF: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
|
7
|
+
RDFS: 'http://www.w3.org/2000/01/rdf-schema#',
|
|
8
|
+
OWL: 'http://www.w3.org/2002/07/owl#',
|
|
9
|
+
XSD: 'http://www.w3.org/2001/XMLSchema#'
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const P = Object.freeze({
|
|
13
|
+
type: NS.RDF + 'type',
|
|
14
|
+
subClassOf: NS.RDFS + 'subClassOf',
|
|
15
|
+
subPropertyOf: NS.RDFS + 'subPropertyOf',
|
|
16
|
+
domain: NS.RDFS + 'domain',
|
|
17
|
+
range: NS.RDFS + 'range',
|
|
18
|
+
sameAs: NS.OWL + 'sameAs',
|
|
19
|
+
differentFrom: NS.OWL + 'differentFrom',
|
|
20
|
+
equivalentClass: NS.OWL + 'equivalentClass',
|
|
21
|
+
equivalentProperty: NS.OWL + 'equivalentProperty',
|
|
22
|
+
disjointWith: NS.OWL + 'disjointWith',
|
|
23
|
+
propertyDisjointWith: NS.OWL + 'propertyDisjointWith',
|
|
24
|
+
inverseOf: NS.OWL + 'inverseOf',
|
|
25
|
+
propertyChainAxiom: NS.OWL + 'propertyChainAxiom',
|
|
26
|
+
hasKey: NS.OWL + 'hasKey',
|
|
27
|
+
intersectionOf: NS.OWL + 'intersectionOf',
|
|
28
|
+
unionOf: NS.OWL + 'unionOf',
|
|
29
|
+
complementOf: NS.OWL + 'complementOf',
|
|
30
|
+
someValuesFrom: NS.OWL + 'someValuesFrom',
|
|
31
|
+
allValuesFrom: NS.OWL + 'allValuesFrom',
|
|
32
|
+
hasValue: NS.OWL + 'hasValue',
|
|
33
|
+
onProperty: NS.OWL + 'onProperty',
|
|
34
|
+
onClass: NS.OWL + 'onClass',
|
|
35
|
+
maxCardinality: NS.OWL + 'maxCardinality',
|
|
36
|
+
maxQualifiedCardinality: NS.OWL + 'maxQualifiedCardinality',
|
|
37
|
+
oneOf: NS.OWL + 'oneOf',
|
|
38
|
+
members: NS.OWL + 'members',
|
|
39
|
+
distinctMembers: NS.OWL + 'distinctMembers',
|
|
40
|
+
sourceIndividual: NS.OWL + 'sourceIndividual',
|
|
41
|
+
assertionProperty: NS.OWL + 'assertionProperty',
|
|
42
|
+
targetIndividual: NS.OWL + 'targetIndividual',
|
|
43
|
+
targetValue: NS.OWL + 'targetValue'
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const C = Object.freeze({
|
|
47
|
+
Class: NS.OWL + 'Class',
|
|
48
|
+
Thing: NS.OWL + 'Thing',
|
|
49
|
+
Nothing: NS.OWL + 'Nothing',
|
|
50
|
+
ObjectProperty: NS.OWL + 'ObjectProperty',
|
|
51
|
+
DatatypeProperty: NS.OWL + 'DatatypeProperty',
|
|
52
|
+
AnnotationProperty: NS.OWL + 'AnnotationProperty',
|
|
53
|
+
FunctionalProperty: NS.OWL + 'FunctionalProperty',
|
|
54
|
+
InverseFunctionalProperty: NS.OWL + 'InverseFunctionalProperty',
|
|
55
|
+
IrreflexiveProperty: NS.OWL + 'IrreflexiveProperty',
|
|
56
|
+
SymmetricProperty: NS.OWL + 'SymmetricProperty',
|
|
57
|
+
AsymmetricProperty: NS.OWL + 'AsymmetricProperty',
|
|
58
|
+
TransitiveProperty: NS.OWL + 'TransitiveProperty',
|
|
59
|
+
AllDifferent: NS.OWL + 'AllDifferent',
|
|
60
|
+
AllDisjointClasses: NS.OWL + 'AllDisjointClasses',
|
|
61
|
+
AllDisjointProperties: NS.OWL + 'AllDisjointProperties',
|
|
62
|
+
Datatype: NS.RDFS + 'Datatype'
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/** Is this object term a typed literal "lex"^^<dt> ? */
|
|
66
|
+
function isLiteral(o) {
|
|
67
|
+
return typeof o === 'string' && o.startsWith('"');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Parse "lex"^^<dt> or "lex"@lang -> {lex, dt, lang}. */
|
|
71
|
+
function parseLiteral(o) {
|
|
72
|
+
if (!isLiteral(o)) return null;
|
|
73
|
+
const m = /^"((?:[^"\\]|\\.)*)"(?:\^\^<([^>]+)>|@([A-Za-z-]+))?$/.exec(o);
|
|
74
|
+
if (!m) return { lex: o.slice(1, -1), dt: null, lang: null };
|
|
75
|
+
return { lex: m[1], dt: m[2] || null, lang: m[3] || null };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Canonical typed literal string. */
|
|
79
|
+
function literal(lex, dt) {
|
|
80
|
+
return `"${lex}"^^<${dt}>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { NS, P, C, isLiteral, parseLiteral, literal };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// inference/rules/owl2el — OWL 2 EL rule subset.
|
|
5
|
+
// EL is designed for large TBoxes (classification in polynomial time). It
|
|
6
|
+
// supports existential restrictions (someValuesFrom), intersection, nominals
|
|
7
|
+
// are excluded. EL excludes: disjunction (unionOf), complement, allValuesFrom
|
|
8
|
+
// propagation is NOT sound for classification, inverseOf, cardinality.
|
|
9
|
+
// The rule subset below is sound for EL classification.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
// Rule IDs from owl2rl.js that are sound for EL.
|
|
13
|
+
// EL KEEPS: someValuesFrom, intersection, subClassOf, subPropertyOf chains,
|
|
14
|
+
// domain/range (existential form), transitivity (via chains).
|
|
15
|
+
// EL DROPS: cls-avf (allValuesFrom), cls-int1 is kept (intersection), union
|
|
16
|
+
// handling, complement, equality-heavy rules are partially kept for nominals
|
|
17
|
+
// but sameAs reasoning is excluded in the minimal EL profile.
|
|
18
|
+
const EL_RULE_IDS = [
|
|
19
|
+
// Existential restrictions
|
|
20
|
+
'cls-svf1', 'cls-svf2',
|
|
21
|
+
// Intersection
|
|
22
|
+
'cls-int1', 'cls-int2',
|
|
23
|
+
// Class hierarchy
|
|
24
|
+
'cax-sco', 'cax-eqc1', 'cax-eqc2',
|
|
25
|
+
// Property hierarchy + chains
|
|
26
|
+
'prp-spo1', 'prp-spo2', // spo2 = property chain
|
|
27
|
+
'prp-dom', 'prp-rng',
|
|
28
|
+
'prp-trp', // transitivity (EL supports it)
|
|
29
|
+
// Top/bottom
|
|
30
|
+
'cls-thing', 'cls-nothing1'
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
module.exports = { EL_RULE_IDS };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// inference/rules/owl2ql — OWL 2 QL rule subset.
|
|
5
|
+
// QL is designed for query answering over large ABoxes; it supports a
|
|
6
|
+
// restricted TBox (subClassOf with limited superclass forms, subPropertyOf,
|
|
7
|
+
// domain/range). The sound+complete rule subset excludes equality (sameAs),
|
|
8
|
+
// functional properties, and complex class expressions on the superclass side.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
// Rule IDs from owl2rl.js that are sound for QL.
|
|
12
|
+
// QL deliberately EXCLUDES: eq-* (equality), prp-fp/prp-ifp (functionality),
|
|
13
|
+
// cax-dw (disjointness-driven inconsistency is fine to keep for consistency
|
|
14
|
+
// checking but not needed for query answering), cls-* complex forms.
|
|
15
|
+
const QL_RULE_IDS = [
|
|
16
|
+
// Class hierarchy
|
|
17
|
+
'cls-svf1', 'cls-svf2', // someValuesFrom
|
|
18
|
+
'cls-avf', // allValuesFrom (kept: sound, cheap)
|
|
19
|
+
'cax-sco', // subclass inheritance of membership
|
|
20
|
+
'cax-eqc1', 'cax-eqc2', // equivalentClass membership propagation
|
|
21
|
+
// Property hierarchy
|
|
22
|
+
'prp-spo1', // subPropertyOf propagation
|
|
23
|
+
'prp-dom', 'prp-rng', // domain / range typing
|
|
24
|
+
'prp-inv1', 'prp-inv2', // inverseOf
|
|
25
|
+
'prp-eqp1', 'prp-eqp2', // equivalentProperty
|
|
26
|
+
'cax-adc' // differentIndividuals via pairwise differentFrom
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
module.exports = { QL_RULE_IDS };
|