n3 2.3.0 → 2.5.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/LICENSE.md +1 -1
- package/README.md +2 -3
- package/browser/n3.esm.min.js +5 -5
- package/browser/n3.min.js +1 -1
- package/lib/N3Lexer.js +5 -0
- package/lib/N3Parser.js +104 -41
- package/package.json +1 -1
- package/src/N3Lexer.js +9 -0
- package/src/N3Parser.js +123 -40
package/lib/N3Lexer.js
CHANGED
|
@@ -88,6 +88,7 @@ class N3Lexer {
|
|
|
88
88
|
this._boolean = /^(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/;
|
|
89
89
|
this._atKeyword = /^@[a-z]+(?=[\s#<:])/i;
|
|
90
90
|
this._keyword = /^(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/i;
|
|
91
|
+
this._n3Id = /^id(?=[\s#<])/;
|
|
91
92
|
this._shortPredicates = /^a(?=[\s#()\[\]\{\}"'<>])/;
|
|
92
93
|
this._newline = /^[ \t]*(?:#[^\n\r]*)?(?:\r\n|\n|\r)[ \t]*/;
|
|
93
94
|
this._comment = /#([^\n\r]*)/;
|
|
@@ -315,6 +316,10 @@ class N3Lexer {
|
|
|
315
316
|
// Try to find an abbreviated predicate
|
|
316
317
|
if (match = this._shortPredicates.exec(input)) type = 'abbreviation', value = 'a';else inconclusive = true;
|
|
317
318
|
break;
|
|
319
|
+
case 'i':
|
|
320
|
+
// Try to find the IRI property list identifier
|
|
321
|
+
if (this._n3Mode && (match = this._n3Id.exec(input))) type = 'id';else inconclusive = true;
|
|
322
|
+
break;
|
|
318
323
|
case '=':
|
|
319
324
|
// Try to find an implication arrow or equals sign
|
|
320
325
|
if (this._n3Mode && input.length > 1) {
|
package/lib/N3Parser.js
CHANGED
|
@@ -97,7 +97,7 @@ class N3Parser {
|
|
|
97
97
|
});
|
|
98
98
|
return;
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
const context = {
|
|
101
101
|
type,
|
|
102
102
|
subject,
|
|
103
103
|
predicate,
|
|
@@ -107,7 +107,14 @@ class N3Parser {
|
|
|
107
107
|
blankPrefix: this._prefixes._,
|
|
108
108
|
quantified: this._quantified,
|
|
109
109
|
emptyFormula: this._emptyFormula
|
|
110
|
-
}
|
|
110
|
+
};
|
|
111
|
+
// Prefix and base declarations are scoped to their formula
|
|
112
|
+
if (type === 'formula') {
|
|
113
|
+
context.prefixes = this._prefixes;
|
|
114
|
+
context.base = [this._base, this._basePath, this._baseRoot, this._baseScheme];
|
|
115
|
+
this._prefixes = Object.create(this._prefixes);
|
|
116
|
+
}
|
|
117
|
+
this._contextStack.push(context);
|
|
111
118
|
// Every new scope resets the predicate direction
|
|
112
119
|
this._inversePredicate = false;
|
|
113
120
|
// In N3, blank nodes are scoped to a formula
|
|
@@ -138,7 +145,10 @@ class N3Parser {
|
|
|
138
145
|
// Restore N3 context settings
|
|
139
146
|
if (this._n3Mode) {
|
|
140
147
|
this._inversePredicate = context.inverse;
|
|
141
|
-
|
|
148
|
+
if (type === 'formula') {
|
|
149
|
+
this._prefixes = context.prefixes;
|
|
150
|
+
[this._base, this._basePath, this._baseRoot, this._baseScheme] = context.base;
|
|
151
|
+
} else this._prefixes._ = context.blankPrefix;
|
|
142
152
|
this._quantified = context.quantified;
|
|
143
153
|
this._emptyFormula = context.emptyFormula;
|
|
144
154
|
}
|
|
@@ -188,6 +198,28 @@ class N3Parser {
|
|
|
188
198
|
}
|
|
189
199
|
}
|
|
190
200
|
|
|
201
|
+
// ### `_readInFormulaContext` reads a token at the statement level of a formula
|
|
202
|
+
_readInFormulaContext(token) {
|
|
203
|
+
switch (token.type) {
|
|
204
|
+
case 'PREFIX':
|
|
205
|
+
this._sparqlStyle = true;
|
|
206
|
+
case '@prefix':
|
|
207
|
+
return this._readPrefix;
|
|
208
|
+
case 'BASE':
|
|
209
|
+
this._sparqlStyle = true;
|
|
210
|
+
case '@base':
|
|
211
|
+
return this._readBaseIRI;
|
|
212
|
+
default:
|
|
213
|
+
return this._readSubject(token);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ### `_getStatementReader` returns the reader for the current statement scope
|
|
218
|
+
_getStatementReader() {
|
|
219
|
+
const context = this._contextStack[this._contextStack.length - 1];
|
|
220
|
+
return context && context.type === 'formula' ? this._readInFormulaContext : this._readInTopContext;
|
|
221
|
+
}
|
|
222
|
+
|
|
191
223
|
// ### `_readEntity` reads an IRI, prefixed name, blank node, or variable
|
|
192
224
|
_readEntity(token, quantifier) {
|
|
193
225
|
let value;
|
|
@@ -252,7 +284,7 @@ class N3Parser {
|
|
|
252
284
|
// Start a new formula
|
|
253
285
|
if (!this._n3Mode) return this._error('Unexpected graph', token);
|
|
254
286
|
this._saveContext('formula', this._graph, this._graph = this._factory.blankNode(), null, null);
|
|
255
|
-
return this.
|
|
287
|
+
return this._readInFormulaContext;
|
|
256
288
|
case '}':
|
|
257
289
|
// No subject; the graph in which we are reading is closed instead
|
|
258
290
|
return this._readPunctuation(token);
|
|
@@ -302,6 +334,7 @@ class N3Parser {
|
|
|
302
334
|
// ### `_readPredicate` reads a quad's predicate
|
|
303
335
|
_readPredicate(token) {
|
|
304
336
|
const type = token.type;
|
|
337
|
+
let pathable = false;
|
|
305
338
|
switch (type) {
|
|
306
339
|
case 'inverse':
|
|
307
340
|
this._inversePredicate = true;
|
|
@@ -326,6 +359,7 @@ class N3Parser {
|
|
|
326
359
|
this._literalValue = token.value;
|
|
327
360
|
return this._completePredicateLiteral;
|
|
328
361
|
} else this._predicate = this._factory.literal(token.value, this._factory.namedNode(token.prefix));
|
|
362
|
+
pathable = true;
|
|
329
363
|
break;
|
|
330
364
|
case '(':
|
|
331
365
|
// In N3, a list can be a predicate
|
|
@@ -336,14 +370,23 @@ class N3Parser {
|
|
|
336
370
|
this._saveContext('blank', this._graph, this._subject, this._subject = this._factory.blankNode(), null);
|
|
337
371
|
return this._readBlankNodeHead;
|
|
338
372
|
}
|
|
373
|
+
return this._error('Disallowed blank node as predicate', token);
|
|
374
|
+
case '{':
|
|
375
|
+
// In N3, a formula can be a predicate
|
|
376
|
+
if (this._n3Mode) {
|
|
377
|
+
this._saveContext('formula', this._graph, this._subject, this._graph = this._factory.blankNode(), null);
|
|
378
|
+
return this._readSubject;
|
|
379
|
+
}
|
|
380
|
+
return this._readEntity(token);
|
|
339
381
|
case 'blank':
|
|
340
382
|
if (!this._n3Mode) return this._error('Disallowed blank node as predicate', token);
|
|
341
383
|
default:
|
|
342
384
|
if ((this._predicate = this._readEntity(token)) === undefined) return;
|
|
385
|
+
pathable = this._n3Mode;
|
|
343
386
|
}
|
|
344
387
|
this._validAnnotation = true;
|
|
345
388
|
// The next token must be an object
|
|
346
|
-
return this._readObject;
|
|
389
|
+
return pathable ? this._getPathReader(this._readObject, 'predicate') : this._readObject;
|
|
347
390
|
}
|
|
348
391
|
|
|
349
392
|
// ### `_readObject` reads a quad's object
|
|
@@ -372,7 +415,7 @@ class N3Parser {
|
|
|
372
415
|
// Start a new formula
|
|
373
416
|
if (!this._n3Mode) return this._error('Unexpected graph', token);
|
|
374
417
|
this._saveContext('formula', this._graph, this._subject, this._predicate, this._graph = this._factory.blankNode());
|
|
375
|
-
return this.
|
|
418
|
+
return this._readInFormulaContext;
|
|
376
419
|
case '<<(':
|
|
377
420
|
this._saveContext('<<(', this._graph, this._subject, this._predicate, null);
|
|
378
421
|
this._graph = null;
|
|
@@ -414,11 +457,33 @@ class N3Parser {
|
|
|
414
457
|
if (parentParent.type === '<<') {
|
|
415
458
|
return this._error('Unexpected compound blank node expression in reified triple', token);
|
|
416
459
|
}
|
|
460
|
+
if (token.type === 'id') return this._readIriPropertyListId;
|
|
417
461
|
this._predicate = null;
|
|
418
462
|
return this._readPredicate(token);
|
|
419
463
|
}
|
|
420
464
|
}
|
|
421
465
|
|
|
466
|
+
// ### `_readIriPropertyListId` replaces a property list's blank node with its IRI
|
|
467
|
+
_readIriPropertyListId(token) {
|
|
468
|
+
const iri = this._readEntity(token);
|
|
469
|
+
if (iri === undefined) return;
|
|
470
|
+
if (iri.termType !== 'NamedNode') return this._error(`Expected IRI after id but got ${token.type}`, token);
|
|
471
|
+
const placeholder = this._subject;
|
|
472
|
+
this._subject = iri;
|
|
473
|
+
const context = this._contextStack[this._contextStack.length - 1];
|
|
474
|
+
if (context.subject === placeholder) context.subject = iri;
|
|
475
|
+
if (context.predicate === placeholder) context.predicate = iri;
|
|
476
|
+
if (context.object === placeholder) context.object = iri;
|
|
477
|
+
this._predicate = null;
|
|
478
|
+
return this._readIriPropertyListPredicate;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ### `_readIriPropertyListPredicate` requires properties after an IRI property list ID
|
|
482
|
+
_readIriPropertyListPredicate(token) {
|
|
483
|
+
if (token.type === ';' || token.type === ']' || token.type === '.' || token.type === '}') return this._error(`Expected predicate but got ${token.type}`, token);
|
|
484
|
+
return this._readPredicate(token);
|
|
485
|
+
}
|
|
486
|
+
|
|
422
487
|
// ### `_readBlankNodeTail` reads the end of a blank node
|
|
423
488
|
_readBlankNodeTail(token) {
|
|
424
489
|
if (token.type !== ']') return this._readBlankNodePunctuation(token);
|
|
@@ -432,7 +497,7 @@ class N3Parser {
|
|
|
432
497
|
// If the blank node was the object, restore previous context and read punctuation
|
|
433
498
|
if (this._object !== null) return this._getContextEndReader();
|
|
434
499
|
// If the blank node was the predicate, continue reading the object
|
|
435
|
-
else if (this._predicate !== null) return this._readObject;
|
|
500
|
+
else if (this._predicate !== null) return this._getPathReader(this._readObject, 'predicate');
|
|
436
501
|
// If the blank node was the subject, continue reading the predicate
|
|
437
502
|
else
|
|
438
503
|
// If the blank node was empty, it could be a named graph label
|
|
@@ -504,7 +569,7 @@ class N3Parser {
|
|
|
504
569
|
// Was this list the parent's predicate?
|
|
505
570
|
else if (this._object === null) {
|
|
506
571
|
// The next token is the object
|
|
507
|
-
next = this._readObject;
|
|
572
|
+
next = this._getPathReader(this._readObject, 'predicate');
|
|
508
573
|
// No list tail if this was an empty list
|
|
509
574
|
if (this._predicate === this.RDF_NIL) return next;
|
|
510
575
|
}
|
|
@@ -551,7 +616,7 @@ class N3Parser {
|
|
|
551
616
|
// Stack the current list quad and start the formula
|
|
552
617
|
this._saveContext('formula', this._graph, list, this.RDF_FIRST, this._graph = item);
|
|
553
618
|
this._subject = null;
|
|
554
|
-
return this.
|
|
619
|
+
return this._readInFormulaContext;
|
|
555
620
|
case '<<(':
|
|
556
621
|
this._saveContext('<<(', this._graph, null, null, null);
|
|
557
622
|
this._graph = null;
|
|
@@ -662,12 +727,12 @@ class N3Parser {
|
|
|
662
727
|
this._literalLanguage = undefined;
|
|
663
728
|
token = null;
|
|
664
729
|
}
|
|
665
|
-
if (component === 'subject') {
|
|
666
|
-
// A subject literal implies N3 mode, so
|
|
667
|
-
const
|
|
668
|
-
|
|
730
|
+
if (component === 'subject' || component === 'predicate') {
|
|
731
|
+
// A subject or predicate literal implies N3 mode, so it might start a path
|
|
732
|
+
const next = component === 'subject' ? this._readPredicateOrNamedGraph : this._readObject;
|
|
733
|
+
const reader = this._getPathEndReader(token, next, component);
|
|
734
|
+
return reader || next.call(this, token);
|
|
669
735
|
}
|
|
670
|
-
if (component === 'predicate') return token === null ? this._readObject : this._readObject(token);
|
|
671
736
|
return this._completeObjectLiteralPost(token, listItem);
|
|
672
737
|
}
|
|
673
738
|
|
|
@@ -691,15 +756,12 @@ class N3Parser {
|
|
|
691
756
|
return this._readDirCode;
|
|
692
757
|
}
|
|
693
758
|
|
|
694
|
-
// A subject literal implies N3 mode, so it might start a path.
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
if (reader) return reader;
|
|
698
|
-
}
|
|
759
|
+
// A subject or predicate literal implies N3 mode, so it might start a path.
|
|
760
|
+
const reader = this._getPathEndReader(completed.token, next, component);
|
|
761
|
+
if (reader) return reader;
|
|
699
762
|
|
|
700
|
-
//
|
|
701
|
-
|
|
702
|
-
return completed.token === null ? next : next.call(this, completed.token);
|
|
763
|
+
// Consume the non-path token now
|
|
764
|
+
return next.call(this, completed.token);
|
|
703
765
|
}
|
|
704
766
|
|
|
705
767
|
// Completes a literal in subject position
|
|
@@ -764,12 +826,13 @@ class N3Parser {
|
|
|
764
826
|
// is read as the boolean literal true, following the N3 spec tests
|
|
765
827
|
// and the direction discussed in https://github.com/w3c-cg/N3/issues/185
|
|
766
828
|
if (empty && this._emptyFormulaAsTrue) {
|
|
767
|
-
if (this._subject === formula) this._subject = this.N3_TRUE;else this._object = this.N3_TRUE;
|
|
829
|
+
if (this._subject === formula) this._subject = this.N3_TRUE;else if (this._predicate === formula) this._predicate = this.N3_TRUE;else this._object = this.N3_TRUE;
|
|
768
830
|
}
|
|
769
831
|
|
|
770
|
-
//
|
|
771
|
-
|
|
772
|
-
|
|
832
|
+
// Continue according to the formula's position in the enclosing statement
|
|
833
|
+
if (this._object !== null) return this._getPathReader(this._getContextEndReader(), 'object');
|
|
834
|
+
if (this._predicate !== null) return this._getPathReader(this._readObject, 'predicate');
|
|
835
|
+
return this._getPathReader(this._readPredicate, 'subject');
|
|
773
836
|
}
|
|
774
837
|
|
|
775
838
|
// ### `_readPunctuation` reads punctuation between quads or quad parts
|
|
@@ -789,7 +852,7 @@ class N3Parser {
|
|
|
789
852
|
case '.':
|
|
790
853
|
this._subject = null;
|
|
791
854
|
this._tripleTerm = null;
|
|
792
|
-
next = this.
|
|
855
|
+
next = this._getStatementReader();
|
|
793
856
|
if (inversePredicate) this._inversePredicate = false;
|
|
794
857
|
break;
|
|
795
858
|
// Semicolon means the subject is shared; predicate and object are different
|
|
@@ -947,10 +1010,10 @@ class N3Parser {
|
|
|
947
1010
|
// SPARQL-style declarations don't have punctuation
|
|
948
1011
|
if (this._sparqlStyle) {
|
|
949
1012
|
this._sparqlStyle = false;
|
|
950
|
-
return this.
|
|
1013
|
+
return this._getStatementReader().call(this, token);
|
|
951
1014
|
}
|
|
952
1015
|
if (token.type !== '.') return this._error('Expected declaration to end with a dot', token);
|
|
953
|
-
return this.
|
|
1016
|
+
return this._getStatementReader();
|
|
954
1017
|
}
|
|
955
1018
|
|
|
956
1019
|
// Reads a list of quantified symbols from a @forSome or @forAll statement
|
|
@@ -995,17 +1058,18 @@ class N3Parser {
|
|
|
995
1058
|
}
|
|
996
1059
|
|
|
997
1060
|
// ### `_getPathReader` reads a potential path and then resumes with the given function
|
|
998
|
-
_getPathReader(afterPath) {
|
|
1061
|
+
_getPathReader(afterPath, position) {
|
|
999
1062
|
this._afterPath = afterPath;
|
|
1063
|
+
this._pathPosition = position || (this._predicate === null ? 'subject' : 'object');
|
|
1000
1064
|
return this._readPath;
|
|
1001
1065
|
}
|
|
1002
1066
|
|
|
1003
1067
|
// ### `_getPathEndReader` continues reading after a term that might start a path,
|
|
1004
1068
|
// given the pending token that follows the term (or `null` if it was consumed)
|
|
1005
|
-
_getPathEndReader(token, afterPath) {
|
|
1069
|
+
_getPathEndReader(token, afterPath, position) {
|
|
1006
1070
|
// Other pending tokens are not handled here
|
|
1007
1071
|
if (token !== null && token.type !== '!' && token.type !== '^') return null;
|
|
1008
|
-
const reader = this._getPathReader(afterPath);
|
|
1072
|
+
const reader = this._getPathReader(afterPath, position);
|
|
1009
1073
|
// If no token is pending, wait for the next one; otherwise, consume it now
|
|
1010
1074
|
return token === null ? reader : reader.call(this, token);
|
|
1011
1075
|
}
|
|
@@ -1021,6 +1085,7 @@ class N3Parser {
|
|
|
1021
1085
|
return this._readBackwardPath;
|
|
1022
1086
|
// Not a path; resume reading where we left off
|
|
1023
1087
|
default:
|
|
1088
|
+
const afterPath = this._afterPath;
|
|
1024
1089
|
const stack = this._contextStack,
|
|
1025
1090
|
parent = stack.length && stack[stack.length - 1];
|
|
1026
1091
|
// If we were reading a list item, we still need to output it
|
|
@@ -1032,7 +1097,9 @@ class N3Parser {
|
|
|
1032
1097
|
// Output the list item
|
|
1033
1098
|
this._emit(this._subject, this.RDF_FIRST, item, this._graph);
|
|
1034
1099
|
}
|
|
1035
|
-
|
|
1100
|
+
this._afterPath = null;
|
|
1101
|
+
this._pathPosition = null;
|
|
1102
|
+
return afterPath.call(this, token);
|
|
1036
1103
|
}
|
|
1037
1104
|
}
|
|
1038
1105
|
|
|
@@ -1042,10 +1109,8 @@ class N3Parser {
|
|
|
1042
1109
|
const object = this._factory.blankNode();
|
|
1043
1110
|
// The next token is the predicate
|
|
1044
1111
|
if ((predicate = this._readEntity(token)) === undefined) return;
|
|
1045
|
-
//
|
|
1046
|
-
if (this.
|
|
1047
|
-
// If we were reading an object, replace the subject by the path's object
|
|
1048
|
-
else subject = this._object, this._object = object;
|
|
1112
|
+
// Replace the path expression with the generated object in its current position
|
|
1113
|
+
if (this._pathPosition === 'subject') subject = this._subject, this._subject = object;else if (this._pathPosition === 'predicate') subject = this._predicate, this._predicate = object;else subject = this._object, this._object = object;
|
|
1049
1114
|
// Emit the path's current quad and read its next section
|
|
1050
1115
|
this._emit(subject, predicate, object, this._graph);
|
|
1051
1116
|
return this._readPath;
|
|
@@ -1057,10 +1122,8 @@ class N3Parser {
|
|
|
1057
1122
|
let predicate, object;
|
|
1058
1123
|
// The next token is the predicate
|
|
1059
1124
|
if ((predicate = this._readEntity(token)) === undefined) return;
|
|
1060
|
-
//
|
|
1061
|
-
if (this.
|
|
1062
|
-
// If we were reading an object, replace the subject by the path's subject
|
|
1063
|
-
else object = this._object, this._object = subject;
|
|
1125
|
+
// Replace the path expression with the generated subject in its current position
|
|
1126
|
+
if (this._pathPosition === 'subject') object = this._subject, this._subject = subject;else if (this._pathPosition === 'predicate') object = this._predicate, this._predicate = subject;else object = this._object, this._object = subject;
|
|
1064
1127
|
// Emit the path's current quad and read its next section
|
|
1065
1128
|
this._emit(subject, predicate, object, this._graph);
|
|
1066
1129
|
return this._readPath;
|
package/package.json
CHANGED
package/src/N3Lexer.js
CHANGED
|
@@ -57,6 +57,7 @@ export default class N3Lexer {
|
|
|
57
57
|
this._boolean = /^(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/;
|
|
58
58
|
this._atKeyword = /^@[a-z]+(?=[\s#<:])/i;
|
|
59
59
|
this._keyword = /^(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/i;
|
|
60
|
+
this._n3Id = /^id(?=[\s#<])/;
|
|
60
61
|
this._shortPredicates = /^a(?=[\s#()\[\]\{\}"'<>])/;
|
|
61
62
|
this._newline = /^[ \t]*(?:#[^\n\r]*)?(?:\r\n|\n|\r)[ \t]*/;
|
|
62
63
|
this._comment = /#([^\n\r]*)/;
|
|
@@ -318,6 +319,14 @@ export default class N3Lexer {
|
|
|
318
319
|
inconclusive = true;
|
|
319
320
|
break;
|
|
320
321
|
|
|
322
|
+
case 'i':
|
|
323
|
+
// Try to find the IRI property list identifier
|
|
324
|
+
if (this._n3Mode && (match = this._n3Id.exec(input)))
|
|
325
|
+
type = 'id';
|
|
326
|
+
else
|
|
327
|
+
inconclusive = true;
|
|
328
|
+
break;
|
|
329
|
+
|
|
321
330
|
case '=':
|
|
322
331
|
// Try to find an implication arrow or equals sign
|
|
323
332
|
if (this._n3Mode && input.length > 1) {
|