@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,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();
|