n3 0.4.2 → 0.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/.eslintrc +173 -0
- package/.travis.yml +3 -6
- package/README.md +43 -8
- package/lib/N3Lexer.js +22 -45
- package/lib/N3Parser.js +120 -54
- package/lib/N3Store.js +97 -81
- package/lib/N3Util.js +33 -4
- package/lib/N3Writer.js +85 -26
- package/package.json +9 -9
- package/perf/.eslintrc +5 -0
- package/perf/N3Parser-perf.js +5 -4
- package/perf/N3Store-perf.js +66 -11
- package/spec/.eslintrc +10 -0
- package/spec/SpecTester.js +16 -17
- package/spec/trig/LICENSE +48 -0
- package/spec/turtle/LICENSE +117 -0
- package/test/.eslintrc +15 -0
- package/test/N3Lexer-test.js +4 -4
- package/test/N3Parser-test.js +480 -12
- package/test/N3Store-test.js +190 -71
- package/test/N3StreamParser-test.js +1 -1
- package/test/N3StreamWriter-test.js +1 -2
- package/test/N3Util-test.js +71 -5
- package/test/N3Writer-test.js +246 -5
- package/.jshintrc +0 -20
package/lib/N3Parser.js
CHANGED
|
@@ -6,9 +6,9 @@ var RDF_PREFIX = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
|
|
6
6
|
RDF_FIRST = RDF_PREFIX + 'first',
|
|
7
7
|
RDF_REST = RDF_PREFIX + 'rest';
|
|
8
8
|
|
|
9
|
-
var absoluteIRI =
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
var absoluteIRI = /^[a-z][a-z0-9+.-]*:/i,
|
|
10
|
+
schemeAuthority = /^(?:([a-z][a-z0-9+.-]*:))?(?:\/\/[^\/]*)?/i,
|
|
11
|
+
dotSegments = /(?:^|\/)\.\.?(?:$|[\/#?])/;
|
|
12
12
|
|
|
13
13
|
// The next ID for new blank nodes
|
|
14
14
|
var blankNodePrefix = 0, blankNodeCount = 0;
|
|
@@ -22,17 +22,7 @@ function N3Parser(options) {
|
|
|
22
22
|
|
|
23
23
|
// Set the document IRI.
|
|
24
24
|
options = options || {};
|
|
25
|
-
|
|
26
|
-
this._baseIRI = null;
|
|
27
|
-
this._baseIRIPath = null;
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
if (options.documentIRI.indexOf('#') > 0)
|
|
31
|
-
throw new Error('Invalid document IRI');
|
|
32
|
-
this._baseIRI = options.documentIRI;
|
|
33
|
-
this._baseIRIPath = this._baseIRI.replace(documentPart, '');
|
|
34
|
-
this._baseIRIRoot = this._baseIRI.match(rootIRI)[0];
|
|
35
|
-
}
|
|
25
|
+
this._setBase(options.documentIRI);
|
|
36
26
|
|
|
37
27
|
// Set supported features depending on the format.
|
|
38
28
|
var format = (typeof options.format === 'string') && options.format.match(/\w*$/)[0].toLowerCase(),
|
|
@@ -44,12 +34,14 @@ function N3Parser(options) {
|
|
|
44
34
|
this._supportsQuads = !(isTurtle || isTriG || isNTriples);
|
|
45
35
|
// Disable relative IRIs in N-Triples or N-Quads mode
|
|
46
36
|
if (isLineMode) {
|
|
47
|
-
this.
|
|
37
|
+
this._base = '';
|
|
48
38
|
this._resolveIRI = function (token) {
|
|
49
39
|
this._error('Disallowed relative IRI', token);
|
|
50
40
|
return this._callback = noop, this._subject = null;
|
|
51
41
|
};
|
|
52
42
|
}
|
|
43
|
+
this._blankNodePrefix = typeof options.blankNodePrefix !== 'string' ? '' :
|
|
44
|
+
'_:' + options.blankNodePrefix.replace(/^_:/, '');
|
|
53
45
|
this._lexer = options.lexer || new N3Lexer({ lineMode: isLineMode });
|
|
54
46
|
}
|
|
55
47
|
|
|
@@ -63,6 +55,21 @@ N3Parser._resetBlankNodeIds = function () {
|
|
|
63
55
|
N3Parser.prototype = {
|
|
64
56
|
// ## Private methods
|
|
65
57
|
|
|
58
|
+
// ### `_setBase` sets the base IRI to resolve relative IRIs.
|
|
59
|
+
_setBase: function (baseIRI) {
|
|
60
|
+
if (!baseIRI)
|
|
61
|
+
baseIRI = null;
|
|
62
|
+
else if (baseIRI.indexOf('#') >= 0)
|
|
63
|
+
throw new Error('Invalid base IRI ' + baseIRI);
|
|
64
|
+
// Set base IRI and its components
|
|
65
|
+
if (this._base = baseIRI) {
|
|
66
|
+
this._basePath = baseIRI.replace(/[^\/?]*(?:\?.*)?$/, '');
|
|
67
|
+
baseIRI = baseIRI.match(schemeAuthority);
|
|
68
|
+
this._baseRoot = baseIRI[0];
|
|
69
|
+
this._baseScheme = baseIRI[1];
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
66
73
|
// ### `_readInTopContext` reads a token when in the top context.
|
|
67
74
|
_readInTopContext: function (token) {
|
|
68
75
|
switch (token.type) {
|
|
@@ -94,9 +101,8 @@ N3Parser.prototype = {
|
|
|
94
101
|
return this._readSubject;
|
|
95
102
|
}
|
|
96
103
|
case 'GRAPH':
|
|
97
|
-
if (this._supportsNamedGraphs)
|
|
104
|
+
if (this._supportsNamedGraphs)
|
|
98
105
|
return this._readNamedGraphLabel;
|
|
99
|
-
}
|
|
100
106
|
// Otherwise, the next token must be a subject.
|
|
101
107
|
default:
|
|
102
108
|
return this._readSubject(token);
|
|
@@ -108,7 +114,7 @@ N3Parser.prototype = {
|
|
|
108
114
|
this._predicate = null;
|
|
109
115
|
switch (token.type) {
|
|
110
116
|
case 'IRI':
|
|
111
|
-
if (this.
|
|
117
|
+
if (this._base === null || absoluteIRI.test(token.value))
|
|
112
118
|
this._subject = token.value;
|
|
113
119
|
else
|
|
114
120
|
this._subject = this._resolveIRI(token);
|
|
@@ -145,21 +151,18 @@ N3Parser.prototype = {
|
|
|
145
151
|
switch (type) {
|
|
146
152
|
case 'IRI':
|
|
147
153
|
case 'abbreviation':
|
|
148
|
-
if (this.
|
|
154
|
+
if (this._base === null || absoluteIRI.test(token.value))
|
|
149
155
|
this._predicate = token.value;
|
|
150
156
|
else
|
|
151
157
|
this._predicate = this._resolveIRI(token);
|
|
152
158
|
break;
|
|
153
159
|
case 'prefixed':
|
|
154
|
-
if (token.prefix === '_')
|
|
160
|
+
if (token.prefix === '_')
|
|
155
161
|
return this._error('Disallowed blank node as predicate', token);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return this._error('Undefined prefix "' + token.prefix + ':"', token);
|
|
161
|
-
this._predicate = prefix + token.value;
|
|
162
|
-
}
|
|
162
|
+
var prefix = this._prefixes[token.prefix];
|
|
163
|
+
if (prefix === undefined)
|
|
164
|
+
return this._error('Undefined prefix "' + token.prefix + ':"', token);
|
|
165
|
+
this._predicate = prefix + token.value;
|
|
163
166
|
break;
|
|
164
167
|
case '.':
|
|
165
168
|
case ']':
|
|
@@ -183,7 +186,7 @@ N3Parser.prototype = {
|
|
|
183
186
|
_readObject: function (token) {
|
|
184
187
|
switch (token.type) {
|
|
185
188
|
case 'IRI':
|
|
186
|
-
if (this.
|
|
189
|
+
if (this._base === null || absoluteIRI.test(token.value))
|
|
187
190
|
this._object = token.value;
|
|
188
191
|
else
|
|
189
192
|
this._object = this._resolveIRI(token);
|
|
@@ -234,8 +237,10 @@ N3Parser.prototype = {
|
|
|
234
237
|
this._subject = null;
|
|
235
238
|
return this._readBlankNodeTail(token);
|
|
236
239
|
}
|
|
237
|
-
|
|
238
|
-
|
|
240
|
+
else {
|
|
241
|
+
this._predicate = null;
|
|
242
|
+
return this._readPredicate(token);
|
|
243
|
+
}
|
|
239
244
|
},
|
|
240
245
|
|
|
241
246
|
// ### `_readBlankNodeTail` reads the end of a blank node.
|
|
@@ -271,7 +276,7 @@ N3Parser.prototype = {
|
|
|
271
276
|
case 'type':
|
|
272
277
|
var value;
|
|
273
278
|
if (token.prefix === '') {
|
|
274
|
-
if (this.
|
|
279
|
+
if (this._base === null || absoluteIRI.test(token.value))
|
|
275
280
|
value = token.value;
|
|
276
281
|
else
|
|
277
282
|
value = this._resolveIRI(token);
|
|
@@ -303,7 +308,10 @@ N3Parser.prototype = {
|
|
|
303
308
|
|
|
304
309
|
switch (token.type) {
|
|
305
310
|
case 'IRI':
|
|
306
|
-
|
|
311
|
+
if (this._base === null || absoluteIRI.test(token.value))
|
|
312
|
+
item = token.value;
|
|
313
|
+
else
|
|
314
|
+
item = this._resolveIRI(token);
|
|
307
315
|
break;
|
|
308
316
|
case 'prefixed':
|
|
309
317
|
var prefix = this._prefixes[token.prefix];
|
|
@@ -420,7 +428,7 @@ N3Parser.prototype = {
|
|
|
420
428
|
// An IRI means this is a quad (only allowed if not already inside a graph).
|
|
421
429
|
case 'IRI':
|
|
422
430
|
if (this._supportsQuads && this._graph === null) {
|
|
423
|
-
if (this.
|
|
431
|
+
if (this._base === null || absoluteIRI.test(token.value))
|
|
424
432
|
graph = token.value;
|
|
425
433
|
else
|
|
426
434
|
graph = this._resolveIRI(token);
|
|
@@ -493,7 +501,7 @@ N3Parser.prototype = {
|
|
|
493
501
|
if (token.type !== 'IRI')
|
|
494
502
|
return this._error('Expected IRI to follow prefix "' + this._prefix + ':"', token);
|
|
495
503
|
var prefixIRI;
|
|
496
|
-
if (this.
|
|
504
|
+
if (this._base === null || absoluteIRI.test(token.value))
|
|
497
505
|
prefixIRI = token.value;
|
|
498
506
|
else
|
|
499
507
|
prefixIRI = this._resolveIRI(token);
|
|
@@ -506,14 +514,11 @@ N3Parser.prototype = {
|
|
|
506
514
|
_readBaseIRI: function (token) {
|
|
507
515
|
if (token.type !== 'IRI')
|
|
508
516
|
return this._error('Expected IRI to follow base declaration', token);
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
this._baseIRI = this._resolveIRI(token);
|
|
515
|
-
this._baseIRIPath = this._baseIRI.replace(documentPart, '');
|
|
516
|
-
this._baseIRIRoot = this._baseIRI.match(rootIRI)[0];
|
|
517
|
+
try {
|
|
518
|
+
this._setBase(this._base === null ||
|
|
519
|
+
absoluteIRI.test(token.value) ? token.value : this._resolveIRI(token));
|
|
520
|
+
}
|
|
521
|
+
catch (error) { this._error(error.message, token); }
|
|
517
522
|
return this._readDeclarationPunctuation;
|
|
518
523
|
},
|
|
519
524
|
|
|
@@ -568,28 +573,89 @@ N3Parser.prototype = {
|
|
|
568
573
|
this._callback(new Error(message + ' at line ' + token.line + '.'));
|
|
569
574
|
},
|
|
570
575
|
|
|
571
|
-
// ### `_resolveIRI` resolves
|
|
576
|
+
// ### `_resolveIRI` resolves a relative IRI token against the base path,
|
|
577
|
+
// assuming that a base path has been set and that the IRI is indeed relative.
|
|
572
578
|
_resolveIRI: function (token) {
|
|
573
579
|
var iri = token.value;
|
|
574
580
|
switch (iri[0]) {
|
|
575
581
|
// An empty relative IRI indicates the base IRI
|
|
576
|
-
case undefined:
|
|
577
|
-
return this._baseIRI;
|
|
582
|
+
case undefined: return this._base;
|
|
578
583
|
// Resolve relative fragment IRIs against the base IRI
|
|
579
|
-
case '#':
|
|
580
|
-
return this._baseIRI + iri;
|
|
584
|
+
case '#': return this._base + iri;
|
|
581
585
|
// Resolve relative query string IRIs by replacing the query string
|
|
582
|
-
case '?':
|
|
583
|
-
|
|
584
|
-
// Resolve root relative IRIs at the root of the base IRI
|
|
586
|
+
case '?': return this._base.replace(/(?:\?.*)?$/, iri);
|
|
587
|
+
// Resolve root-relative IRIs at the root of the base IRI
|
|
585
588
|
case '/':
|
|
586
|
-
|
|
589
|
+
// Resolve scheme-relative IRIs to the scheme
|
|
590
|
+
return (iri[1] === '/' ? this._baseScheme : this._baseRoot) + this._removeDotSegments(iri);
|
|
587
591
|
// Resolve all other IRIs at the base IRI's path
|
|
588
592
|
default:
|
|
589
|
-
return this.
|
|
593
|
+
return this._removeDotSegments(this._basePath + iri);
|
|
590
594
|
}
|
|
591
595
|
},
|
|
592
596
|
|
|
597
|
+
// ### `_removeDotSegments` resolves './' and '../' path segments in an IRI as per RFC3986.
|
|
598
|
+
_removeDotSegments: function (iri) {
|
|
599
|
+
// Don't modify the IRI if it does not contain any dot segments
|
|
600
|
+
if (!dotSegments.test(iri))
|
|
601
|
+
return iri;
|
|
602
|
+
|
|
603
|
+
// Start with an imaginary slash before the IRI in order to resolve trailing './' and '../'
|
|
604
|
+
var result = '', length = iri.length, i = -1, pathStart = -1, segmentStart = 0, next = '/';
|
|
605
|
+
|
|
606
|
+
while (i < length) {
|
|
607
|
+
switch (next) {
|
|
608
|
+
// The path starts with the first slash after the authority
|
|
609
|
+
case ':':
|
|
610
|
+
if (pathStart < 0) {
|
|
611
|
+
// Skip two slashes before the authority
|
|
612
|
+
if (iri[++i] === '/' && iri[++i] === '/')
|
|
613
|
+
// Skip to slash after the authority
|
|
614
|
+
while ((pathStart = i + 1) < length && iri[pathStart] !== '/')
|
|
615
|
+
i = pathStart;
|
|
616
|
+
}
|
|
617
|
+
break;
|
|
618
|
+
// Don't modify a query string or fragment
|
|
619
|
+
case '?':
|
|
620
|
+
case '#':
|
|
621
|
+
i = length;
|
|
622
|
+
break;
|
|
623
|
+
// Handle '/.' or '/..' path segments
|
|
624
|
+
case '/':
|
|
625
|
+
if (iri[i + 1] === '.') {
|
|
626
|
+
next = iri[++i + 1];
|
|
627
|
+
switch (next) {
|
|
628
|
+
// Remove a '/.' segment
|
|
629
|
+
case '/':
|
|
630
|
+
result += iri.substring(segmentStart, i - 1);
|
|
631
|
+
segmentStart = i + 1;
|
|
632
|
+
break;
|
|
633
|
+
// Remove a trailing '/.' segment
|
|
634
|
+
case undefined:
|
|
635
|
+
case '?':
|
|
636
|
+
case '#':
|
|
637
|
+
return result + iri.substring(segmentStart, i) + iri.substr(i + 1);
|
|
638
|
+
// Remove a '/..' segment
|
|
639
|
+
case '.':
|
|
640
|
+
next = iri[++i + 1];
|
|
641
|
+
if (next === undefined || next === '/' || next === '?' || next === '#') {
|
|
642
|
+
result += iri.substring(segmentStart, i - 2);
|
|
643
|
+
// Try to remove the parent path from result
|
|
644
|
+
if ((segmentStart = result.lastIndexOf('/')) >= pathStart)
|
|
645
|
+
result = result.substr(0, segmentStart);
|
|
646
|
+
// Remove a trailing '/..' segment
|
|
647
|
+
if (next !== '/')
|
|
648
|
+
return result + '/' + iri.substr(i + 1);
|
|
649
|
+
segmentStart = i + 1;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
next = iri[++i];
|
|
655
|
+
}
|
|
656
|
+
return result + iri.substring(segmentStart);
|
|
657
|
+
},
|
|
658
|
+
|
|
593
659
|
// ## Public methods
|
|
594
660
|
|
|
595
661
|
// ### `parse` parses the N3 input and emits each parsed triple through the callback.
|
|
@@ -598,7 +664,7 @@ N3Parser.prototype = {
|
|
|
598
664
|
// We start reading in the top context.
|
|
599
665
|
this._readCallback = this._readInTopContext;
|
|
600
666
|
this._prefixes = Object.create(null);
|
|
601
|
-
this._prefixes._ = '_:b' + blankNodePrefix++ + '_';
|
|
667
|
+
this._prefixes._ = this._blankNodePrefix || '_:b' + blankNodePrefix++ + '_';
|
|
602
668
|
|
|
603
669
|
// If the input argument is not given, shift parameters
|
|
604
670
|
if (typeof input === 'function')
|
|
@@ -622,7 +688,7 @@ N3Parser.prototype = {
|
|
|
622
688
|
this.addChunk = this._lexer.addChunk;
|
|
623
689
|
this.end = this._lexer.end;
|
|
624
690
|
}
|
|
625
|
-
}
|
|
691
|
+
},
|
|
626
692
|
};
|
|
627
693
|
|
|
628
694
|
// The empty function
|
package/lib/N3Store.js
CHANGED
|
@@ -11,22 +11,25 @@ function N3Store(triples, options) {
|
|
|
11
11
|
this._size = 0;
|
|
12
12
|
// `_graphs` contains subject, predicate, and object indexes per graph.
|
|
13
13
|
this._graphs = Object.create(null);
|
|
14
|
-
// `
|
|
15
|
-
//
|
|
16
|
-
this.
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
19
|
-
|
|
14
|
+
// `_ids` maps entities such as `http://xmlns.com/foaf/0.1/name` to numbers,
|
|
15
|
+
// saving memory by using only numbers as keys in `_graphs`.
|
|
16
|
+
this._id = 0;
|
|
17
|
+
this._ids = Object.create(null);
|
|
18
|
+
this._ids['><'] = 0; // dummy entry, so the first actual key is non-zero
|
|
19
|
+
this._entities = Object.create(null); // inverse of `_ids`
|
|
20
|
+
// `_blankNodeIndex` is the index of the last automatically named blank node
|
|
20
21
|
this._blankNodeIndex = 0;
|
|
21
22
|
|
|
22
23
|
// Shift parameters if `triples` is not given
|
|
23
24
|
if (!options && triples && !triples[0])
|
|
24
25
|
options = triples, triples = null;
|
|
26
|
+
options = options || {};
|
|
25
27
|
|
|
26
28
|
// Add triples and prefixes if passed
|
|
27
29
|
this._prefixes = Object.create(null);
|
|
28
|
-
if (options
|
|
30
|
+
if (options.prefixes)
|
|
29
31
|
this.addPrefixes(options.prefixes);
|
|
32
|
+
this.defaultGraph = options.defaultGraph || 'http://example.org/#defaultGraph';
|
|
30
33
|
if (triples)
|
|
31
34
|
this.addTriples(triples);
|
|
32
35
|
}
|
|
@@ -53,12 +56,16 @@ N3Store.prototype = {
|
|
|
53
56
|
// ## Private methods
|
|
54
57
|
|
|
55
58
|
// ### `_addToIndex` adds a triple to a three-layered index.
|
|
59
|
+
// Returns if the index has changed, if the entry did not already exist.
|
|
56
60
|
_addToIndex: function (index0, key0, key1, key2) {
|
|
57
61
|
// Create layers as necessary.
|
|
58
62
|
var index1 = index0[key0] || (index0[key0] = {});
|
|
59
63
|
var index2 = index1[key1] || (index1[key1] = {});
|
|
60
|
-
// Setting the key to _any_ value
|
|
61
|
-
|
|
64
|
+
// Setting the key to _any_ value signals the presence of the triple.
|
|
65
|
+
var existed = key2 in index2;
|
|
66
|
+
if (!existed)
|
|
67
|
+
index2[key2] = null;
|
|
68
|
+
return !existed;
|
|
62
69
|
},
|
|
63
70
|
|
|
64
71
|
// ### `_removeFromIndex` removes a triple from a three-layered index.
|
|
@@ -82,7 +89,9 @@ N3Store.prototype = {
|
|
|
82
89
|
// (for instance: _subject_, _predicate_, and _object_).
|
|
83
90
|
// Finally, `graph` will be the graph of the created triples.
|
|
84
91
|
_findInIndex: function (index0, key0, key1, key2, name0, name1, name2, graph) {
|
|
85
|
-
var results = [],
|
|
92
|
+
var results = [], tmp, index1, index2, varCount = !key0 + !key1 + !key2,
|
|
93
|
+
// depending on the number of variables, keys or reverse index are faster
|
|
94
|
+
entityKeys = varCount > 1 ? Object.keys(this._ids) : this._entities;
|
|
86
95
|
|
|
87
96
|
// If a key is specified, use only that part of index 0.
|
|
88
97
|
if (key0) (tmp = index0, index0 = {})[key0] = tmp[key0];
|
|
@@ -123,7 +132,6 @@ N3Store.prototype = {
|
|
|
123
132
|
if (key0) (tmp = index0, index0 = {})[key0] = tmp[key0];
|
|
124
133
|
for (var value0 in index0) {
|
|
125
134
|
if (index1 = index0[value0]) {
|
|
126
|
-
|
|
127
135
|
// If a key is specified, count only that part of index 1.
|
|
128
136
|
if (key1) (tmp = index1, index1 = {})[key1] = tmp[key1];
|
|
129
137
|
for (var value1 in index1) {
|
|
@@ -142,6 +150,7 @@ N3Store.prototype = {
|
|
|
142
150
|
// ## Public methods
|
|
143
151
|
|
|
144
152
|
// ### `addTriple` adds a new N3 triple to the store.
|
|
153
|
+
// Returns if the triple index has changed, if the triple did not already exist.
|
|
145
154
|
addTriple: function (subject, predicate, object, graph) {
|
|
146
155
|
// Shift arguments if a triple object is given instead of components
|
|
147
156
|
if (!predicate)
|
|
@@ -149,15 +158,11 @@ N3Store.prototype = {
|
|
|
149
158
|
predicate = subject.predicate, subject = subject.subject;
|
|
150
159
|
|
|
151
160
|
// Find the graph that will contain the triple.
|
|
152
|
-
graph = graph ||
|
|
161
|
+
graph = graph || this.defaultGraph;
|
|
153
162
|
var graphItem = this._graphs[graph];
|
|
154
163
|
// Create the graph if it doesn't exist yet.
|
|
155
164
|
if (!graphItem) {
|
|
156
|
-
graphItem = this._graphs[graph] = {
|
|
157
|
-
subjects: {},
|
|
158
|
-
predicates: {},
|
|
159
|
-
objects: {}
|
|
160
|
-
};
|
|
165
|
+
graphItem = this._graphs[graph] = { subjects: {}, predicates: {}, objects: {} };
|
|
161
166
|
// Freezing a graph helps subsequent `add` performance,
|
|
162
167
|
// and properties will never be modified anyway.
|
|
163
168
|
Object.freeze(graphItem);
|
|
@@ -166,17 +171,19 @@ N3Store.prototype = {
|
|
|
166
171
|
// Since entities can often be long IRIs, we avoid storing them in every index.
|
|
167
172
|
// Instead, we have a separate index that maps entities to numbers,
|
|
168
173
|
// which are then used as keys in the other indexes.
|
|
174
|
+
var ids = this._ids;
|
|
169
175
|
var entities = this._entities;
|
|
170
|
-
subject =
|
|
171
|
-
predicate =
|
|
172
|
-
object =
|
|
176
|
+
subject = ids[subject] || (ids[entities[++this._id] = subject] = this._id);
|
|
177
|
+
predicate = ids[predicate] || (ids[entities[++this._id] = predicate] = this._id);
|
|
178
|
+
object = ids[object] || (ids[entities[++this._id] = object] = this._id);
|
|
173
179
|
|
|
174
|
-
this._addToIndex(graphItem.subjects, subject, predicate, object);
|
|
180
|
+
var changed = this._addToIndex(graphItem.subjects, subject, predicate, object);
|
|
175
181
|
this._addToIndex(graphItem.predicates, predicate, object, subject);
|
|
176
182
|
this._addToIndex(graphItem.objects, object, subject, predicate);
|
|
177
183
|
|
|
178
184
|
// The cached triple count is now invalid.
|
|
179
185
|
this._size = null;
|
|
186
|
+
return changed;
|
|
180
187
|
},
|
|
181
188
|
|
|
182
189
|
// ### `addTriples` adds multiple N3 triples to the store.
|
|
@@ -190,7 +197,7 @@ N3Store.prototype = {
|
|
|
190
197
|
this._prefixes[prefix] = iri;
|
|
191
198
|
},
|
|
192
199
|
|
|
193
|
-
// ### `
|
|
200
|
+
// ### `addPrefixes` adds support for querying with the given prefixes
|
|
194
201
|
addPrefixes: function (prefixes) {
|
|
195
202
|
for (var prefix in prefixes)
|
|
196
203
|
this.addPrefix(prefix, prefixes[prefix]);
|
|
@@ -202,20 +209,20 @@ N3Store.prototype = {
|
|
|
202
209
|
if (!predicate)
|
|
203
210
|
graph = subject.graph, object = subject.object,
|
|
204
211
|
predicate = subject.predicate, subject = subject.subject;
|
|
205
|
-
graph = graph ||
|
|
212
|
+
graph = graph || this.defaultGraph;
|
|
206
213
|
|
|
207
214
|
// Find internal identifiers for all components.
|
|
208
|
-
var graphItem,
|
|
209
|
-
if (!(subject =
|
|
210
|
-
if (!(predicate =
|
|
211
|
-
if (!(object =
|
|
212
|
-
if (!(graphItem
|
|
215
|
+
var graphItem, ids = this._ids, graphs = this._graphs;
|
|
216
|
+
if (!(subject = ids[subject])) return false;
|
|
217
|
+
if (!(predicate = ids[predicate])) return false;
|
|
218
|
+
if (!(object = ids[object])) return false;
|
|
219
|
+
if (!(graphItem = graphs[graph])) return false;
|
|
213
220
|
|
|
214
221
|
// Verify that the triple exists.
|
|
215
222
|
var subjects, predicates;
|
|
216
|
-
if (!(subjects = graphItem.subjects[subject])) return;
|
|
217
|
-
if (!(predicates = subjects[predicate])) return;
|
|
218
|
-
if (!(object in predicates)) return;
|
|
223
|
+
if (!(subjects = graphItem.subjects[subject])) return false;
|
|
224
|
+
if (!(predicates = subjects[predicate])) return false;
|
|
225
|
+
if (!(object in predicates)) return false;
|
|
219
226
|
|
|
220
227
|
// Remove it from all indexes.
|
|
221
228
|
this._removeFromIndex(graphItem.subjects, subject, predicate, object);
|
|
@@ -224,8 +231,9 @@ N3Store.prototype = {
|
|
|
224
231
|
if (this._size !== null) this._size--;
|
|
225
232
|
|
|
226
233
|
// Remove the graph if it is empty.
|
|
227
|
-
for (subject in graphItem.subjects) return;
|
|
234
|
+
for (subject in graphItem.subjects) return true;
|
|
228
235
|
delete graphs[graph];
|
|
236
|
+
return true;
|
|
229
237
|
},
|
|
230
238
|
|
|
231
239
|
// ### `removeTriples` removes multiple N3 triples from the store.
|
|
@@ -235,8 +243,7 @@ N3Store.prototype = {
|
|
|
235
243
|
},
|
|
236
244
|
|
|
237
245
|
// ### `find` finds a set of triples matching a pattern, expanding prefixes as necessary.
|
|
238
|
-
// Setting `subject`, `predicate`,
|
|
239
|
-
// Setting `graph` to `null` means the default graph.
|
|
246
|
+
// Setting `subject`, `predicate`, `object` or `graph` to a falsy value means an _anything_ wildcard.
|
|
240
247
|
find: function (subject, predicate, object, graph) {
|
|
241
248
|
var prefixes = this._prefixes;
|
|
242
249
|
return this.findByIRI(
|
|
@@ -248,44 +255,51 @@ N3Store.prototype = {
|
|
|
248
255
|
},
|
|
249
256
|
|
|
250
257
|
// ### `findByIRI` finds a set of triples matching a pattern.
|
|
251
|
-
// Setting `subject`, `predicate`, or `
|
|
252
|
-
// Setting `graph` to a falsy value means the default graph.
|
|
258
|
+
// Setting `subject`, `predicate`, `object` or `graph` to a falsy value means an _anything_ wildcard.
|
|
253
259
|
findByIRI: function (subject, predicate, object, graph) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// Translate IRIs to internal index keys.
|
|
261
|
-
// Optimization: if the entity doesn't exist, no triples with it exist.
|
|
262
|
-
if (subject && !(subject = entities[subject])) return [];
|
|
263
|
-
if (predicate && !(predicate = entities[predicate])) return [];
|
|
264
|
-
if (object && !(object = entities[object])) return [];
|
|
265
|
-
|
|
266
|
-
// Choose the optimal index, based on what fields are present
|
|
267
|
-
if (subject) {
|
|
268
|
-
if (object)
|
|
269
|
-
// If subject and object are given, the object index will be the fastest.
|
|
270
|
-
return this._findInIndex(graphItem.objects, object, subject, predicate,
|
|
271
|
-
'object', 'subject', 'predicate', graph);
|
|
272
|
-
else
|
|
273
|
-
// If only subject and possibly predicate are given, the subject index will be the fastest.
|
|
274
|
-
return this._findInIndex(graphItem.subjects, subject, predicate, null,
|
|
275
|
-
'subject', 'predicate', 'object', graph);
|
|
276
|
-
}
|
|
277
|
-
else if (predicate)
|
|
278
|
-
// If only predicate and possibly object are given, the predicate index will be the fastest.
|
|
279
|
-
return this._findInIndex(graphItem.predicates, predicate, object, null,
|
|
280
|
-
'predicate', 'object', 'subject', graph);
|
|
281
|
-
else if (object)
|
|
282
|
-
// If only object is given, the object index will be the fastest.
|
|
283
|
-
return this._findInIndex(graphItem.objects, object, null, null,
|
|
284
|
-
'object', 'subject', 'predicate', graph);
|
|
260
|
+
var quads = [], graphs = {}, graphContents,
|
|
261
|
+
ids = this._ids, subjectId, predicateId, objectId;
|
|
262
|
+
// Either loop over all graphs, or over just one selected graph.
|
|
263
|
+
if (!graph)
|
|
264
|
+
graphs = this._graphs;
|
|
285
265
|
else
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
266
|
+
graphs[graph] = this._graphs[graph];
|
|
267
|
+
|
|
268
|
+
for (var graphId in graphs) {
|
|
269
|
+
// Only if the specified graph contains triples, there can be results
|
|
270
|
+
if (graphContents = graphs[graphId]) {
|
|
271
|
+
// Translate IRIs to internal index keys.
|
|
272
|
+
// Optimization: if the entity doesn't exist, no triples with it exist.
|
|
273
|
+
if (subject && !(subjectId = ids[subject])) return quads;
|
|
274
|
+
if (predicate && !(predicateId = ids[predicate])) return quads;
|
|
275
|
+
if (object && !(objectId = ids[object])) return quads;
|
|
276
|
+
|
|
277
|
+
// Choose the optimal index, based on what fields are present
|
|
278
|
+
if (subjectId) {
|
|
279
|
+
if (objectId)
|
|
280
|
+
// If subject and object are given, the object index will be the fastest.
|
|
281
|
+
quads.push(this._findInIndex(graphContents.objects, objectId, subjectId, predicateId,
|
|
282
|
+
'object', 'subject', 'predicate', graphId));
|
|
283
|
+
else
|
|
284
|
+
// If only subject and possibly predicate are given, the subject index will be the fastest.
|
|
285
|
+
quads.push(this._findInIndex(graphContents.subjects, subjectId, predicateId, null,
|
|
286
|
+
'subject', 'predicate', 'object', graphId));
|
|
287
|
+
}
|
|
288
|
+
else if (predicateId)
|
|
289
|
+
// If only predicate and possibly object are given, the predicate index will be the fastest.
|
|
290
|
+
quads.push(this._findInIndex(graphContents.predicates, predicateId, objectId, null,
|
|
291
|
+
'predicate', 'object', 'subject', graphId));
|
|
292
|
+
else if (objectId)
|
|
293
|
+
// If only object is given, the object index will be the fastest.
|
|
294
|
+
quads.push(this._findInIndex(graphContents.objects, objectId, null, null,
|
|
295
|
+
'object', 'subject', 'predicate', graphId));
|
|
296
|
+
else
|
|
297
|
+
// If nothing is given, iterate subjects and predicates first
|
|
298
|
+
quads.push(this._findInIndex(graphContents.subjects, null, null, null,
|
|
299
|
+
'subject', 'predicate', 'object', graphId));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return quads.length === 1 ? quads[0] : quads.concat.apply([], quads);
|
|
289
303
|
},
|
|
290
304
|
|
|
291
305
|
// ### `count` returns the number of triples matching a pattern, expanding prefixes as necessary.
|
|
@@ -305,17 +319,17 @@ N3Store.prototype = {
|
|
|
305
319
|
// Setting `subject`, `predicate`, or `object` to `null` means an _anything_ wildcard.
|
|
306
320
|
// Setting `graph` to `null` means the default graph.
|
|
307
321
|
countByIRI: function (subject, predicate, object, graph) {
|
|
308
|
-
graph = graph ||
|
|
309
|
-
var graphItem = this._graphs[graph],
|
|
322
|
+
graph = graph || this.defaultGraph;
|
|
323
|
+
var graphItem = this._graphs[graph], ids = this._ids;
|
|
310
324
|
|
|
311
325
|
// If the specified graph contain no triples, there are no results.
|
|
312
326
|
if (!graphItem) return 0;
|
|
313
327
|
|
|
314
328
|
// Translate IRIs to internal index keys.
|
|
315
329
|
// Optimization: if the entity doesn't exist, no triples with it exist.
|
|
316
|
-
if (subject && !(subject =
|
|
317
|
-
if (predicate && !(predicate =
|
|
318
|
-
if (object && !(object =
|
|
330
|
+
if (subject && !(subject = ids[subject])) return 0;
|
|
331
|
+
if (predicate && !(predicate = ids[predicate])) return 0;
|
|
332
|
+
if (object && !(object = ids[object])) return 0;
|
|
319
333
|
|
|
320
334
|
// Choose the optimal index, based on what fields are present
|
|
321
335
|
if (subject) {
|
|
@@ -338,18 +352,20 @@ N3Store.prototype = {
|
|
|
338
352
|
|
|
339
353
|
// ### `createBlankNode` creates a new blank node, returning its name.
|
|
340
354
|
createBlankNode: function (suggestedName) {
|
|
341
|
-
var name;
|
|
355
|
+
var name, index;
|
|
356
|
+
// Generate a name based on the suggested name
|
|
342
357
|
if (suggestedName) {
|
|
343
|
-
name = suggestedName = '_:' + suggestedName;
|
|
344
|
-
|
|
345
|
-
while (this._entities[name])
|
|
358
|
+
name = suggestedName = '_:' + suggestedName, index = 1;
|
|
359
|
+
while (this._ids[name])
|
|
346
360
|
name = suggestedName + index++;
|
|
347
361
|
}
|
|
362
|
+
// Generate a generic blank node name
|
|
348
363
|
else {
|
|
349
364
|
do { name = '_:b' + this._blankNodeIndex++; }
|
|
350
|
-
while (this.
|
|
365
|
+
while (this._ids[name]);
|
|
351
366
|
}
|
|
352
|
-
|
|
367
|
+
// Add the blank node to the entities, avoiding the generation of duplicates
|
|
368
|
+
this._ids[name] = ++this._id;
|
|
353
369
|
return name;
|
|
354
370
|
},
|
|
355
371
|
};
|