n3 2.13.4 → 2.13.5
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/browser/n3.esm.min.js +10 -9
- package/browser/n3.min.js +10 -9
- package/lib/N3Lexer.js +127 -28
- package/lib/N3Reasoner.js +5 -4
- package/lib/N3Store.js +58 -27
- package/package.json +1 -1
- package/src/N3Lexer.js +150 -32
- package/src/N3Reasoner.js +6 -4
- package/src/N3Store.js +55 -29
package/lib/N3Lexer.js
CHANGED
|
@@ -15,7 +15,33 @@ const SPACE = 0x20,
|
|
|
15
15
|
TAB = 0x09,
|
|
16
16
|
LF = 0x0A,
|
|
17
17
|
CR = 0x0D,
|
|
18
|
-
HASH = 0x23
|
|
18
|
+
HASH = 0x23,
|
|
19
|
+
DOT = 0x2E,
|
|
20
|
+
COLON = 0x3A,
|
|
21
|
+
ZERO = 0x30,
|
|
22
|
+
NINE = 0x39,
|
|
23
|
+
PERCENT = 0x25,
|
|
24
|
+
BACKSLASH = 0x5C;
|
|
25
|
+
|
|
26
|
+
// Whitespace as matched by `\s`
|
|
27
|
+
function isWhitespace(charCode) {
|
|
28
|
+
return charCode === SPACE || charCode >= TAB && charCode <= CR || charCode >= 0xA0 && (charCode === 0xA0 || charCode === 0x1680 || charCode >= 0x2000 && charCode <= 0x200A || charCode === 0x2028 || charCode === 0x2029 || charCode === 0x202F || charCode === 0x205F || charCode === 0x3000 || charCode === 0xFEFF);
|
|
29
|
+
}
|
|
30
|
+
// Characters that can directly follow a name: whitespace and punctuation
|
|
31
|
+
// (the lookahead `[,;!\^\s#()\[\]\{\}"'<>]` of the former regular expressions)
|
|
32
|
+
const asciiDelimiters = new Uint8Array(0x80);
|
|
33
|
+
for (const char of ',;!^#()[]{}"\'<> \t\n\v\f\r') asciiDelimiters[char.charCodeAt(0)] = 1;
|
|
34
|
+
function isDelimiter(charCode) {
|
|
35
|
+
return charCode < 0x80 ? asciiDelimiters[charCode] === 1 : isWhitespace(charCode);
|
|
36
|
+
}
|
|
37
|
+
// Whether a name can end before the given position: it must be followed by
|
|
38
|
+
// a delimiter, optionally after a dot. At the end of finished input, it can always end.
|
|
39
|
+
function canEndName(input, pos, inputFinished) {
|
|
40
|
+
let charCode = input.charCodeAt(pos);
|
|
41
|
+
if (charCode === DOT) charCode = input.charCodeAt(++pos);
|
|
42
|
+
if (pos >= input.length) return inputFinished;
|
|
43
|
+
return isDelimiter(charCode);
|
|
44
|
+
}
|
|
19
45
|
|
|
20
46
|
// Fixed escape sequences allowed in string literals (ECHAR)
|
|
21
47
|
const stringEscapeReplacements = {
|
|
@@ -52,9 +78,87 @@ const localNameEscapeReplacements = {
|
|
|
52
78
|
'%': '%'
|
|
53
79
|
};
|
|
54
80
|
const illegalIriChars = /[\x00-\x20<>\\"\{\}\|\^\`]/;
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
const
|
|
81
|
+
|
|
82
|
+
// Character classes of names, as bit flags for ASCII characters
|
|
83
|
+
const PREFIX_START = 1,
|
|
84
|
+
// PN_CHARS_BASE
|
|
85
|
+
LOCAL_START = 2,
|
|
86
|
+
// PN_CHARS_U, digits, and colon
|
|
87
|
+
NAME_CHAR = 4,
|
|
88
|
+
// PN_CHARS
|
|
89
|
+
LOCAL_CHAR = 8,
|
|
90
|
+
// PN_CHARS and colon
|
|
91
|
+
LOCAL_ESCAPE = 16; // characters that can be escaped in local names (PN_LOCAL_ESC)
|
|
92
|
+
const asciiNameClasses = new Uint8Array(0x80);
|
|
93
|
+
for (let charCode = 0; charCode < 0x80; charCode++) {
|
|
94
|
+
const char = String.fromCharCode(charCode);
|
|
95
|
+
const letter = char >= 'A' && char <= 'Z' || char >= 'a' && char <= 'z';
|
|
96
|
+
const nameChar = letter || char === '_' || char === '-' || char >= '0' && char <= '9';
|
|
97
|
+
asciiNameClasses[charCode] = (letter ? PREFIX_START : 0) | (nameChar && char !== '-' || char === ':' ? LOCAL_START : 0) | (nameChar ? NAME_CHAR : 0) | (nameChar || char === ':' ? LOCAL_CHAR : 0) | (char in localNameEscapeReplacements ? LOCAL_ESCAPE : 0);
|
|
98
|
+
}
|
|
99
|
+
// Returns the length (0, 1, or 2 code units) of the character at the given position
|
|
100
|
+
// if it is in the given name character class
|
|
101
|
+
function nameCharLength(input, pos, charClass) {
|
|
102
|
+
const charCode = input.charCodeAt(pos);
|
|
103
|
+
if (charCode < 0x80) return (asciiNameClasses[charCode] & charClass) !== 0 ? 1 : 0;
|
|
104
|
+
// Characters from U+10000 to U+EFFFF consist of a surrogate pair
|
|
105
|
+
if (charCode >= 0xD800 && charCode <= 0xDB7F) {
|
|
106
|
+
const low = input.charCodeAt(pos + 1);
|
|
107
|
+
return low >= 0xDC00 && low <= 0xDFFF ? 2 : 0;
|
|
108
|
+
}
|
|
109
|
+
// PN_CHARS has some characters that PN_CHARS_BASE does not
|
|
110
|
+
if (charClass >= NAME_CHAR && (charCode === 0xB7 || charCode >= 0x300 && charCode <= 0x36F || charCode === 0x203F || charCode === 0x2040)) return 1;
|
|
111
|
+
// PN_CHARS_BASE
|
|
112
|
+
return charCode >= 0xC0 && charCode <= 0x1FFF && charCode !== 0xD7 && charCode !== 0xF7 && (charCode < 0x300 || charCode >= 0x370 && charCode !== 0x37E) || charCode >= 0x200C && charCode <= 0x200D || charCode >= 0x2070 && charCode <= 0x218F || charCode >= 0x2C00 && charCode <= 0x2FEF || charCode >= 0x3001 && charCode <= 0xD7FF || charCode >= 0xF900 && charCode <= 0xFDCF || charCode >= 0xFDF0 && charCode <= 0xFFFD ? 1 : 0;
|
|
113
|
+
}
|
|
114
|
+
function isLocalEscape(charCode) {
|
|
115
|
+
return charCode < 0x80 && (asciiNameClasses[charCode] & LOCAL_ESCAPE) !== 0;
|
|
116
|
+
}
|
|
117
|
+
function isHexDigit(charCode) {
|
|
118
|
+
return charCode >= ZERO && charCode <= NINE || charCode >= 0x41 && charCode <= 0x46 || charCode >= 0x61 && charCode <= 0x66;
|
|
119
|
+
}
|
|
120
|
+
// Returns the end of the prefix (PN_PREFIX) at the given position,
|
|
121
|
+
// which can contain single dots, but not start or end with one
|
|
122
|
+
function skipPrefix(input, pos) {
|
|
123
|
+
let length = nameCharLength(input, pos, PREFIX_START);
|
|
124
|
+
while (length !== 0) {
|
|
125
|
+
pos += length;
|
|
126
|
+
const next = input.charCodeAt(pos) === DOT ? pos + 1 : pos;
|
|
127
|
+
// Most names are ASCII, so look those up without a call
|
|
128
|
+
const charCode = input.charCodeAt(next);
|
|
129
|
+
length = charCode < 0x80 ? (asciiNameClasses[charCode] & NAME_CHAR) !== 0 ? 1 : 0 : nameCharLength(input, next, NAME_CHAR);
|
|
130
|
+
if (length !== 0) pos = next;
|
|
131
|
+
}
|
|
132
|
+
return pos;
|
|
133
|
+
}
|
|
134
|
+
// Returns the end of the local name (PN_LOCAL) at the given position,
|
|
135
|
+
// which can contain dots, but not start or end with one
|
|
136
|
+
function skipLocalName(input, pos) {
|
|
137
|
+
let end = pos,
|
|
138
|
+
charClass = LOCAL_START;
|
|
139
|
+
while (true) {
|
|
140
|
+
// Most names are ASCII, so look those up without a call
|
|
141
|
+
const charCode = input.charCodeAt(pos);
|
|
142
|
+
let length = charCode < 0x80 ? (asciiNameClasses[charCode] & charClass) !== 0 ? 1 : 0 : nameCharLength(input, pos, charClass);
|
|
143
|
+
// Percent-encoded character (PERCENT)
|
|
144
|
+
if (length === 0 && charCode === PERCENT && isHexDigit(input.charCodeAt(pos + 1)) && isHexDigit(input.charCodeAt(pos + 2))) length = 3;
|
|
145
|
+
// Escaped character (PN_LOCAL_ESC)
|
|
146
|
+
else if (length === 0 && charCode === BACKSLASH && isLocalEscape(input.charCodeAt(pos + 1))) length = 2;
|
|
147
|
+
if (length !== 0) {
|
|
148
|
+
end = pos += length;
|
|
149
|
+
charClass = LOCAL_CHAR;
|
|
150
|
+
}
|
|
151
|
+
// Dots are allowed after the first character, but not at the end
|
|
152
|
+
else if (charCode === DOT && charClass === LOCAL_CHAR) pos++;else return end;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Returns the end of the prefixed name at the given position, or -1 if there is none
|
|
156
|
+
function skipPrefixedName(input, pos, inputFinished) {
|
|
157
|
+
const colon = skipPrefix(input, pos);
|
|
158
|
+
if (input.charCodeAt(colon) !== COLON) return -1;
|
|
159
|
+
const end = skipLocalName(input, colon + 1);
|
|
160
|
+
return canEndName(input, end, inputFinished) ? end : -1;
|
|
161
|
+
}
|
|
58
162
|
|
|
59
163
|
// A valid code point is a Unicode scalar value: at most U+10FFFF and not a surrogate
|
|
60
164
|
function isValidCodePoint(charCode) {
|
|
@@ -110,8 +214,6 @@ class N3Lexer {
|
|
|
110
214
|
this._simpleQuotedString = /"([^"\\\r\n]*)"(?=[^"])/y; // string without escape sequences
|
|
111
215
|
this._simpleApostropheString = /'([^'\\\r\n]*)'(?=[^'])/y;
|
|
112
216
|
this._langcode = /@([a-z]+(?:-[a-z0-9]+)*)(?=[^a-z0-9])/iy;
|
|
113
|
-
this._prefix = /((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:(?=[#\s<])/y;
|
|
114
|
-
this._prefixed = /((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:((?:(?:[0-9:A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])(?:(?:[\.\-0-9:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])*(?:[\-0-9:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~]))?)?)(?:[ \t]+|(?=\.?[,;!\^\s#()\[\]\{\}"'<>]))/y;
|
|
115
217
|
this._variable = /\?(?:(?:[A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:[\-0-9:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
|
|
116
218
|
this._blank = /_:((?:[0-9A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?:[ \t]+|(?=\.?[,;:!\^\s#()\[\]\{\}"'<>]))/y;
|
|
117
219
|
this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
|
|
@@ -476,17 +578,22 @@ class N3Lexer {
|
|
|
476
578
|
// Some first characters do not allow an immediate decision, so inspect more
|
|
477
579
|
if (inconclusive) {
|
|
478
580
|
// Try to find a prefix
|
|
479
|
-
|
|
581
|
+
let end;
|
|
582
|
+
if ((this._previousMarker === '@prefix' || this._previousMarker === 'PREFIX') && input.charCodeAt(end = skipPrefix(input, pos)) === COLON && end + 1 < input.length && (input[end + 1] === '#' || input[end + 1] === '<' || isWhitespace(input.charCodeAt(end + 1)))) {
|
|
583
|
+
type = 'prefix', value = input.slice(pos, end);
|
|
584
|
+
matchLength = end + 1 - pos;
|
|
585
|
+
}
|
|
480
586
|
// Try to find an additional directive keyword
|
|
481
587
|
// (at the end of the input, only a short final word can be one)
|
|
482
588
|
else if (this._directive !== null && this._directiveStarts.includes(firstChar) && ((match = execAt(this._directive, input, pos)) || inputFinished && input.length - pos <= this._directiveMaxLength && (match = execAtEnd(this._directive, input, pos)))) type = match[0].toUpperCase();
|
|
483
589
|
// Try to find a prefixed name. Since it can contain (but not end with) a dot,
|
|
484
|
-
// we always need a non-dot character before deciding it is a prefixed name
|
|
485
|
-
//
|
|
486
|
-
else if (
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
590
|
+
// we always need a non-dot character before deciding it is a prefixed name,
|
|
591
|
+
// except at the end of the input.
|
|
592
|
+
else if (!this._lineMode && (end = skipPrefixedName(input, pos, inputFinished)) >= 0) {
|
|
593
|
+
const colon = input.indexOf(':', pos);
|
|
594
|
+
type = 'prefixed', prefix = input.slice(pos, colon);
|
|
595
|
+
value = this._unescape(input.slice(colon + 1, end), localNameEscapeReplacements);
|
|
596
|
+
matchLength = end - pos;
|
|
490
597
|
}
|
|
491
598
|
}
|
|
492
599
|
|
|
@@ -595,25 +702,17 @@ class N3Lexer {
|
|
|
595
702
|
if (!verb) return null;
|
|
596
703
|
|
|
597
704
|
// Most verb boundaries cannot be part of a prefix, so keep the common path fast.
|
|
705
|
+
// U+1680 and U+FEFF are whitespace to the regular expression but name characters.
|
|
598
706
|
const next = input[pos + verb[0].length];
|
|
599
|
-
if (next !== '-' && next !== '_' && (next < '0' || next > '9')) return verb;
|
|
707
|
+
if (next !== '-' && next !== '_' && (next < '0' || next > '9') && next !== '\u1680' && next !== '\ufeff') return verb;
|
|
600
708
|
|
|
601
709
|
// A prefix can start with a verb and continue with characters that are also
|
|
602
710
|
// valid verb boundaries. Prefer the longer prefixed name when it is complete.
|
|
603
|
-
if (
|
|
604
|
-
|
|
605
|
-
//
|
|
606
|
-
//
|
|
607
|
-
if (
|
|
608
|
-
if (execAtEnd(this._prefixed, input, pos)) return null;
|
|
609
|
-
|
|
610
|
-
// If a stream chunk ends partway through such a prefix, wait for the colon
|
|
611
|
-
// instead of prematurely emitting the verb. Appending ": " lets the prefix
|
|
612
|
-
// grammar determine whether all input seen so far can be a complete prefix.
|
|
613
|
-
if (!inputFinished) {
|
|
614
|
-
const prefix = execAt(this._prefix, `${input.slice(pos)}: `, 0);
|
|
615
|
-
if (prefix) return null;
|
|
616
|
-
}
|
|
711
|
+
if (skipPrefixedName(input, pos, true) >= 0) return null;
|
|
712
|
+
|
|
713
|
+
// If a stream chunk ends partway through such a prefix,
|
|
714
|
+
// wait for the colon instead of prematurely emitting the verb.
|
|
715
|
+
if (!inputFinished && skipPrefix(input, pos) === input.length) return null;
|
|
617
716
|
return verb;
|
|
618
717
|
}
|
|
619
718
|
|
package/lib/N3Reasoner.js
CHANGED
|
@@ -44,6 +44,7 @@ class N3Reasoner {
|
|
|
44
44
|
if (!store._addToIndex(graphItem.subjects, subject, predicate, object)) return;
|
|
45
45
|
store._addToIndex(graphItem.predicates, predicate, object, subject);
|
|
46
46
|
store._addToIndex(graphItem.objects, object, subject, predicate);
|
|
47
|
+
this._indexed++;
|
|
47
48
|
}
|
|
48
49
|
// Count genuinely new derivations and fail past the budget. The check comes
|
|
49
50
|
// after all three indexes are updated, so a caught error leaves the store
|
|
@@ -176,7 +177,7 @@ class N3Reasoner {
|
|
|
176
177
|
};
|
|
177
178
|
}
|
|
178
179
|
reason(rules) {
|
|
179
|
-
this._derivations = 0;
|
|
180
|
+
this._derivations = this._indexed = 0;
|
|
180
181
|
if (!Array.isArray(rules)) {
|
|
181
182
|
rules = getRulesFromDataset(rules);
|
|
182
183
|
}
|
|
@@ -236,9 +237,9 @@ class N3Reasoner {
|
|
|
236
237
|
this._reasonGraphNaive(rules, graphs[graphId]);
|
|
237
238
|
}
|
|
238
239
|
} finally {
|
|
239
|
-
//
|
|
240
|
-
// so a caught budget error leaves the store
|
|
241
|
-
this._store._size
|
|
240
|
+
// Count the quads added directly to the indexes, even if a derivation
|
|
241
|
+
// budget was exceeded, so a caught budget error leaves the store consistent
|
|
242
|
+
if (this._store._size !== null) this._store._size += this._indexed;
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
}
|
package/lib/N3Store.js
CHANGED
|
@@ -31,11 +31,15 @@ function hasInIndex(index0, key0, key1, key2) {
|
|
|
31
31
|
const index2 = index1 && index1[key1];
|
|
32
32
|
return !!index2 && key2 in index2;
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
// The number of index leaves that the last `merge` call added
|
|
36
|
+
let mergedLeaves = 0;
|
|
34
37
|
function merge(target, source, depth = 4) {
|
|
35
38
|
let size = target[SIZE] || 0;
|
|
36
39
|
for (const key in source) {
|
|
37
40
|
if (!(key in target)) {
|
|
38
41
|
size++;
|
|
42
|
+
if (depth === 0) mergedLeaves++;
|
|
39
43
|
target[key] = depth === 0 ? null : merge(Object.create(null), source[key], depth - 1);
|
|
40
44
|
}
|
|
41
45
|
// Merge into the existing object in place,
|
|
@@ -216,8 +220,14 @@ class N3EntityIndex {
|
|
|
216
220
|
}
|
|
217
221
|
}
|
|
218
222
|
|
|
219
|
-
//
|
|
223
|
+
// Returns the size of a store from this copy of N3 if it is known without counting,
|
|
224
|
+
// or null otherwise
|
|
220
225
|
exports.N3EntityIndex = N3EntityIndex;
|
|
226
|
+
function knownSize(dataset) {
|
|
227
|
+
return dataset instanceof N3Store ? dataset._size : null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ## Constructor
|
|
221
231
|
class N3Store {
|
|
222
232
|
constructor(quads, options) {
|
|
223
233
|
// The number of quads is initially zero
|
|
@@ -248,16 +258,10 @@ class N3Store {
|
|
|
248
258
|
|
|
249
259
|
// ### `size` returns the number of quads in the store
|
|
250
260
|
get size() {
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
// Calculate the number of quads by counting to the deepest level
|
|
256
|
-
size = 0;
|
|
257
|
-
const graphs = this._graphs;
|
|
258
|
-
let subjects, subject;
|
|
259
|
-
for (const graphKey in graphs) for (const subjectKey in subjects = graphs[graphKey].subjects) for (const predicateKey in subject = subjects[subjectKey]) size += subject[predicateKey][SIZE];
|
|
260
|
-
return this._size = size;
|
|
261
|
+
// The quad count is kept up to date by every change,
|
|
262
|
+
// except by reasoners from earlier versions of N3, which set it to null
|
|
263
|
+
if (this._size === null) this._size = this.countQuads();
|
|
264
|
+
return this._size;
|
|
261
265
|
}
|
|
262
266
|
|
|
263
267
|
// ## Private methods
|
|
@@ -534,9 +538,7 @@ class N3Store {
|
|
|
534
538
|
if (!this._addToIndex(graphItem.subjects, subject, predicate, object)) return false;
|
|
535
539
|
this._addToIndex(graphItem.predicates, predicate, object, subject);
|
|
536
540
|
this._addToIndex(graphItem.objects, object, subject, predicate);
|
|
537
|
-
|
|
538
|
-
// The cached quad count is now invalid
|
|
539
|
-
this._size = null;
|
|
541
|
+
if (this._size !== null) this._size++;
|
|
540
542
|
return true;
|
|
541
543
|
}
|
|
542
544
|
|
|
@@ -734,9 +736,12 @@ class N3Store {
|
|
|
734
736
|
} else if (predicate) {
|
|
735
737
|
// If only predicate and possibly object are given, the predicate index will be the fastest
|
|
736
738
|
count += this._countInIndex(content.predicates, predicateId, objectId, subjectId);
|
|
737
|
-
} else {
|
|
738
|
-
// If only object is
|
|
739
|
+
} else if (object) {
|
|
740
|
+
// If only object is given, the object index will be the fastest
|
|
739
741
|
count += this._countInIndex(content.objects, objectId, subjectId, predicateId);
|
|
742
|
+
} else {
|
|
743
|
+
// Without a pattern, the subject index sums the fewest leaf counts
|
|
744
|
+
count += this._countInIndex(content.subjects);
|
|
740
745
|
}
|
|
741
746
|
}
|
|
742
747
|
}
|
|
@@ -1022,9 +1027,11 @@ class N3Store {
|
|
|
1022
1027
|
if (Array.isArray(quads)) this.addQuads(quads);
|
|
1023
1028
|
// Index merging bypasses observer notifications
|
|
1024
1029
|
else if (this._observers === null && quads instanceof N3Store && quads._entityIndex === this._entityIndex) {
|
|
1025
|
-
if (quads.
|
|
1030
|
+
if (quads.size !== 0) {
|
|
1031
|
+
// Each new quad adds one leaf to each of the three indexes
|
|
1032
|
+
mergedLeaves = 0;
|
|
1026
1033
|
this._graphs = merge(this._graphs, quads._graphs);
|
|
1027
|
-
this._size
|
|
1034
|
+
if (this._size !== null) this._size += mergedLeaves / 3;
|
|
1028
1035
|
}
|
|
1029
1036
|
} else {
|
|
1030
1037
|
for (const quad of quads) this.add(quad);
|
|
@@ -1041,18 +1048,27 @@ class N3Store {
|
|
|
1041
1048
|
contains(other) {
|
|
1042
1049
|
if (other instanceof DatasetCoreAndReadableStream) other = other.filtered;
|
|
1043
1050
|
if (other === this) return true;
|
|
1044
|
-
if (!(other instanceof N3Store) || this._entityIndex !== other._entityIndex)
|
|
1051
|
+
if (!(other instanceof N3Store) || this._entityIndex !== other._entityIndex) {
|
|
1052
|
+
// A larger set cannot be a subset, but only compare sizes that are known without counting
|
|
1053
|
+
const otherSize = knownSize(other),
|
|
1054
|
+
thisSize = this._size;
|
|
1055
|
+
if (otherSize !== null && thisSize !== null && otherSize > thisSize) return false;
|
|
1056
|
+
return other.every(quad => this.has(quad));
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// At every level, the index of the subset cannot have more entries
|
|
1045
1060
|
const g1 = this._graphs,
|
|
1046
1061
|
g2 = other._graphs;
|
|
1047
|
-
let s1, s2, p1, p2, o1;
|
|
1062
|
+
let s1, s2, p1, p2, o1, o2;
|
|
1048
1063
|
for (const graph in g2) {
|
|
1049
1064
|
if (!(s1 = g1[graph])) return false;
|
|
1050
1065
|
s1 = s1.subjects;
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1066
|
+
if ((s2 = g2[graph].subjects)[SIZE] > s1[SIZE]) return false;
|
|
1067
|
+
for (const subject in s2) {
|
|
1068
|
+
if (!(p1 = s1[subject]) || (p2 = s2[subject])[SIZE] > p1[SIZE]) return false;
|
|
1069
|
+
for (const predicate in p2) {
|
|
1070
|
+
if (!(o1 = p1[predicate]) || (o2 = p2[predicate])[SIZE] > o1[SIZE]) return false;
|
|
1071
|
+
for (const object in o2) if (!(object in o1)) return false;
|
|
1056
1072
|
}
|
|
1057
1073
|
}
|
|
1058
1074
|
}
|
|
@@ -1132,7 +1148,7 @@ class N3Store {
|
|
|
1132
1148
|
entityIndex: this._entityIndex
|
|
1133
1149
|
});
|
|
1134
1150
|
store._graphs = merge(Object.create(null), this._graphs);
|
|
1135
|
-
store._size = this.
|
|
1151
|
+
store._size = this.size;
|
|
1136
1152
|
return store;
|
|
1137
1153
|
} else if (other instanceof N3Store && this._entityIndex === other._entityIndex) {
|
|
1138
1154
|
const store = new N3Store({
|
|
@@ -1145,6 +1161,21 @@ class N3Store {
|
|
|
1145
1161
|
}
|
|
1146
1162
|
return store;
|
|
1147
1163
|
}
|
|
1164
|
+
|
|
1165
|
+
// Test the quads of the smaller dataset against the larger one
|
|
1166
|
+
// when both sizes are known without counting
|
|
1167
|
+
const otherSize = knownSize(other),
|
|
1168
|
+
thisSize = this._size;
|
|
1169
|
+
if (otherSize !== null && thisSize !== null && otherSize < thisSize && typeof other[Symbol.iterator] === 'function' &&
|
|
1170
|
+
// unless it is a store whose custom factories may not create RDF/JS quads,
|
|
1171
|
+
// either for its quads or, through its entity index, for their terms
|
|
1172
|
+
other._factory === _N3DataFactory.default && other._entityIndex._factory === _N3DataFactory.default) {
|
|
1173
|
+
const store = new N3Store({
|
|
1174
|
+
entityIndex: this._entityIndex
|
|
1175
|
+
});
|
|
1176
|
+
for (const quad of other) if (this.has(quad)) store.add(quad);
|
|
1177
|
+
return store;
|
|
1178
|
+
}
|
|
1148
1179
|
return this.filter(quad => other.has(quad));
|
|
1149
1180
|
}
|
|
1150
1181
|
|
|
@@ -1220,7 +1251,7 @@ class N3Store {
|
|
|
1220
1251
|
entityIndex: this._entityIndex
|
|
1221
1252
|
});
|
|
1222
1253
|
store._graphs = merge(Object.create(null), this._graphs);
|
|
1223
|
-
store._size = this.
|
|
1254
|
+
store._size = this.size;
|
|
1224
1255
|
store.addAll(quads);
|
|
1225
1256
|
return store;
|
|
1226
1257
|
}
|
package/package.json
CHANGED
package/src/N3Lexer.js
CHANGED
|
@@ -2,7 +2,35 @@
|
|
|
2
2
|
import namespaces from './IRIs';
|
|
3
3
|
|
|
4
4
|
const { xsd } = namespaces;
|
|
5
|
-
const SPACE = 0x20, TAB = 0x09, LF = 0x0A, CR = 0x0D, HASH = 0x23
|
|
5
|
+
const SPACE = 0x20, TAB = 0x09, LF = 0x0A, CR = 0x0D, HASH = 0x23,
|
|
6
|
+
DOT = 0x2E, COLON = 0x3A, ZERO = 0x30, NINE = 0x39,
|
|
7
|
+
PERCENT = 0x25, BACKSLASH = 0x5C;
|
|
8
|
+
|
|
9
|
+
// Whitespace as matched by `\s`
|
|
10
|
+
function isWhitespace(charCode) {
|
|
11
|
+
return charCode === SPACE || charCode >= TAB && charCode <= CR ||
|
|
12
|
+
charCode >= 0xA0 && (charCode === 0xA0 || charCode === 0x1680 ||
|
|
13
|
+
charCode >= 0x2000 && charCode <= 0x200A || charCode === 0x2028 || charCode === 0x2029 ||
|
|
14
|
+
charCode === 0x202F || charCode === 0x205F || charCode === 0x3000 || charCode === 0xFEFF);
|
|
15
|
+
}
|
|
16
|
+
// Characters that can directly follow a name: whitespace and punctuation
|
|
17
|
+
// (the lookahead `[,;!\^\s#()\[\]\{\}"'<>]` of the former regular expressions)
|
|
18
|
+
const asciiDelimiters = new Uint8Array(0x80);
|
|
19
|
+
for (const char of ',;!^#()[]{}"\'<> \t\n\v\f\r')
|
|
20
|
+
asciiDelimiters[char.charCodeAt(0)] = 1;
|
|
21
|
+
function isDelimiter(charCode) {
|
|
22
|
+
return charCode < 0x80 ? asciiDelimiters[charCode] === 1 : isWhitespace(charCode);
|
|
23
|
+
}
|
|
24
|
+
// Whether a name can end before the given position: it must be followed by
|
|
25
|
+
// a delimiter, optionally after a dot. At the end of finished input, it can always end.
|
|
26
|
+
function canEndName(input, pos, inputFinished) {
|
|
27
|
+
let charCode = input.charCodeAt(pos);
|
|
28
|
+
if (charCode === DOT)
|
|
29
|
+
charCode = input.charCodeAt(++pos);
|
|
30
|
+
if (pos >= input.length)
|
|
31
|
+
return inputFinished;
|
|
32
|
+
return isDelimiter(charCode);
|
|
33
|
+
}
|
|
6
34
|
|
|
7
35
|
// Fixed escape sequences allowed in string literals (ECHAR)
|
|
8
36
|
const stringEscapeReplacements = {
|
|
@@ -16,9 +44,105 @@ const localNameEscapeReplacements = {
|
|
|
16
44
|
'=': '=', '/': '/', '?': '?', '#': '#', '@': '@', '%': '%',
|
|
17
45
|
};
|
|
18
46
|
const illegalIriChars = /[\x00-\x20<>\\"\{\}\|\^\`]/;
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
const
|
|
47
|
+
|
|
48
|
+
// Character classes of names, as bit flags for ASCII characters
|
|
49
|
+
const PREFIX_START = 1, // PN_CHARS_BASE
|
|
50
|
+
LOCAL_START = 2, // PN_CHARS_U, digits, and colon
|
|
51
|
+
NAME_CHAR = 4, // PN_CHARS
|
|
52
|
+
LOCAL_CHAR = 8, // PN_CHARS and colon
|
|
53
|
+
LOCAL_ESCAPE = 16; // characters that can be escaped in local names (PN_LOCAL_ESC)
|
|
54
|
+
const asciiNameClasses = new Uint8Array(0x80);
|
|
55
|
+
for (let charCode = 0; charCode < 0x80; charCode++) {
|
|
56
|
+
const char = String.fromCharCode(charCode);
|
|
57
|
+
const letter = char >= 'A' && char <= 'Z' || char >= 'a' && char <= 'z';
|
|
58
|
+
const nameChar = letter || char === '_' || char === '-' || char >= '0' && char <= '9';
|
|
59
|
+
asciiNameClasses[charCode] =
|
|
60
|
+
(letter ? PREFIX_START : 0) |
|
|
61
|
+
(nameChar && char !== '-' || char === ':' ? LOCAL_START : 0) |
|
|
62
|
+
(nameChar ? NAME_CHAR : 0) |
|
|
63
|
+
(nameChar || char === ':' ? LOCAL_CHAR : 0) |
|
|
64
|
+
(char in localNameEscapeReplacements ? LOCAL_ESCAPE : 0);
|
|
65
|
+
}
|
|
66
|
+
// Returns the length (0, 1, or 2 code units) of the character at the given position
|
|
67
|
+
// if it is in the given name character class
|
|
68
|
+
function nameCharLength(input, pos, charClass) {
|
|
69
|
+
const charCode = input.charCodeAt(pos);
|
|
70
|
+
if (charCode < 0x80)
|
|
71
|
+
return (asciiNameClasses[charCode] & charClass) !== 0 ? 1 : 0;
|
|
72
|
+
// Characters from U+10000 to U+EFFFF consist of a surrogate pair
|
|
73
|
+
if (charCode >= 0xD800 && charCode <= 0xDB7F) {
|
|
74
|
+
const low = input.charCodeAt(pos + 1);
|
|
75
|
+
return low >= 0xDC00 && low <= 0xDFFF ? 2 : 0;
|
|
76
|
+
}
|
|
77
|
+
// PN_CHARS has some characters that PN_CHARS_BASE does not
|
|
78
|
+
if (charClass >= NAME_CHAR && (charCode === 0xB7 || charCode >= 0x300 && charCode <= 0x36F ||
|
|
79
|
+
charCode === 0x203F || charCode === 0x2040))
|
|
80
|
+
return 1;
|
|
81
|
+
// PN_CHARS_BASE
|
|
82
|
+
return charCode >= 0xC0 && charCode <= 0x1FFF && charCode !== 0xD7 && charCode !== 0xF7 &&
|
|
83
|
+
(charCode < 0x300 || charCode >= 0x370 && charCode !== 0x37E) ||
|
|
84
|
+
charCode >= 0x200C && charCode <= 0x200D || charCode >= 0x2070 && charCode <= 0x218F ||
|
|
85
|
+
charCode >= 0x2C00 && charCode <= 0x2FEF || charCode >= 0x3001 && charCode <= 0xD7FF ||
|
|
86
|
+
charCode >= 0xF900 && charCode <= 0xFDCF || charCode >= 0xFDF0 && charCode <= 0xFFFD ? 1 : 0;
|
|
87
|
+
}
|
|
88
|
+
function isLocalEscape(charCode) {
|
|
89
|
+
return charCode < 0x80 && (asciiNameClasses[charCode] & LOCAL_ESCAPE) !== 0;
|
|
90
|
+
}
|
|
91
|
+
function isHexDigit(charCode) {
|
|
92
|
+
return charCode >= ZERO && charCode <= NINE ||
|
|
93
|
+
charCode >= 0x41 && charCode <= 0x46 || charCode >= 0x61 && charCode <= 0x66;
|
|
94
|
+
}
|
|
95
|
+
// Returns the end of the prefix (PN_PREFIX) at the given position,
|
|
96
|
+
// which can contain single dots, but not start or end with one
|
|
97
|
+
function skipPrefix(input, pos) {
|
|
98
|
+
let length = nameCharLength(input, pos, PREFIX_START);
|
|
99
|
+
while (length !== 0) {
|
|
100
|
+
pos += length;
|
|
101
|
+
const next = input.charCodeAt(pos) === DOT ? pos + 1 : pos;
|
|
102
|
+
// Most names are ASCII, so look those up without a call
|
|
103
|
+
const charCode = input.charCodeAt(next);
|
|
104
|
+
length = charCode < 0x80 ? (asciiNameClasses[charCode] & NAME_CHAR) !== 0 ? 1 : 0 :
|
|
105
|
+
nameCharLength(input, next, NAME_CHAR);
|
|
106
|
+
if (length !== 0)
|
|
107
|
+
pos = next;
|
|
108
|
+
}
|
|
109
|
+
return pos;
|
|
110
|
+
}
|
|
111
|
+
// Returns the end of the local name (PN_LOCAL) at the given position,
|
|
112
|
+
// which can contain dots, but not start or end with one
|
|
113
|
+
function skipLocalName(input, pos) {
|
|
114
|
+
let end = pos, charClass = LOCAL_START;
|
|
115
|
+
while (true) {
|
|
116
|
+
// Most names are ASCII, so look those up without a call
|
|
117
|
+
const charCode = input.charCodeAt(pos);
|
|
118
|
+
let length = charCode < 0x80 ? (asciiNameClasses[charCode] & charClass) !== 0 ? 1 : 0 :
|
|
119
|
+
nameCharLength(input, pos, charClass);
|
|
120
|
+
// Percent-encoded character (PERCENT)
|
|
121
|
+
if (length === 0 && charCode === PERCENT &&
|
|
122
|
+
isHexDigit(input.charCodeAt(pos + 1)) && isHexDigit(input.charCodeAt(pos + 2)))
|
|
123
|
+
length = 3;
|
|
124
|
+
// Escaped character (PN_LOCAL_ESC)
|
|
125
|
+
else if (length === 0 && charCode === BACKSLASH && isLocalEscape(input.charCodeAt(pos + 1)))
|
|
126
|
+
length = 2;
|
|
127
|
+
if (length !== 0) {
|
|
128
|
+
end = pos += length;
|
|
129
|
+
charClass = LOCAL_CHAR;
|
|
130
|
+
}
|
|
131
|
+
// Dots are allowed after the first character, but not at the end
|
|
132
|
+
else if (charCode === DOT && charClass === LOCAL_CHAR)
|
|
133
|
+
pos++;
|
|
134
|
+
else
|
|
135
|
+
return end;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Returns the end of the prefixed name at the given position, or -1 if there is none
|
|
139
|
+
function skipPrefixedName(input, pos, inputFinished) {
|
|
140
|
+
const colon = skipPrefix(input, pos);
|
|
141
|
+
if (input.charCodeAt(colon) !== COLON)
|
|
142
|
+
return -1;
|
|
143
|
+
const end = skipLocalName(input, colon + 1);
|
|
144
|
+
return canEndName(input, end, inputFinished) ? end : -1;
|
|
145
|
+
}
|
|
22
146
|
|
|
23
147
|
// A valid code point is a Unicode scalar value: at most U+10FFFF and not a surrogate
|
|
24
148
|
function isValidCodePoint(charCode) {
|
|
@@ -75,8 +199,6 @@ export default class N3Lexer {
|
|
|
75
199
|
this._simpleQuotedString = /"([^"\\\r\n]*)"(?=[^"])/y; // string without escape sequences
|
|
76
200
|
this._simpleApostropheString = /'([^'\\\r\n]*)'(?=[^'])/y;
|
|
77
201
|
this._langcode = /@([a-z]+(?:-[a-z0-9]+)*)(?=[^a-z0-9])/iy;
|
|
78
|
-
this._prefix = /((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:(?=[#\s<])/y;
|
|
79
|
-
this._prefixed = /((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:((?:(?:[0-9:A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])(?:(?:[\.\-0-9:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])*(?:[\-0-9:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~]))?)?)(?:[ \t]+|(?=\.?[,;!\^\s#()\[\]\{\}"'<>]))/y;
|
|
80
202
|
this._variable = /\?(?:(?:[A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:[\-0-9:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
|
|
81
203
|
this._blank = /_:((?:[0-9A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?:[ \t]+|(?=\.?[,;:!\^\s#()\[\]\{\}"'<>]))/y;
|
|
82
204
|
this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
|
|
@@ -501,9 +623,14 @@ export default class N3Lexer {
|
|
|
501
623
|
// Some first characters do not allow an immediate decision, so inspect more
|
|
502
624
|
if (inconclusive) {
|
|
503
625
|
// Try to find a prefix
|
|
626
|
+
let end;
|
|
504
627
|
if ((this._previousMarker === '@prefix' || this._previousMarker === 'PREFIX') &&
|
|
505
|
-
(
|
|
506
|
-
|
|
628
|
+
input.charCodeAt(end = skipPrefix(input, pos)) === COLON &&
|
|
629
|
+
end + 1 < input.length && (input[end + 1] === '#' || input[end + 1] === '<' ||
|
|
630
|
+
isWhitespace(input.charCodeAt(end + 1)))) {
|
|
631
|
+
type = 'prefix', value = input.slice(pos, end);
|
|
632
|
+
matchLength = end + 1 - pos;
|
|
633
|
+
}
|
|
507
634
|
// Try to find an additional directive keyword
|
|
508
635
|
// (at the end of the input, only a short final word can be one)
|
|
509
636
|
else if (this._directive !== null && this._directiveStarts.includes(firstChar) &&
|
|
@@ -512,13 +639,13 @@ export default class N3Lexer {
|
|
|
512
639
|
(match = execAtEnd(this._directive, input, pos))))
|
|
513
640
|
type = match[0].toUpperCase();
|
|
514
641
|
// Try to find a prefixed name. Since it can contain (but not end with) a dot,
|
|
515
|
-
// we always need a non-dot character before deciding it is a prefixed name
|
|
516
|
-
//
|
|
517
|
-
else if ((
|
|
518
|
-
|
|
519
|
-
type = 'prefixed', prefix =
|
|
520
|
-
value = this._unescape(
|
|
521
|
-
|
|
642
|
+
// we always need a non-dot character before deciding it is a prefixed name,
|
|
643
|
+
// except at the end of the input.
|
|
644
|
+
else if (!this._lineMode && (end = skipPrefixedName(input, pos, inputFinished)) >= 0) {
|
|
645
|
+
const colon = input.indexOf(':', pos);
|
|
646
|
+
type = 'prefixed', prefix = input.slice(pos, colon);
|
|
647
|
+
value = this._unescape(input.slice(colon + 1, end), localNameEscapeReplacements);
|
|
648
|
+
matchLength = end - pos;
|
|
522
649
|
}
|
|
523
650
|
}
|
|
524
651
|
|
|
@@ -605,30 +732,21 @@ export default class N3Lexer {
|
|
|
605
732
|
return null;
|
|
606
733
|
|
|
607
734
|
// Most verb boundaries cannot be part of a prefix, so keep the common path fast.
|
|
735
|
+
// U+1680 and U+FEFF are whitespace to the regular expression but name characters.
|
|
608
736
|
const next = input[pos + verb[0].length];
|
|
609
|
-
if (next !== '-' && next !== '_' && (next < '0' || next > '9')
|
|
737
|
+
if (next !== '-' && next !== '_' && (next < '0' || next > '9') &&
|
|
738
|
+
next !== '\u1680' && next !== '\ufeff')
|
|
610
739
|
return verb;
|
|
611
740
|
|
|
612
741
|
// A prefix can start with a verb and continue with characters that are also
|
|
613
742
|
// valid verb boundaries. Prefer the longer prefixed name when it is complete.
|
|
614
|
-
if (
|
|
615
|
-
return null;
|
|
616
|
-
// Appending to the input only matters when a prefixed name could run up to
|
|
617
|
-
// its end, which a character that cannot occur in prefixed names rules out.
|
|
618
|
-
// This avoids copying the rest of the document for every such verb.
|
|
619
|
-
if (testAt(nonPrefixedNameChar, input, pos))
|
|
620
|
-
return verb;
|
|
621
|
-
if (execAtEnd(this._prefixed, input, pos))
|
|
743
|
+
if (skipPrefixedName(input, pos, true) >= 0)
|
|
622
744
|
return null;
|
|
623
745
|
|
|
624
|
-
// If a stream chunk ends partway through such a prefix,
|
|
625
|
-
// instead of prematurely emitting the verb.
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const prefix = execAt(this._prefix, `${input.slice(pos)}: `, 0);
|
|
629
|
-
if (prefix)
|
|
630
|
-
return null;
|
|
631
|
-
}
|
|
746
|
+
// If a stream chunk ends partway through such a prefix,
|
|
747
|
+
// wait for the colon instead of prematurely emitting the verb.
|
|
748
|
+
if (!inputFinished && skipPrefix(input, pos) === input.length)
|
|
749
|
+
return null;
|
|
632
750
|
return verb;
|
|
633
751
|
}
|
|
634
752
|
|