@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,363 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// TurtleParser — a compact RDF/Turtle parser covering the OWL 2 RL-relevant
|
|
5
|
+
// subset of the W3C Turtle spec (https://www.w3.org/TR/turtle/).
|
|
6
|
+
// Supports: @prefix / @base / PREFIX / BASE, triples with `a`, predicate-object
|
|
7
|
+
// lists, object lists, blank nodes [], collections (), literals with
|
|
8
|
+
// ^^datatype or @lang, numbers, booleans, and comments (#...).
|
|
9
|
+
// Emits raw triples to a TripleStore; mapping to OWL axioms is done elsewhere.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
const { TripleStore } = require('../inference/TripleStore');
|
|
13
|
+
|
|
14
|
+
const RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
|
|
15
|
+
const XSD_NS = 'http://www.w3.org/2001/XMLSchema#';
|
|
16
|
+
|
|
17
|
+
const P = Object.freeze({
|
|
18
|
+
type: RDF_NS + 'type',
|
|
19
|
+
first: RDF_NS + 'first',
|
|
20
|
+
rest: RDF_NS + 'rest',
|
|
21
|
+
nil: RDF_NS + 'nil'
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const XSD_INTEGER = XSD_NS + 'integer';
|
|
25
|
+
const XSD_DECIMAL = XSD_NS + 'decimal';
|
|
26
|
+
const XSD_DOUBLE = XSD_NS + 'double';
|
|
27
|
+
const XSD_BOOLEAN = XSD_NS + 'boolean';
|
|
28
|
+
|
|
29
|
+
class TurtleParser {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.store = new TripleStore();
|
|
32
|
+
this.prefixes = new Map([
|
|
33
|
+
['rdf', RDF_NS],
|
|
34
|
+
['rdfs', 'http://www.w3.org/2000/01/rdf-schema#'],
|
|
35
|
+
['owl', 'http://www.w3.org/2002/07/owl#'],
|
|
36
|
+
['xsd', XSD_NS]
|
|
37
|
+
]);
|
|
38
|
+
this.base = '';
|
|
39
|
+
this._blankCounter = 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_newBlank() { return '_:b' + (++this._blankCounter); }
|
|
43
|
+
|
|
44
|
+
/** Parse Turtle text. Returns the TripleStore. */
|
|
45
|
+
parse(text) {
|
|
46
|
+
const tokens = this._tokenize(text);
|
|
47
|
+
this._pos = 0;
|
|
48
|
+
this._tokens = tokens;
|
|
49
|
+
while (this._pos < tokens.length) {
|
|
50
|
+
this._statement();
|
|
51
|
+
}
|
|
52
|
+
return this.store;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---- tokenizer -----------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
_tokenize(text) {
|
|
58
|
+
const out = [];
|
|
59
|
+
let i = 0;
|
|
60
|
+
const n = text.length;
|
|
61
|
+
while (i < n) {
|
|
62
|
+
const ch = text[i];
|
|
63
|
+
// whitespace / comments
|
|
64
|
+
if (/\s/.test(ch)) { i++; continue; }
|
|
65
|
+
if (ch === '#') { while (i < n && text[i] !== '\n') i++; continue; }
|
|
66
|
+
// punctuation
|
|
67
|
+
if (';,.[]()'.includes(ch)) { out.push({ t: ch, v: ch }); i++; continue; }
|
|
68
|
+
if (ch === '^' && text[i + 1] === '^') { out.push({ t: '^^', v: '^^' }); i += 2; continue; }
|
|
69
|
+
// string literal (single/double, possibly long)
|
|
70
|
+
if (ch === '"' || ch === "'") {
|
|
71
|
+
const quote = ch;
|
|
72
|
+
const long = text.substr(i, 3) === quote.repeat(3);
|
|
73
|
+
const endQuote = long ? quote.repeat(3) : quote;
|
|
74
|
+
let j = i + (long ? 3 : 1);
|
|
75
|
+
let value = '';
|
|
76
|
+
while (j < n) {
|
|
77
|
+
if (text.substr(j, endQuote.length) === endQuote) break;
|
|
78
|
+
if (text[j] === '\\' && j + 1 < n) {
|
|
79
|
+
const esc = text[j + 1];
|
|
80
|
+
const map = { n: '\n', t: '\t', r: '\r', '"': '"', "'": "'", '\\': '\\' };
|
|
81
|
+
if (esc === 'u' && j + 5 < n) {
|
|
82
|
+
value += String.fromCharCode(parseInt(text.substr(j + 2, 4), 16));
|
|
83
|
+
j += 6; continue;
|
|
84
|
+
}
|
|
85
|
+
value += map[esc] !== undefined ? map[esc] : esc;
|
|
86
|
+
j += 2;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
value += text[j];
|
|
90
|
+
j++;
|
|
91
|
+
}
|
|
92
|
+
j += endQuote.length;
|
|
93
|
+
out.push({ t: 'STRING', v: value });
|
|
94
|
+
i = j;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// IRI_REF <...>
|
|
98
|
+
if (ch === '<') {
|
|
99
|
+
const j = text.indexOf('>', i);
|
|
100
|
+
if (j < 0) throw new Error('Unterminated IRI');
|
|
101
|
+
out.push({ t: 'IRI', v: text.slice(i + 1, j) });
|
|
102
|
+
i = j + 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Blank node label _:name
|
|
106
|
+
if (ch === '_' && text[i + 1] === ':') {
|
|
107
|
+
let j = i + 2;
|
|
108
|
+
while (j < n && /[A-Za-z0-9_-]/.test(text[j])) j++;
|
|
109
|
+
out.push({ t: 'BNODE', v: '_:' + text.slice(i + 2, j) });
|
|
110
|
+
i = j;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// @word keyword (@prefix, @base) or lang tag
|
|
114
|
+
if (ch === '@') {
|
|
115
|
+
let j = i + 1;
|
|
116
|
+
while (j < n && /[A-Za-z0-9_-]/.test(text[j])) j++;
|
|
117
|
+
out.push({ t: 'AT', v: text.slice(i + 1, j) });
|
|
118
|
+
i = j;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// keyword PREFIX / BASE
|
|
122
|
+
if (text.startsWith('PREFIX', i) && /\s/.test(text[i + 6] || ' ')) {
|
|
123
|
+
out.push({ t: 'PREFIX_KW', v: 'PREFIX' }); i += 6; continue;
|
|
124
|
+
}
|
|
125
|
+
if (text.startsWith('BASE', i) && /\s/.test(text[i + 4] || ' ')) {
|
|
126
|
+
out.push({ t: 'BASE_KW', v: 'BASE' }); i += 4; continue;
|
|
127
|
+
}
|
|
128
|
+
// number
|
|
129
|
+
if (/[0-9+-]/.test(ch) && /[0-9]/.test(text[i + 1] || '') || /[0-9]/.test(ch)) {
|
|
130
|
+
let j = i;
|
|
131
|
+
if (/[+-]/.test(text[j])) j++;
|
|
132
|
+
let hasDot = false, hasE = false;
|
|
133
|
+
while (j < n) {
|
|
134
|
+
const c = text[j];
|
|
135
|
+
if (/[0-9]/.test(c)) { j++; continue; }
|
|
136
|
+
if (c === '.' && !hasDot && !hasE && /[0-9]/.test(text[j + 1] || '')) { hasDot = true; j++; continue; }
|
|
137
|
+
if ((c === 'e' || c === 'E') && !hasE) {
|
|
138
|
+
hasE = true; j++;
|
|
139
|
+
if (/[+-]/.test(text[j])) j++;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
const num = text.slice(i, j);
|
|
145
|
+
out.push({ t: 'NUMBER', v: num, hasDot, hasE });
|
|
146
|
+
i = j;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
// boolean
|
|
150
|
+
if (text.startsWith('true', i) && !/\w/.test(text[i + 4] || '')) {
|
|
151
|
+
out.push({ t: 'BOOLEAN', v: 'true' }); i += 4; continue;
|
|
152
|
+
}
|
|
153
|
+
if (text.startsWith('false', i) && !/\w/.test(text[i + 5] || '')) {
|
|
154
|
+
out.push({ t: 'BOOLEAN', v: 'false' }); i += 5; continue;
|
|
155
|
+
}
|
|
156
|
+
// keyword `a`
|
|
157
|
+
if (ch === 'a' && !/[A-Za-z0-9_]/.test(text[i + 1] || '')) {
|
|
158
|
+
out.push({ t: 'A', v: 'a' }); i++; continue;
|
|
159
|
+
}
|
|
160
|
+
// prefixed name prefix:local or plain prefix:
|
|
161
|
+
let j = i;
|
|
162
|
+
while (j < n && /[A-Za-z0-9_-]/.test(text[j])) j++;
|
|
163
|
+
if (text[j] === ':') {
|
|
164
|
+
const prefix = text.slice(i, j);
|
|
165
|
+
let k = j + 1;
|
|
166
|
+
while (k < n && /[A-Za-z0-9_\-.]/.test(text[k])) k++;
|
|
167
|
+
const local = text.slice(j + 1, k);
|
|
168
|
+
out.push({ t: 'PNAME', v: prefix + ':' + local });
|
|
169
|
+
i = k;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// bare name (treat as prefixed name without colon -> error unless prefix known)
|
|
173
|
+
// fallthrough: treat as anonymous token
|
|
174
|
+
throw new Error('Unexpected char at ' + i + ': ' + text.slice(i, i + 20));
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_peek() { return this._tokens[this._pos]; }
|
|
180
|
+
_next() { return this._tokens[this._pos++]; }
|
|
181
|
+
_expect(t) {
|
|
182
|
+
const tok = this._next();
|
|
183
|
+
if (!tok || tok.t !== t) throw new Error(`Expected ${t}, got ${tok && tok.t}`);
|
|
184
|
+
return tok;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ---- grammar -------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
_statement() {
|
|
190
|
+
const tok = this._peek();
|
|
191
|
+
if (!tok) return;
|
|
192
|
+
if (tok.t === 'AT') {
|
|
193
|
+
this._next();
|
|
194
|
+
if (tok.v === 'prefix') {
|
|
195
|
+
const pn = this._expect('PNAME'); // e.g. rdf:
|
|
196
|
+
const iri = this._expect('IRI');
|
|
197
|
+
this.prefixes.set(pn.v.slice(0, -1), iri.v);
|
|
198
|
+
} else if (tok.v === 'base') {
|
|
199
|
+
const iri = this._expect('IRI');
|
|
200
|
+
this.base = iri.v;
|
|
201
|
+
}
|
|
202
|
+
if (this._peek() && this._peek().t === '.') this._next();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (tok.t === 'PREFIX_KW') {
|
|
206
|
+
this._next();
|
|
207
|
+
const pn = this._expect('PNAME');
|
|
208
|
+
const iri = this._expect('IRI');
|
|
209
|
+
this.prefixes.set(pn.v.slice(0, -1), iri.v);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (tok.t === 'BASE_KW') {
|
|
213
|
+
this._next();
|
|
214
|
+
const iri = this._expect('IRI');
|
|
215
|
+
this.base = iri.v;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this._triples();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
_triples() {
|
|
222
|
+
const subject = this._subject();
|
|
223
|
+
this._predicateObjectList(subject);
|
|
224
|
+
this._expect('.');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_subject() {
|
|
228
|
+
const tok = this._peek();
|
|
229
|
+
if (tok.t === 'IRI' || tok.t === 'PNAME') return this._iriToken(this._next());
|
|
230
|
+
if (tok.t === 'BNODE') { this._next(); return tok.v; }
|
|
231
|
+
if (tok.t === '[') return this._blankNode();
|
|
232
|
+
if (tok.t === '(') return this._collection();
|
|
233
|
+
throw new Error('Unexpected subject: ' + JSON.stringify(tok));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
_iriToken(tok) {
|
|
237
|
+
if (tok.t === 'IRI') return this._resolveIRI(tok.v);
|
|
238
|
+
if (tok.t === 'PNAME') {
|
|
239
|
+
const idx = tok.v.indexOf(':');
|
|
240
|
+
const prefix = tok.v.slice(0, idx);
|
|
241
|
+
const local = tok.v.slice(idx + 1);
|
|
242
|
+
if (!this.prefixes.has(prefix)) throw new Error('Unknown prefix: ' + prefix);
|
|
243
|
+
return this.prefixes.get(prefix) + local;
|
|
244
|
+
}
|
|
245
|
+
throw new Error('Not an IRI token: ' + JSON.stringify(tok));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
_resolveIRI(rel) {
|
|
249
|
+
if (/^https?:/i.test(rel) || /^urn:/i.test(rel)) return rel;
|
|
250
|
+
if (this.base) {
|
|
251
|
+
if (rel.startsWith('#')) return this.base + rel;
|
|
252
|
+
if (rel.startsWith('/')) {
|
|
253
|
+
const m = /^(https?:\/\/[^/]+)/.exec(this.base);
|
|
254
|
+
return m ? m[1] + rel : rel;
|
|
255
|
+
}
|
|
256
|
+
const base = this.base.endsWith('/') ? this.base : this.base + '/';
|
|
257
|
+
return base + rel;
|
|
258
|
+
}
|
|
259
|
+
return rel;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
_predicateObjectList(subject) {
|
|
263
|
+
while (true) {
|
|
264
|
+
const tok = this._peek();
|
|
265
|
+
if (!tok || tok.t === '.') return;
|
|
266
|
+
// predicate
|
|
267
|
+
let predicate;
|
|
268
|
+
if (tok.t === 'A') { this._next(); predicate = P.type; }
|
|
269
|
+
else if (tok.t === 'IRI' || tok.t === 'PNAME') predicate = this._iriToken(this._next());
|
|
270
|
+
else throw new Error('Expected predicate, got ' + JSON.stringify(tok));
|
|
271
|
+
// object list
|
|
272
|
+
this._objectList(subject, predicate);
|
|
273
|
+
// ; or .
|
|
274
|
+
const sep = this._peek();
|
|
275
|
+
if (sep && sep.t === ';') { this._next(); continue; }
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
_objectList(subject, predicate) {
|
|
281
|
+
while (true) {
|
|
282
|
+
const obj = this._object();
|
|
283
|
+
if (obj !== undefined && obj !== null) {
|
|
284
|
+
this.store.add(subject, predicate, obj);
|
|
285
|
+
}
|
|
286
|
+
const tok = this._peek();
|
|
287
|
+
if (tok && tok.t === ',') { this._next(); continue; }
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
_object() {
|
|
293
|
+
const tok = this._peek();
|
|
294
|
+
if (tok.t === 'IRI' || tok.t === 'PNAME') return this._iriToken(this._next());
|
|
295
|
+
if (tok.t === 'BNODE') { this._next(); return tok.v; }
|
|
296
|
+
if (tok.t === 'STRING') {
|
|
297
|
+
this._next();
|
|
298
|
+
let lit = '"' + tok.v.replace(/"/g, '\\"') + '"';
|
|
299
|
+
const next = this._peek();
|
|
300
|
+
if (next && next.t === '^^') {
|
|
301
|
+
this._next();
|
|
302
|
+
const dt = this._iriToken(this._next());
|
|
303
|
+
lit += '^^<' + dt + '>';
|
|
304
|
+
} else if (next && next.t === 'AT') {
|
|
305
|
+
this._next();
|
|
306
|
+
lit += '@' + next.v;
|
|
307
|
+
}
|
|
308
|
+
return lit;
|
|
309
|
+
}
|
|
310
|
+
if (tok.t === 'NUMBER') {
|
|
311
|
+
this._next();
|
|
312
|
+
const dt = tok.hasE ? XSD_DOUBLE : tok.hasDot ? XSD_DECIMAL : XSD_INTEGER;
|
|
313
|
+
return `"${tok.v}"^^<${dt}>`;
|
|
314
|
+
}
|
|
315
|
+
if (tok.t === 'BOOLEAN') {
|
|
316
|
+
this._next();
|
|
317
|
+
return `"${tok.v}"^^<${XSD_BOOLEAN}>`;
|
|
318
|
+
}
|
|
319
|
+
if (tok.t === '[') return this._blankNode();
|
|
320
|
+
if (tok.t === '(') return this._collection();
|
|
321
|
+
throw new Error('Unexpected object: ' + JSON.stringify(tok));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
_blankNode() {
|
|
325
|
+
this._expect('[');
|
|
326
|
+
const head = this._newBlank();
|
|
327
|
+
const tok = this._peek();
|
|
328
|
+
if (tok && tok.t !== ']') {
|
|
329
|
+
this._predicateObjectList(head);
|
|
330
|
+
}
|
|
331
|
+
this._expect(']');
|
|
332
|
+
return head;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
_collection() {
|
|
336
|
+
this._expect('(');
|
|
337
|
+
const items = [];
|
|
338
|
+
while (this._peek() && this._peek().t !== ')') {
|
|
339
|
+
items.push(this._object());
|
|
340
|
+
}
|
|
341
|
+
this._expect(')');
|
|
342
|
+
if (items.length === 0) return P.nil;
|
|
343
|
+
// build rdf:first/rest chain
|
|
344
|
+
let head = null;
|
|
345
|
+
let prev = null;
|
|
346
|
+
for (const item of items) {
|
|
347
|
+
const node = this._newBlank();
|
|
348
|
+
if (!head) head = node;
|
|
349
|
+
this.store.add(node, P.first, item);
|
|
350
|
+
if (prev) this.store.add(prev, P.rest, node);
|
|
351
|
+
prev = node;
|
|
352
|
+
}
|
|
353
|
+
this.store.add(prev, P.rest, P.nil);
|
|
354
|
+
return head;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Convenience: parse Turtle text, return TripleStore. */
|
|
359
|
+
function parseTurtle(text) {
|
|
360
|
+
return new TurtleParser().parse(text);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
module.exports = { TurtleParser, parseTurtle };
|