n3 2.13.4 → 2.13.6
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 +11 -10
- package/browser/n3.min.js +10 -9
- package/lib/N3Lexer.js +127 -28
- package/lib/N3Reasoner.js +5 -4
- package/lib/N3Store.js +128 -133
- package/package.json +7 -2
- package/src/N3Lexer.js +150 -32
- package/src/N3Reasoner.js +6 -4
- package/src/N3Store.js +122 -143
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
|
|
package/src/N3Reasoner.js
CHANGED
|
@@ -34,6 +34,7 @@ export default class N3Reasoner {
|
|
|
34
34
|
if (!store._addToIndex(graphItem.subjects, subject, predicate, object)) return;
|
|
35
35
|
store._addToIndex(graphItem.predicates, predicate, object, subject);
|
|
36
36
|
store._addToIndex(graphItem.objects, object, subject, predicate);
|
|
37
|
+
this._indexed++;
|
|
37
38
|
}
|
|
38
39
|
// Count genuinely new derivations and fail past the budget. The check comes
|
|
39
40
|
// after all three indexes are updated, so a caught error leaves the store
|
|
@@ -175,7 +176,7 @@ export default class N3Reasoner {
|
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
reason(rules) {
|
|
178
|
-
this._derivations = 0;
|
|
179
|
+
this._derivations = this._indexed = 0;
|
|
179
180
|
if (!Array.isArray(rules)) {
|
|
180
181
|
rules = getRulesFromDataset(rules);
|
|
181
182
|
}
|
|
@@ -242,9 +243,10 @@ export default class N3Reasoner {
|
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
finally {
|
|
245
|
-
//
|
|
246
|
-
// so a caught budget error leaves the store
|
|
247
|
-
this._store._size
|
|
246
|
+
// Count the quads added directly to the indexes, even if a derivation
|
|
247
|
+
// budget was exceeded, so a caught budget error leaves the store consistent
|
|
248
|
+
if (this._store._size !== null)
|
|
249
|
+
this._store._size += this._indexed;
|
|
248
250
|
}
|
|
249
251
|
}
|
|
250
252
|
}
|
package/src/N3Store.js
CHANGED
|
@@ -23,101 +23,6 @@ function hasInIndex(index0, key0, key1, key2) {
|
|
|
23
23
|
return !!index2 && key2 in index2;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function merge(target, source, depth = 4) {
|
|
27
|
-
let size = target[SIZE] || 0;
|
|
28
|
-
for (const key in source) {
|
|
29
|
-
if (!(key in target)) {
|
|
30
|
-
size++;
|
|
31
|
-
target[key] = depth === 0 ? null : merge(Object.create(null), source[key], depth - 1);
|
|
32
|
-
}
|
|
33
|
-
// Merge into the existing object in place,
|
|
34
|
-
// as graph objects are frozen and cannot be reassigned
|
|
35
|
-
else if (depth !== 0)
|
|
36
|
-
merge(target[key], source[key], depth - 1);
|
|
37
|
-
}
|
|
38
|
-
// Depth 2 is the level of the `subjects`, `predicates`, and `objects` indexes.
|
|
39
|
-
if (depth <= 2)
|
|
40
|
-
target[SIZE] = size;
|
|
41
|
-
|
|
42
|
-
return target;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Determines the intersection of the `_graphs` index s1 and s2.
|
|
47
|
-
* s1 and s2 *must* belong to Stores that share an `_entityIndex`.
|
|
48
|
-
*
|
|
49
|
-
* False is returned when there is no intersection; this should
|
|
50
|
-
* *not* be set as the value for an index.
|
|
51
|
-
*/
|
|
52
|
-
function intersect(s1, s2, depth = 4) {
|
|
53
|
-
let target = false, size = 0;
|
|
54
|
-
|
|
55
|
-
if (depth <= 2 && s2[SIZE] < s1[SIZE])
|
|
56
|
-
[s1, s2] = [s2, s1];
|
|
57
|
-
|
|
58
|
-
for (const key in s1) {
|
|
59
|
-
if (key in s2) {
|
|
60
|
-
const intersection = depth === 0 ? null : intersect(s1[key], s2[key], depth - 1);
|
|
61
|
-
if (intersection !== false) {
|
|
62
|
-
target = target || Object.create(null);
|
|
63
|
-
target[key] = intersection;
|
|
64
|
-
size++;
|
|
65
|
-
}
|
|
66
|
-
// Depth 3 is the 'subjects', 'predicates' and 'objects' keys.
|
|
67
|
-
// If the 'subjects' index is empty, so will the 'predicates' and 'objects' index.
|
|
68
|
-
else if (depth === 3) {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Depth 2 is the level of the `subjects`, `predicates`, and `objects` indexes.
|
|
75
|
-
if (depth <= 2 && target)
|
|
76
|
-
target[SIZE] = size;
|
|
77
|
-
|
|
78
|
-
return target;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Determines the difference of the `_graphs` index s1 and s2.
|
|
83
|
-
* s1 and s2 *must* belong to Stores that share an `_entityIndex`.
|
|
84
|
-
*
|
|
85
|
-
* False is returned when there is no difference; this should
|
|
86
|
-
* *not* be set as the value for an index.
|
|
87
|
-
*/
|
|
88
|
-
function difference(s1, s2, depth = 4) {
|
|
89
|
-
let target = false, size = 0;
|
|
90
|
-
|
|
91
|
-
for (const key in s1) {
|
|
92
|
-
// When the key is not in the index, then none of the triples defined by s1[key] are
|
|
93
|
-
// in s2 and so we want to copy them over to the resultant store.
|
|
94
|
-
if (!(key in s2)) {
|
|
95
|
-
target = target || Object.create(null);
|
|
96
|
-
target[key] = depth === 0 ? null : merge({}, s1[key], depth - 1);
|
|
97
|
-
size++;
|
|
98
|
-
}
|
|
99
|
-
else if (depth !== 0) {
|
|
100
|
-
const diff = difference(s1[key], s2[key], depth - 1);
|
|
101
|
-
if (diff !== false) {
|
|
102
|
-
target = target || Object.create(null);
|
|
103
|
-
target[key] = diff;
|
|
104
|
-
size++;
|
|
105
|
-
}
|
|
106
|
-
// Depth 3 is the 'subjects', 'predicates' and 'objects' keys.
|
|
107
|
-
// If the 'subjects' index is empty, so will the 'predicates' and 'objects' index.
|
|
108
|
-
else if (depth === 3) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Depth 2 is the level of the `subjects`, `predicates`, and `objects` indexes.
|
|
115
|
-
if (depth <= 2 && target)
|
|
116
|
-
target[SIZE] = size;
|
|
117
|
-
|
|
118
|
-
return target;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
26
|
// Returns the key of a term in the entity index.
|
|
122
27
|
// Keys mark the term type by their first character, so the IRI of a named node
|
|
123
28
|
// that starts with such a marker (as relative IRIs can) is wrapped in < and >.
|
|
@@ -242,6 +147,12 @@ export class N3EntityIndex {
|
|
|
242
147
|
}
|
|
243
148
|
}
|
|
244
149
|
|
|
150
|
+
// Returns the size of a store from this copy of N3 if it is known without counting,
|
|
151
|
+
// or null otherwise
|
|
152
|
+
function knownSize(dataset) {
|
|
153
|
+
return dataset instanceof N3Store ? dataset._size : null;
|
|
154
|
+
}
|
|
155
|
+
|
|
245
156
|
// ## Constructor
|
|
246
157
|
export default class N3Store {
|
|
247
158
|
constructor(quads, options) {
|
|
@@ -249,6 +160,7 @@ export default class N3Store {
|
|
|
249
160
|
this._size = 0;
|
|
250
161
|
// `_graphs` contains subject, predicate, and object indexes per graph
|
|
251
162
|
this._graphs = Object.create(null);
|
|
163
|
+
this._graphCount = 0;
|
|
252
164
|
// `_observers` contains weak references to views notified before every mutation
|
|
253
165
|
this._observers = null;
|
|
254
166
|
|
|
@@ -273,20 +185,11 @@ export default class N3Store {
|
|
|
273
185
|
|
|
274
186
|
// ### `size` returns the number of quads in the store
|
|
275
187
|
get size() {
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// Calculate the number of quads by counting to the deepest level
|
|
282
|
-
size = 0;
|
|
283
|
-
const graphs = this._graphs;
|
|
284
|
-
let subjects, subject;
|
|
285
|
-
for (const graphKey in graphs)
|
|
286
|
-
for (const subjectKey in (subjects = graphs[graphKey].subjects))
|
|
287
|
-
for (const predicateKey in (subject = subjects[subjectKey]))
|
|
288
|
-
size += subject[predicateKey][SIZE];
|
|
289
|
-
return this._size = size;
|
|
188
|
+
// The quad count is kept up to date by every change,
|
|
189
|
+
// except by reasoners from earlier versions of N3, which set it to null
|
|
190
|
+
if (this._size === null)
|
|
191
|
+
this._size = this.countQuads();
|
|
192
|
+
return this._size;
|
|
290
193
|
}
|
|
291
194
|
|
|
292
195
|
// ## Private methods
|
|
@@ -554,6 +457,7 @@ export default class N3Store {
|
|
|
554
457
|
// Freezing a graph helps subsequent `add` performance,
|
|
555
458
|
// and properties will never be modified anyway
|
|
556
459
|
Object.freeze(graphItem);
|
|
460
|
+
this._graphCount++;
|
|
557
461
|
}
|
|
558
462
|
|
|
559
463
|
// Notify observers before inserting a new quad so snapshots retain their prior contents
|
|
@@ -568,11 +472,67 @@ export default class N3Store {
|
|
|
568
472
|
this._addToIndex(graphItem.predicates, predicate, object, subject);
|
|
569
473
|
this._addToIndex(graphItem.objects, object, subject, predicate);
|
|
570
474
|
|
|
571
|
-
|
|
572
|
-
this._size = null;
|
|
475
|
+
if (this._size !== null) this._size++;
|
|
573
476
|
return true;
|
|
574
477
|
}
|
|
575
478
|
|
|
479
|
+
// ### `_addFromIndex` adds the quads in the given graph indexes of a store
|
|
480
|
+
// that shares this store's entity index, so no terms need to be converted.
|
|
481
|
+
// With `otherGraphs`, it only adds the quads that are not in those indexes.
|
|
482
|
+
// Index keys are strings, so the ids are converted back to the numbers that
|
|
483
|
+
// `addQuad` uses: forwarded views compare the ids they are notified with to
|
|
484
|
+
// their own numeric ids.
|
|
485
|
+
_addFromIndex(graphs, otherGraphs = null) {
|
|
486
|
+
for (const graphKey in graphs) {
|
|
487
|
+
const subjects = graphs[graphKey].subjects, other = otherGraphs && otherGraphs[graphKey];
|
|
488
|
+
const otherSubjects = other ? other.subjects : null;
|
|
489
|
+
for (const subjectKey in subjects) {
|
|
490
|
+
const subject = Number(subjectKey), predicates = subjects[subjectKey];
|
|
491
|
+
const otherPredicates = otherSubjects && otherSubjects[subjectKey];
|
|
492
|
+
for (const predicateKey in predicates) {
|
|
493
|
+
const predicate = Number(predicateKey), objects = predicates[predicateKey];
|
|
494
|
+
const otherObjects = otherPredicates && otherPredicates[predicateKey];
|
|
495
|
+
for (const objectKey in objects) {
|
|
496
|
+
if (!otherObjects || !(objectKey in otherObjects))
|
|
497
|
+
this._addQuad(subject, predicate, Number(objectKey), Number(graphKey));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ### `_addIntersectionFromIndex` adds the quads that are in both of the
|
|
505
|
+
// given graph indexes, walking the graphs of the first and, below them,
|
|
506
|
+
// whichever index has fewer keys
|
|
507
|
+
_addIntersectionFromIndex(graphs, otherGraphs) {
|
|
508
|
+
for (const graphKey in graphs) {
|
|
509
|
+
const other = otherGraphs[graphKey];
|
|
510
|
+
if (other) {
|
|
511
|
+
let subjects = graphs[graphKey].subjects, otherSubjects = other.subjects;
|
|
512
|
+
if (otherSubjects[SIZE] < subjects[SIZE])
|
|
513
|
+
[subjects, otherSubjects] = [otherSubjects, subjects];
|
|
514
|
+
for (const subjectKey in subjects) {
|
|
515
|
+
let predicates = subjects[subjectKey], otherPredicates = otherSubjects[subjectKey];
|
|
516
|
+
if (otherPredicates) {
|
|
517
|
+
if (otherPredicates[SIZE] < predicates[SIZE])
|
|
518
|
+
[predicates, otherPredicates] = [otherPredicates, predicates];
|
|
519
|
+
for (const predicateKey in predicates) {
|
|
520
|
+
let objects = predicates[predicateKey], otherObjects = otherPredicates[predicateKey];
|
|
521
|
+
if (otherObjects) {
|
|
522
|
+
if (otherObjects[SIZE] < objects[SIZE])
|
|
523
|
+
[objects, otherObjects] = [otherObjects, objects];
|
|
524
|
+
for (const objectKey in objects) {
|
|
525
|
+
if (objectKey in otherObjects)
|
|
526
|
+
this._addQuad(Number(subjectKey), Number(predicateKey), Number(objectKey), Number(graphKey));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
576
536
|
// ### `addQuads` adds multiple quads to the store
|
|
577
537
|
addQuads(quads) {
|
|
578
538
|
for (let i = 0; i < quads.length; i++)
|
|
@@ -643,8 +603,10 @@ export default class N3Store {
|
|
|
643
603
|
if (this._size !== null) this._size--;
|
|
644
604
|
|
|
645
605
|
// Remove the graph if it is empty
|
|
646
|
-
if (graphItem.subjects[SIZE] === 0)
|
|
606
|
+
if (graphItem.subjects[SIZE] === 0) {
|
|
647
607
|
delete graphs[graph];
|
|
608
|
+
this._graphCount--;
|
|
609
|
+
}
|
|
648
610
|
return true;
|
|
649
611
|
}
|
|
650
612
|
|
|
@@ -779,10 +741,14 @@ export default class N3Store {
|
|
|
779
741
|
// If only predicate and possibly object are given, the predicate index will be the fastest
|
|
780
742
|
count += this._countInIndex(content.predicates, predicateId, objectId, subjectId);
|
|
781
743
|
}
|
|
782
|
-
else {
|
|
783
|
-
// If only object is
|
|
744
|
+
else if (object) {
|
|
745
|
+
// If only object is given, the object index will be the fastest
|
|
784
746
|
count += this._countInIndex(content.objects, objectId, subjectId, predicateId);
|
|
785
747
|
}
|
|
748
|
+
else {
|
|
749
|
+
// Without a pattern, the subject index sums the fewest leaf counts
|
|
750
|
+
count += this._countInIndex(content.subjects);
|
|
751
|
+
}
|
|
786
752
|
}
|
|
787
753
|
}
|
|
788
754
|
return count;
|
|
@@ -1100,13 +1066,9 @@ export default class N3Store {
|
|
|
1100
1066
|
|
|
1101
1067
|
if (Array.isArray(quads))
|
|
1102
1068
|
this.addQuads(quads);
|
|
1103
|
-
//
|
|
1104
|
-
else if (
|
|
1105
|
-
|
|
1106
|
-
this._graphs = merge(this._graphs, quads._graphs);
|
|
1107
|
-
this._size = null; // Invalidate the cached size
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1069
|
+
// A store with the same entity index can be copied by identifier
|
|
1070
|
+
else if (quads instanceof N3Store && quads._entityIndex === this._entityIndex)
|
|
1071
|
+
this._addFromIndex(quads._graphs);
|
|
1110
1072
|
else {
|
|
1111
1073
|
for (const quad of quads)
|
|
1112
1074
|
this.add(quad);
|
|
@@ -1127,19 +1089,26 @@ export default class N3Store {
|
|
|
1127
1089
|
if (other === this)
|
|
1128
1090
|
return true;
|
|
1129
1091
|
|
|
1130
|
-
if (!(other instanceof N3Store) || this._entityIndex !== other._entityIndex)
|
|
1092
|
+
if (!(other instanceof N3Store) || this._entityIndex !== other._entityIndex) {
|
|
1093
|
+
// A larger set cannot be a subset, but only compare sizes that are known without counting
|
|
1094
|
+
const otherSize = knownSize(other), thisSize = this._size;
|
|
1095
|
+
if (otherSize !== null && thisSize !== null && otherSize > thisSize)
|
|
1096
|
+
return false;
|
|
1131
1097
|
return other.every(quad => this.has(quad));
|
|
1098
|
+
}
|
|
1132
1099
|
|
|
1100
|
+
// At every level, the index of the subset cannot have more entries
|
|
1133
1101
|
const g1 = this._graphs, g2 = other._graphs;
|
|
1134
|
-
let s1, s2, p1, p2, o1;
|
|
1102
|
+
let s1, s2, p1, p2, o1, o2;
|
|
1135
1103
|
for (const graph in g2) {
|
|
1136
1104
|
if (!(s1 = g1[graph])) return false;
|
|
1137
1105
|
s1 = s1.subjects;
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1106
|
+
if ((s2 = g2[graph].subjects)[SIZE] > s1[SIZE]) return false;
|
|
1107
|
+
for (const subject in s2) {
|
|
1108
|
+
if (!(p1 = s1[subject]) || (p2 = s2[subject])[SIZE] > p1[SIZE]) return false;
|
|
1109
|
+
for (const predicate in p2) {
|
|
1110
|
+
if (!(o1 = p1[predicate]) || (o2 = p2[predicate])[SIZE] > o1[SIZE]) return false;
|
|
1111
|
+
for (const object in o2)
|
|
1143
1112
|
if (!(object in o1)) return false;
|
|
1144
1113
|
}
|
|
1145
1114
|
}
|
|
@@ -1176,11 +1145,7 @@ export default class N3Store {
|
|
|
1176
1145
|
|
|
1177
1146
|
if ((other instanceof N3Store) && other._entityIndex === this._entityIndex) {
|
|
1178
1147
|
const store = new N3Store({ entityIndex: this._entityIndex });
|
|
1179
|
-
|
|
1180
|
-
if (graphs) {
|
|
1181
|
-
store._graphs = graphs;
|
|
1182
|
-
store._size = null;
|
|
1183
|
-
}
|
|
1148
|
+
store._addFromIndex(this._graphs, other._graphs);
|
|
1184
1149
|
return store;
|
|
1185
1150
|
}
|
|
1186
1151
|
|
|
@@ -1221,20 +1186,34 @@ export default class N3Store {
|
|
|
1221
1186
|
|
|
1222
1187
|
if (other === this) {
|
|
1223
1188
|
const store = new N3Store({ entityIndex: this._entityIndex });
|
|
1224
|
-
store.
|
|
1225
|
-
store._size = this._size;
|
|
1189
|
+
store._addFromIndex(this._graphs);
|
|
1226
1190
|
return store;
|
|
1227
1191
|
}
|
|
1228
1192
|
else if ((other instanceof N3Store) && this._entityIndex === other._entityIndex) {
|
|
1229
1193
|
const store = new N3Store({ entityIndex: this._entityIndex });
|
|
1230
|
-
|
|
1231
|
-
if (
|
|
1232
|
-
|
|
1233
|
-
|
|
1194
|
+
// Starting a loop over no graphs costs more than checking for an empty store
|
|
1195
|
+
if (this._size !== 0 && other._size !== 0) {
|
|
1196
|
+
if (other._graphCount < this._graphCount)
|
|
1197
|
+
store._addIntersectionFromIndex(other._graphs, this._graphs);
|
|
1198
|
+
else
|
|
1199
|
+
store._addIntersectionFromIndex(this._graphs, other._graphs);
|
|
1234
1200
|
}
|
|
1235
1201
|
return store;
|
|
1236
1202
|
}
|
|
1237
1203
|
|
|
1204
|
+
// Test the quads of the smaller dataset against the larger one
|
|
1205
|
+
// when both sizes are known without counting
|
|
1206
|
+
const otherSize = knownSize(other), thisSize = this._size;
|
|
1207
|
+
if (otherSize !== null && thisSize !== null && otherSize < thisSize && typeof other[Symbol.iterator] === 'function' &&
|
|
1208
|
+
// unless it is a store whose custom factories may not create RDF/JS quads,
|
|
1209
|
+
// either for its quads or, through its entity index, for their terms
|
|
1210
|
+
other._factory === N3DataFactory && other._entityIndex._factory === N3DataFactory) {
|
|
1211
|
+
const store = new N3Store({ entityIndex: this._entityIndex });
|
|
1212
|
+
for (const quad of other)
|
|
1213
|
+
if (this.has(quad))
|
|
1214
|
+
store.add(quad);
|
|
1215
|
+
return store;
|
|
1216
|
+
}
|
|
1238
1217
|
return this.filter(quad => other.has(quad));
|
|
1239
1218
|
}
|
|
1240
1219
|
|
|
@@ -1305,9 +1284,7 @@ export default class N3Store {
|
|
|
1305
1284
|
*/
|
|
1306
1285
|
union(quads) {
|
|
1307
1286
|
const store = new N3Store({ entityIndex: this._entityIndex });
|
|
1308
|
-
store.
|
|
1309
|
-
store._size = this._size;
|
|
1310
|
-
|
|
1287
|
+
store._addFromIndex(this._graphs);
|
|
1311
1288
|
store.addAll(quads);
|
|
1312
1289
|
return store;
|
|
1313
1290
|
}
|
|
@@ -1572,8 +1549,10 @@ class DatasetCoreAndReadableStream extends Readable {
|
|
|
1572
1549
|
objects = indexMatch(content.objects, [objectId, subjectId, predicateId]);
|
|
1573
1550
|
}
|
|
1574
1551
|
|
|
1575
|
-
if (subjects)
|
|
1552
|
+
if (subjects) {
|
|
1576
1553
|
newStore._graphs[graphKey] = { subjects, predicates, objects };
|
|
1554
|
+
newStore._graphCount++;
|
|
1555
|
+
}
|
|
1577
1556
|
}
|
|
1578
1557
|
}
|
|
1579
1558
|
newStore._size = null;
|