@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,152 @@
1
+ 'use strict';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // SWRL — Semantic Web Rule Language (https://www.w3.org/Submission/SWRL/).
5
+ // A SWRL rule has the form: antecedent ⇒ consequent, where both sides are
6
+ // conjunctions of atoms. Supported atoms:
7
+ // ClassAtom(classExpr, i-var)
8
+ // ObjectPropertyAtom(prop, i-var, i-var)
9
+ // DataPropertyAtom(prop, i-var, d-var)
10
+ // SameAsAtom(i-var, i-var) DifferentFromAtom(i-var, i-var)
11
+ // BuiltInAtom(builtin, args...) DataRangeAtom(d-var, dataRange)
12
+ // Variables start with ? (e.g. ?x). IRIs/prefixed names identify entities.
13
+ // ---------------------------------------------------------------------------
14
+
15
+ const { OWLClass, OWLObjectProperty, OWLDataProperty, OWLNamedIndividual } = require('../model/OWLEntity');
16
+ const { IRI } = require('../model/IRI');
17
+ const CE = require('../model/OWLClassExpression');
18
+
19
+ const AtomType = Object.freeze({
20
+ CLASS: 'ClassAtom',
21
+ OBJECT_PROPERTY: 'ObjectPropertyAtom',
22
+ DATA_PROPERTY: 'DataPropertyAtom',
23
+ SAME_AS: 'SameAsAtom',
24
+ DIFFERENT_FROM: 'DifferentFromAtom',
25
+ BUILTIN: 'BuiltInAtom',
26
+ DATA_RANGE: 'DataRangeAtom'
27
+ });
28
+
29
+ class SWRLVariable {
30
+ constructor(iri) {
31
+ this.iri = iri instanceof IRI ? iri : IRI.create(iri);
32
+ }
33
+ toString() { return '?' + this.iri.getShortForm(); }
34
+ }
35
+
36
+ class SWRLClassAtom {
37
+ constructor(classExpression, arg) {
38
+ this.type = AtomType.CLASS;
39
+ this.classExpression = classExpression;
40
+ this.arg = arg; // SWRLVariable or OWLNamedIndividual
41
+ }
42
+ toString() { return `${this.classExpression}(${this.arg})`; }
43
+ }
44
+
45
+ class SWRLObjectPropertyAtom {
46
+ constructor(property, arg1, arg2) {
47
+ this.type = AtomType.OBJECT_PROPERTY;
48
+ this.property = property;
49
+ this.arg1 = arg1;
50
+ this.arg2 = arg2;
51
+ }
52
+ toString() { return `${this.property}(${this.arg1}, ${this.arg2})`; }
53
+ }
54
+
55
+ class SWRLDataPropertyAtom {
56
+ constructor(property, arg1, arg2) {
57
+ this.type = AtomType.DATA_PROPERTY;
58
+ this.property = property;
59
+ this.arg1 = arg1;
60
+ this.arg2 = arg2;
61
+ }
62
+ toString() { return `${this.property}(${this.arg1}, ${this.arg2})`; }
63
+ }
64
+
65
+ class SWRLSameAsAtom {
66
+ constructor(arg1, arg2) {
67
+ this.type = AtomType.SAME_AS;
68
+ this.arg1 = arg1;
69
+ this.arg2 = arg2;
70
+ }
71
+ toString() { return `sameAs(${this.arg1}, ${this.arg2})`; }
72
+ }
73
+
74
+ class SWRLDifferentFromAtom {
75
+ constructor(arg1, arg2) {
76
+ this.type = AtomType.DIFFERENT_FROM;
77
+ this.arg1 = arg1;
78
+ this.arg2 = arg2;
79
+ }
80
+ toString() { return `differentFrom(${this.arg1}, ${this.arg2})`; }
81
+ }
82
+
83
+ class SWRLBuiltInAtom {
84
+ constructor(builtinIRI, args) {
85
+ this.type = AtomType.BUILTIN;
86
+ this.builtin = builtinIRI instanceof IRI ? builtinIRI : IRI.create(builtinIRI);
87
+ this.args = args.slice();
88
+ }
89
+ toString() { return `${this.builtin.getShortForm()}(${this.args.join(', ')})`; }
90
+ }
91
+
92
+ class SWRLDataRangeAtom {
93
+ constructor(dataRange, arg) {
94
+ this.type = AtomType.DATA_RANGE;
95
+ this.dataRange = dataRange;
96
+ this.arg = arg;
97
+ }
98
+ toString() { return `${this.dataRange}(${this.arg})`; }
99
+ }
100
+
101
+ class SWRLRule {
102
+ constructor(body, head, iri = null, annotations = []) {
103
+ this.body = body.slice(); // antecedent atoms
104
+ this.head = head.slice(); // consequent atoms
105
+ this.iri = iri;
106
+ this.annotations = annotations;
107
+ }
108
+ toString() {
109
+ return `${this.body.join(' ^ ')} -> ${this.head.join(' ^ ')}`;
110
+ }
111
+ }
112
+
113
+ // Built-in namespace from the SWRL submission
114
+ const SWRLB_NS = 'http://www.w3.org/2003/11/swrlb#';
115
+
116
+ const SWRL_BUILTINS = [
117
+ // Comparison
118
+ 'equal', 'notEqual', 'lessThan', 'lessThanOrEqual', 'greaterThan', 'greaterThanOrEqual',
119
+ // Math
120
+ 'add', 'subtract', 'multiply', 'divide', 'integerDivide', 'mod',
121
+ 'pow', 'abs', 'ceiling', 'floor', 'round', 'roundHalfToEven',
122
+ 'sin', 'cos', 'tan', 'sqrt',
123
+ // String core
124
+ 'stringConcat', 'substring', 'stringLength', 'normalizeSpace', 'upperCase', 'lowerCase',
125
+ 'contains', 'startsWith', 'endsWith', 'matches', 'replace',
126
+ // String long-tail
127
+ 'stringEqualIgnoreCase', 'translate', 'substringBefore', 'substringAfter',
128
+ // Date / time
129
+ 'yearMonthDuration', 'dayTimeDuration', 'dateTime', 'date', 'time',
130
+ // anyURI
131
+ 'anyURI', 'resolveURI',
132
+ // List operations
133
+ 'listConcat', 'listIntersection', 'listSubtraction', 'member', 'length',
134
+ 'first', 'rest', 'sublist', 'empty',
135
+ // Boolean
136
+ 'booleanNot'
137
+ ].map(n => SWRLB_NS + n);
138
+
139
+ module.exports = {
140
+ AtomType,
141
+ SWRLVariable,
142
+ SWRLClassAtom,
143
+ SWRLObjectPropertyAtom,
144
+ SWRLDataPropertyAtom,
145
+ SWRLSameAsAtom,
146
+ SWRLDifferentFromAtom,
147
+ SWRLBuiltInAtom,
148
+ SWRLDataRangeAtom,
149
+ SWRLRule,
150
+ SWRLB_NS,
151
+ SWRL_BUILTINS
152
+ };
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // event — mirrors org.protege.editor.owl.model.event
5
+ // ---------------------------------------------------------------------------
6
+
7
+ const EventType = Object.freeze({
8
+ ACTIVE_ONTOLOGY_CHANGED: 'ACTIVE_ONTOLOGY_CHANGED',
9
+ ONTOLOGY_LOADED: 'ONTOLOGY_LOADED',
10
+ ONTOLOGY_RELOADED: 'ONTOLOGY_RELOADED',
11
+ ONTOLOGY_SAVED: 'ONTOLOGY_SAVED',
12
+ ONTOLOGY_CREATED: 'ONTOLOGY_CREATED',
13
+ ONTOLOGY_VISIBILITY_CHANGED: 'ONTOLOGY_VISIBILITY_CHANGED',
14
+ REASONER_CHANGED: 'REASONER_CHANGED',
15
+ ABOUT_TO_CLASSIFY: 'ABOUT_TO_CLASSIFY',
16
+ ONTOLOGY_CLASSIFIED: 'ONTOLOGY_CLASSIFIED',
17
+ ENTITY_RENDERER_CHANGED: 'ENTITY_RENDERER_CHANGED',
18
+ ENTITY_RENDERING_CHANGED: 'ENTITY_RENDERING_CHANGED',
19
+ WORKSPACE_LOADED: 'WORKSPACE_LOADED',
20
+ ENTITY_SELECTION_CHANGED: 'ENTITY_SELECTION_CHANGED'
21
+ });
22
+
23
+ class OWLModelManagerChangeEvent {
24
+ constructor(type, payload = null) {
25
+ this.type = type;
26
+ this.payload = payload;
27
+ }
28
+ getType() { return this.type; }
29
+ getPayload() { return this.payload; }
30
+ isType(t) { return this.type === t; }
31
+ }
32
+
33
+ module.exports = { EventType, OWLModelManagerChangeEvent };
@@ -0,0 +1,214 @@
1
+ 'use strict';
2
+
3
+ const { AxiomType } = require('../OWLAxiom');
4
+ const { OWL, OWLClass, OWLObjectProperty, OWLDataProperty } = require('../OWLEntity');
5
+ const { isNamedClass } = require('../OWLClassExpression');
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // hierarchy — mirrors org.protege.editor.owl.model.hierarchy
9
+ // AssertedClassHierarchyProvider: builds a tree from SubClassOf axioms.
10
+ // ---------------------------------------------------------------------------
11
+
12
+ class OWLObjectHierarchyProvider {
13
+ constructor(ontology) {
14
+ this._ontology = ontology;
15
+ this._roots = new Set();
16
+ this._parents = new Map(); // child iri -> Set<parent iri>
17
+ this._children = new Map(); // parent iri -> Set<child iri>
18
+ this._nodes = new Map(); // iri -> entity
19
+ this._listeners = new Set();
20
+ }
21
+
22
+ setOntology(ontology) {
23
+ this._ontology = ontology;
24
+ this.rebuild();
25
+ }
26
+
27
+ addListener(l) { this._listeners.add(l); }
28
+ removeListener(l) { this._listeners.delete(l); }
29
+ _fireChanged() { for (const l of this._listeners) l(); }
30
+
31
+ rebuild() {
32
+ this._roots.clear();
33
+ this._parents.clear();
34
+ this._children.clear();
35
+ this._nodes.clear();
36
+ this._build();
37
+ this._fireChanged();
38
+ }
39
+
40
+ /** @abstract */
41
+ _build() { throw new Error('override _build'); }
42
+
43
+ _addNode(entity) {
44
+ this._nodes.set(entity.getIRI().toString(), entity);
45
+ }
46
+
47
+ _addEdge(parent, child) {
48
+ if (parent.equals(child)) return; // ignore self-loops
49
+ this._addNode(parent);
50
+ this._addNode(child);
51
+ const pKey = parent.getIRI().toString();
52
+ const cKey = child.getIRI().toString();
53
+ if (!this._children.has(pKey)) this._children.set(pKey, new Set());
54
+ this._children.get(pKey).add(cKey);
55
+ if (!this._parents.has(cKey)) this._parents.set(cKey, new Set());
56
+ this._parents.get(cKey).add(pKey);
57
+ }
58
+
59
+ getRoots() {
60
+ return [...this._roots].map(k => this._nodes.get(k)).filter(Boolean);
61
+ }
62
+
63
+ getChildren(entity) {
64
+ const keys = this._children.get(entity.getIRI().toString());
65
+ if (!keys) return [];
66
+ return [...keys].map(k => this._nodes.get(k)).filter(Boolean);
67
+ }
68
+
69
+ getParents(entity) {
70
+ const keys = this._parents.get(entity.getIRI().toString());
71
+ if (!keys) return [];
72
+ return [...keys].map(k => this._nodes.get(k)).filter(Boolean);
73
+ }
74
+
75
+ getDescendants(entity) {
76
+ const seen = new Set();
77
+ const stack = [entity];
78
+ const out = [];
79
+ while (stack.length) {
80
+ const cur = stack.pop();
81
+ const key = cur.getIRI().toString();
82
+ if (seen.has(key)) continue;
83
+ seen.add(key);
84
+ if (cur !== entity) out.push(cur);
85
+ stack.push(...this.getChildren(cur));
86
+ }
87
+ return out;
88
+ }
89
+
90
+ getAncestors(entity) {
91
+ const seen = new Set();
92
+ const stack = [entity];
93
+ const out = [];
94
+ while (stack.length) {
95
+ const cur = stack.pop();
96
+ const key = cur.getIRI().toString();
97
+ if (seen.has(key)) continue;
98
+ seen.add(key);
99
+ if (cur !== entity) out.push(cur);
100
+ stack.push(...this.getParents(cur));
101
+ }
102
+ return out;
103
+ }
104
+
105
+ contains(entity) {
106
+ return this._nodes.has(entity.getIRI().toString());
107
+ }
108
+
109
+ /** Tree as plain nested objects, for JSON serialization to the UI. */
110
+ toTree() {
111
+ const build = (entity, visited) => {
112
+ const key = entity.getIRI().toString();
113
+ const node = { iri: key, name: entity.getShortForm(), children: [] };
114
+ if (visited.has(key)) return node; // cycle guard
115
+ visited.add(key);
116
+ for (const child of this.getChildren(entity)) {
117
+ node.children.push(build(child, new Set(visited)));
118
+ }
119
+ node.children.sort((a, b) => a.name.localeCompare(b.name));
120
+ return node;
121
+ };
122
+ return this.getRoots()
123
+ .map(r => build(r, new Set()))
124
+ .sort((a, b) => a.name.localeCompare(b.name));
125
+ }
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // AssertedClassHierarchyProvider — roots at owl:Thing.
130
+ // ---------------------------------------------------------------------------
131
+
132
+ class AssertedClassHierarchyProvider extends OWLObjectHierarchyProvider {
133
+ _build() {
134
+ const thing = OWL.THING;
135
+ this._addNode(thing);
136
+ this._roots.add(thing.getIRI().toString());
137
+
138
+ if (!this._ontology) return;
139
+ const classes = this._ontology.getClassesInSignature();
140
+ const withParent = new Set();
141
+
142
+ for (const ax of this._ontology.getAxiomsOfType(AxiomType.SUBCLASS_OF)) {
143
+ const sub = ax.subClass;
144
+ const sup = ax.superClass;
145
+ if (isNamedClass(sub) && isNamedClass(sup)) {
146
+ this._addEdge(sup, sub);
147
+ withParent.add(sub.getIRI().toString());
148
+ }
149
+ }
150
+
151
+ // Classes with no asserted superclass hang directly under owl:Thing.
152
+ for (const cls of classes) {
153
+ const key = cls.getIRI().toString();
154
+ if (!withParent.has(key) && !thing.equals(cls)) {
155
+ this._addEdge(thing, cls);
156
+ }
157
+ }
158
+ }
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // AbstractOWLPropertyHierarchyProvider — shared by object/data properties.
163
+ // ---------------------------------------------------------------------------
164
+
165
+ class OWLPropertyHierarchyProvider extends OWLObjectHierarchyProvider {
166
+ constructor(ontology, rootEntity, subAxiomType) {
167
+ super(ontology);
168
+ this._rootEntity = rootEntity;
169
+ this._subAxiomType = subAxiomType;
170
+ }
171
+
172
+ _build() {
173
+ const root = this._rootEntity;
174
+ this._addNode(root);
175
+ this._roots.add(root.getIRI().toString());
176
+
177
+ if (!this._ontology) return;
178
+ const props = this._subAxiomType === AxiomType.SUB_OBJECT_PROPERTY_OF
179
+ ? this._ontology.getObjectPropertiesInSignature()
180
+ : this._ontology.getDataPropertiesInSignature();
181
+ const withParent = new Set();
182
+
183
+ for (const ax of this._ontology.getAxiomsOfType(this._subAxiomType)) {
184
+ this._addEdge(ax.superProperty, ax.subProperty);
185
+ withParent.add(ax.subProperty.getIRI().toString());
186
+ }
187
+
188
+ for (const p of props) {
189
+ const key = p.getIRI().toString();
190
+ if (!withParent.has(key) && !root.equals(p)) {
191
+ this._addEdge(root, p);
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ class OWLObjectPropertyHierarchyProvider extends OWLPropertyHierarchyProvider {
198
+ constructor(ontology) {
199
+ super(ontology, OWL.TOP_OBJECT_PROPERTY, AxiomType.SUB_OBJECT_PROPERTY_OF);
200
+ }
201
+ }
202
+
203
+ class OWLDataPropertyHierarchyProvider extends OWLPropertyHierarchyProvider {
204
+ constructor(ontology) {
205
+ super(ontology, OWL.TOP_DATA_PROPERTY, AxiomType.SUB_DATA_PROPERTY_OF);
206
+ }
207
+ }
208
+
209
+ module.exports = {
210
+ OWLObjectHierarchyProvider,
211
+ AssertedClassHierarchyProvider,
212
+ OWLObjectPropertyHierarchyProvider,
213
+ OWLDataPropertyHierarchyProvider
214
+ };
@@ -0,0 +1,185 @@
1
+ 'use strict';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // profiles/OWL2Profiles — structural conformance checkers for the three
5
+ // OWL 2 profiles (W3C §Profiles): RL, QL, EL.
6
+ //
7
+ // Each checker returns a list of violations:
8
+ // { profile, rule, message, axiom }
9
+ // An ontology belongs to a profile iff the violation list is empty.
10
+ //
11
+ // These checkers implement the *syntactic* restrictions of each profile,
12
+ // not full expressivity analysis. They are intentionally conservative.
13
+ // ---------------------------------------------------------------------------
14
+
15
+ const { AxiomType: A } = require('../model/OWLAxiom');
16
+ const { ClassExpressionType: T } = require('../model/OWLClassExpression');
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Shared helpers
20
+ // ---------------------------------------------------------------------------
21
+
22
+ function isNamedClass(e) {
23
+ return e && e.getIRI && e.getEntityType && e.getEntityType() === 'Class';
24
+ }
25
+
26
+ function walk(expr, fn, acc = []) {
27
+ if (!expr) return acc;
28
+ fn(expr, acc);
29
+ for (const k of ['operand', 'filler']) {
30
+ if (expr[k]) walk(expr[k], fn, acc);
31
+ }
32
+ if (Array.isArray(expr.operands)) for (const o of expr.operands) walk(o, fn, acc);
33
+ return acc;
34
+ }
35
+
36
+ function hasType(expr, typeSet) {
37
+ let found = false;
38
+ walk(expr, (e) => { if (e.type && typeSet.has(e.type)) found = true; });
39
+ return found;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // OWL 2 RL — subclass side must not use ObjectUnionOf / ObjectComplementOf /
44
+ // ObjectAllValuesFrom / ObjectMaxCardinality / ObjectExactCardinality /
45
+ // ObjectHasSelf; superclass side must not use ObjectMinCardinality /
46
+ // ObjectExactCardinality / ObjectHasSelf / ObjectComplementOf.
47
+ // (Simplified from W3C §RL profile definition.)
48
+ // ---------------------------------------------------------------------------
49
+
50
+ const RL_SUBCLASS_FORBIDDEN = new Set([
51
+ T.OBJECT_UNION_OF, T.OBJECT_COMPLEMENT_OF, T.OBJECT_ALL_VALUES_FROM,
52
+ T.OBJECT_MAX_CARDINALITY, T.OBJECT_EXACT_CARDINALITY, T.OBJECT_HAS_SELF
53
+ ]);
54
+ const RL_SUPERCLASS_FORBIDDEN = new Set([
55
+ T.OBJECT_MIN_CARDINALITY, T.OBJECT_EXACT_CARDINALITY,
56
+ T.OBJECT_HAS_SELF, T.OBJECT_COMPLEMENT_OF
57
+ ]);
58
+
59
+ function checkRL(ont) {
60
+ const violations = [];
61
+ const v = (rule, message, axiom) => violations.push({ profile: 'RL', rule, message, axiom });
62
+
63
+ for (const ax of ont.getAxiomsOfType(A.SUBCLASS_OF)) {
64
+ if (hasType(ax.subClass, RL_SUBCLASS_FORBIDDEN)) {
65
+ v('RL-subclass', 'SubClassOf LHS uses a construct disallowed in OWL 2 RL subclass position', ax);
66
+ }
67
+ if (hasType(ax.superClass, RL_SUPERCLASS_FORBIDDEN)) {
68
+ v('RL-superclass', 'SubClassOf RHS uses a construct disallowed in OWL 2 RL superclass position', ax);
69
+ }
70
+ }
71
+ // FunctionalDataProperty + FunctionalObjectProperty are allowed;
72
+ // owl:hasSelf is disallowed entirely in RL.
73
+ for (const ax of ont.getAxiomsOfType(A.EQUIVALENT_CLASSES)) {
74
+ for (const ce of ax.classExpressions) {
75
+ if (hasType(ce, new Set([T.OBJECT_HAS_SELF]))) {
76
+ v('RL-hasSelf', 'ObjectHasSelf is not allowed in OWL 2 RL', ax);
77
+ }
78
+ }
79
+ }
80
+ return violations;
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // OWL 2 QL — subclass side: named class or ObjectSomeValuesFrom with named
85
+ // filler only; superclass side: named class, ObjectSomeValuesFrom(named),
86
+ // ObjectIntersectionOf of superclass-expressions, ObjectHasValue.
87
+ // No cardinality > 1, no ObjectUnionOf/ComplementOf/AllValuesFrom on RHS.
88
+ // (Simplified.)
89
+ // ---------------------------------------------------------------------------
90
+
91
+ function qlSubClassOK(e) {
92
+ if (isNamedClass(e)) return true;
93
+ if (e.type === T.OBJECT_SOME_VALUES_FROM) return isNamedClass(e.filler) || isOWLThing(e.filler);
94
+ return false;
95
+ }
96
+ function qlSuperClassOK(e) {
97
+ if (isNamedClass(e)) return true;
98
+ if (e.type === T.OBJECT_SOME_VALUES_FROM) return isNamedClass(e.filler) || isOWLThing(e.filler);
99
+ if (e.type === T.OBJECT_INTERSECTION_OF) return e.operands.every(qlSuperClassOK);
100
+ if (e.type === T.OBJECT_HAS_VALUE) return true;
101
+ return false;
102
+ }
103
+ function isOWLThing(e) {
104
+ return isNamedClass(e) && e.getIRI().toString() === 'http://www.w3.org/2002/07/owl#Thing';
105
+ }
106
+
107
+ function checkQL(ont) {
108
+ const violations = [];
109
+ const v = (rule, message, axiom) => violations.push({ profile: 'QL', rule, message, axiom });
110
+
111
+ for (const ax of ont.getAxiomsOfType(A.SUBCLASS_OF)) {
112
+ if (!qlSubClassOK(ax.subClass)) {
113
+ v('QL-subclass', 'SubClassOf LHS not in QL form (named class or ∃P.Thing)', ax);
114
+ }
115
+ if (!qlSuperClassOK(ax.superClass)) {
116
+ v('QL-superclass', 'SubClassOf RHS not in QL form', ax);
117
+ }
118
+ }
119
+ // No FunctionalObjectProperty, no TransitiveObjectProperty, no cardinality
120
+ for (const t of [A.FUNCTIONAL_OBJECT_PROPERTY, A.TRANSITIVE_OBJECT_PROPERTY,
121
+ A.SYMMETRIC_OBJECT_PROPERTY, A.REFLEXIVE_OBJECT_PROPERTY]) {
122
+ for (const ax of ont.getAxiomsOfType(t)) {
123
+ v('QL-' + t, `${t} is not allowed in OWL 2 QL`, ax);
124
+ }
125
+ }
126
+ return violations;
127
+ }
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // OWL 2 EL — no ObjectComplementOf, no ObjectAllValuesFrom, no
131
+ // DisjointClasses with > 2 operands allowed (binary allowed), no
132
+ // IrreflexiveObjectProperty, no AsymmetricObjectProperty, no
133
+ // NegativePropertyAssertion, no ObjectOneOf with > 1 individual (EL++ allows
134
+ // nominals in some profiles but standard EL does not).
135
+ // (Simplified.)
136
+ // ---------------------------------------------------------------------------
137
+
138
+ const EL_FORBIDDEN = new Set([
139
+ T.OBJECT_COMPLEMENT_OF, T.OBJECT_ALL_VALUES_FROM, T.OBJECT_UNION_OF
140
+ ]);
141
+
142
+ function checkEL(ont) {
143
+ const violations = [];
144
+ const v = (rule, message, axiom) => violations.push({ profile: 'EL', rule, message, axiom });
145
+
146
+ for (const ax of ont.getAxiomsOfType(A.SUBCLASS_OF)) {
147
+ if (hasType(ax.subClass, EL_FORBIDDEN) || hasType(ax.superClass, EL_FORBIDDEN)) {
148
+ v('EL-expr', 'ObjectComplementOf / ObjectAllValuesFrom / ObjectUnionOf not allowed in OWL 2 EL', ax);
149
+ }
150
+ }
151
+ for (const t of [A.IRREFLEXIVE_OBJECT_PROPERTY, A.ASYMMETRIC_OBJECT_PROPERTY,
152
+ A.FUNCTIONAL_OBJECT_PROPERTY, A.INVERSE_FUNCTIONAL_OBJECT_PROPERTY,
153
+ A.NEGATIVE_OBJECT_PROPERTY_ASSERTION, A.NEGATIVE_DATA_PROPERTY_ASSERTION]) {
154
+ for (const ax of ont.getAxiomsOfType(t)) {
155
+ v('EL-' + t, `${t} is not allowed in OWL 2 EL`, ax);
156
+ }
157
+ }
158
+ return violations;
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Public API
163
+ // ---------------------------------------------------------------------------
164
+
165
+ const Profiles = Object.freeze({ RL: 'RL', QL: 'QL', EL: 'EL' });
166
+
167
+ /**
168
+ * Check an ontology against a profile.
169
+ * @param {OWLOntology} ont
170
+ * @param {'RL'|'QL'|'EL'} profile
171
+ * @returns {Array<{profile:string, rule:string, message:string, axiom:Object}>}
172
+ */
173
+ function checkProfile(ont, profile) {
174
+ if (profile === 'RL') return checkRL(ont);
175
+ if (profile === 'QL') return checkQL(ont);
176
+ if (profile === 'EL') return checkEL(ont);
177
+ throw new Error('Unknown profile: ' + profile);
178
+ }
179
+
180
+ /** True iff the ontology is in the given profile. */
181
+ function isInProfile(ont, profile) {
182
+ return checkProfile(ont, profile).length === 0;
183
+ }
184
+
185
+ module.exports = { Profiles, checkProfile, isInProfile, checkRL, checkQL, checkEL };
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ let currentKind = 'class';
4
+
5
+ async function api(path, opts) {
6
+ const res = await fetch(path, opts);
7
+ return res.json();
8
+ }
9
+
10
+ function renderNode(node) {
11
+ const div = document.createElement('div');
12
+ div.className = 'node';
13
+ const row = document.createElement('div');
14
+ row.className = 'row';
15
+ const twisty = document.createElement('span');
16
+ twisty.className = 'twisty';
17
+ const hasChildren = node.children && node.children.length > 0;
18
+ twisty.textContent = hasChildren ? '▾' : '·';
19
+ if (hasChildren) {
20
+ twisty.onclick = () => {
21
+ div.classList.toggle('collapsed');
22
+ twisty.textContent = div.classList.contains('collapsed') ? '▸' : '▾';
23
+ };
24
+ }
25
+ row.appendChild(twisty);
26
+ const name = document.createElement('span');
27
+ name.textContent = node.name;
28
+ name.title = node.iri;
29
+ row.appendChild(name);
30
+ if (hasChildren) {
31
+ const badge = document.createElement('span');
32
+ badge.className = 'badge';
33
+ badge.textContent = `(${node.children.length})`;
34
+ row.appendChild(badge);
35
+ }
36
+ div.appendChild(row);
37
+ if (hasChildren) {
38
+ const ch = document.createElement('div');
39
+ ch.className = 'children';
40
+ for (const c of node.children) ch.appendChild(renderNode(c));
41
+ div.appendChild(ch);
42
+ }
43
+ return div;
44
+ }
45
+
46
+ async function refreshTree() {
47
+ const wrap = document.getElementById('tree-wrap');
48
+ const data = await api('/api/tree?kind=' + encodeURIComponent(currentKind));
49
+ wrap.innerHTML = '';
50
+ if (!data.ok) {
51
+ wrap.innerHTML = '<div id="empty">尚未加载本体。</div>';
52
+ return;
53
+ }
54
+ if (!data.tree.length) {
55
+ wrap.innerHTML = '<div id="empty">该层级为空。</div>';
56
+ return;
57
+ }
58
+ for (const root of data.tree) wrap.appendChild(renderNode(root));
59
+ }
60
+
61
+ async function refreshStats() {
62
+ const s = await api('/api/stats');
63
+ const el = document.getElementById('stats');
64
+ if (!s.loaded) { el.textContent = ''; return; }
65
+ el.textContent = `公理 ${s.axiomCount} · 类 ${s.classes} · 对象属性 ${s.objectProperties} · 数据属性 ${s.dataProperties} · 个体 ${s.individuals}`;
66
+ }
67
+
68
+ async function loadOntology() {
69
+ const filePath = document.getElementById('filePath').value.trim();
70
+ const msg = document.getElementById('msg');
71
+ if (!filePath) { msg.textContent = '请输入文件路径'; return; }
72
+ msg.textContent = '加载中…';
73
+ const r = await api('/api/load', {
74
+ method: 'POST',
75
+ headers: { 'content-type': 'application/json' },
76
+ body: JSON.stringify({ filePath })
77
+ });
78
+ if (r.error) { msg.textContent = '加载失败: ' + r.error; return; }
79
+ msg.textContent = '已加载: ' + r.ontology.id;
80
+ await refreshStats();
81
+ await refreshTree();
82
+ }
83
+
84
+ document.getElementById('btn-load').addEventListener('click', loadOntology);
85
+ document.getElementById('filePath').addEventListener('keydown', (e) => {
86
+ if (e.key === 'Enter') loadOntology();
87
+ });
88
+ document.querySelectorAll('nav .tab').forEach(tab => {
89
+ tab.addEventListener('click', async () => {
90
+ document.querySelectorAll('nav .tab').forEach(t => t.classList.remove('active'));
91
+ tab.classList.add('active');
92
+ currentKind = tab.dataset.kind;
93
+ await refreshTree();
94
+ });
95
+ });
96
+
97
+ refreshStats();