n3 3.0.0-alpha.7 → 3.0.0-alpha.9

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/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
- // Return the quad count if if was cached
277
- let size = this._size;
278
- if (size !== null)
279
- return size;
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
- // The cached quad count is now invalid
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++)
@@ -688,8 +648,10 @@ export default class N3Store {
688
648
  if (this._size !== null) this._size--;
689
649
 
690
650
  // Remove the graph if it is empty
691
- if (graphItem.subjects[SIZE] === 0)
651
+ if (graphItem.subjects[SIZE] === 0) {
692
652
  delete graphs[graph];
653
+ this._graphCount--;
654
+ }
693
655
  return true;
694
656
  }
695
657
 
@@ -827,10 +789,14 @@ export default class N3Store {
827
789
  // If only predicate and possibly object are given, the predicate index will be the fastest
828
790
  count += this._countInIndex(content.predicates, predicateId, objectId, subjectId);
829
791
  }
830
- else {
831
- // If only object is possibly given, the object index will be the fastest
792
+ else if (object) {
793
+ // If only object is given, the object index will be the fastest
832
794
  count += this._countInIndex(content.objects, objectId, subjectId, predicateId);
833
795
  }
796
+ else {
797
+ // Without a pattern, the subject index sums the fewest leaf counts
798
+ count += this._countInIndex(content.subjects);
799
+ }
834
800
  }
835
801
  }
836
802
  return count;
@@ -1148,13 +1114,9 @@ export default class N3Store {
1148
1114
 
1149
1115
  if (Array.isArray(quads))
1150
1116
  this.addQuads(quads);
1151
- // Index merging bypasses observer notifications
1152
- else if (this._observers === null && quads instanceof N3Store && quads._entityIndex === this._entityIndex) {
1153
- if (quads._size !== 0) {
1154
- this._graphs = merge(this._graphs, quads._graphs);
1155
- this._size = null; // Invalidate the cached size
1156
- }
1157
- }
1117
+ // A store with the same entity index can be copied by identifier
1118
+ else if (quads instanceof N3Store && quads._entityIndex === this._entityIndex)
1119
+ this._addFromIndex(quads._graphs);
1158
1120
  else {
1159
1121
  for (const quad of quads)
1160
1122
  this.add(quad);
@@ -1175,19 +1137,26 @@ export default class N3Store {
1175
1137
  if (other === this)
1176
1138
  return true;
1177
1139
 
1178
- if (!(other instanceof N3Store) || this._entityIndex !== other._entityIndex)
1140
+ if (!(other instanceof N3Store) || this._entityIndex !== other._entityIndex) {
1141
+ // A larger set cannot be a subset, but only compare sizes that are known without counting
1142
+ const otherSize = knownSize(other), thisSize = this._size;
1143
+ if (otherSize !== null && thisSize !== null && otherSize > thisSize)
1144
+ return false;
1179
1145
  return other.every(quad => this.has(quad));
1146
+ }
1180
1147
 
1148
+ // At every level, the index of the subset cannot have more entries
1181
1149
  const g1 = this._graphs, g2 = other._graphs;
1182
- let s1, s2, p1, p2, o1;
1150
+ let s1, s2, p1, p2, o1, o2;
1183
1151
  for (const graph in g2) {
1184
1152
  if (!(s1 = g1[graph])) return false;
1185
1153
  s1 = s1.subjects;
1186
- for (const subject in (s2 = g2[graph].subjects)) {
1187
- if (!(p1 = s1[subject])) return false;
1188
- for (const predicate in (p2 = s2[subject])) {
1189
- if (!(o1 = p1[predicate])) return false;
1190
- for (const object in p2[predicate])
1154
+ if ((s2 = g2[graph].subjects)[SIZE] > s1[SIZE]) return false;
1155
+ for (const subject in s2) {
1156
+ if (!(p1 = s1[subject]) || (p2 = s2[subject])[SIZE] > p1[SIZE]) return false;
1157
+ for (const predicate in p2) {
1158
+ if (!(o1 = p1[predicate]) || (o2 = p2[predicate])[SIZE] > o1[SIZE]) return false;
1159
+ for (const object in o2)
1191
1160
  if (!(object in o1)) return false;
1192
1161
  }
1193
1162
  }
@@ -1224,11 +1193,7 @@ export default class N3Store {
1224
1193
 
1225
1194
  if ((other instanceof N3Store) && other._entityIndex === this._entityIndex) {
1226
1195
  const store = new N3Store({ entityIndex: this._entityIndex });
1227
- const graphs = difference(this._graphs, other._graphs);
1228
- if (graphs) {
1229
- store._graphs = graphs;
1230
- store._size = null;
1231
- }
1196
+ store._addFromIndex(this._graphs, other._graphs);
1232
1197
  return store;
1233
1198
  }
1234
1199
 
@@ -1269,20 +1234,34 @@ export default class N3Store {
1269
1234
 
1270
1235
  if (other === this) {
1271
1236
  const store = new N3Store({ entityIndex: this._entityIndex });
1272
- store._graphs = merge(Object.create(null), this._graphs);
1273
- store._size = this._size;
1237
+ store._addFromIndex(this._graphs);
1274
1238
  return store;
1275
1239
  }
1276
1240
  else if ((other instanceof N3Store) && this._entityIndex === other._entityIndex) {
1277
1241
  const store = new N3Store({ entityIndex: this._entityIndex });
1278
- const graphs = intersect(other._graphs, this._graphs);
1279
- if (graphs) {
1280
- store._graphs = graphs;
1281
- store._size = null;
1242
+ // Starting a loop over no graphs costs more than checking for an empty store
1243
+ if (this._size !== 0 && other._size !== 0) {
1244
+ if (other._graphCount < this._graphCount)
1245
+ store._addIntersectionFromIndex(other._graphs, this._graphs);
1246
+ else
1247
+ store._addIntersectionFromIndex(this._graphs, other._graphs);
1282
1248
  }
1283
1249
  return store;
1284
1250
  }
1285
1251
 
1252
+ // Test the quads of the smaller dataset against the larger one
1253
+ // when both sizes are known without counting
1254
+ const otherSize = knownSize(other), thisSize = this._size;
1255
+ if (otherSize !== null && thisSize !== null && otherSize < thisSize && typeof other[Symbol.iterator] === 'function' &&
1256
+ // unless it is a store whose custom factories may not create RDF/JS quads,
1257
+ // either for its quads or, through its entity index, for their terms
1258
+ other._factory === N3DataFactory && other._entityIndex._factory === N3DataFactory) {
1259
+ const store = new N3Store({ entityIndex: this._entityIndex });
1260
+ for (const quad of other)
1261
+ if (this.has(quad))
1262
+ store.add(quad);
1263
+ return store;
1264
+ }
1286
1265
  return this.filter(quad => other.has(quad));
1287
1266
  }
1288
1267
 
@@ -1353,9 +1332,7 @@ export default class N3Store {
1353
1332
  */
1354
1333
  union(quads) {
1355
1334
  const store = new N3Store({ entityIndex: this._entityIndex });
1356
- store._graphs = merge(Object.create(null), this._graphs);
1357
- store._size = this._size;
1358
-
1335
+ store._addFromIndex(this._graphs);
1359
1336
  store.addAll(quads);
1360
1337
  return store;
1361
1338
  }
@@ -1620,8 +1597,10 @@ class DatasetCoreAndReadableStream extends Readable {
1620
1597
  objects = indexMatch(content.objects, [objectId, subjectId, predicateId]);
1621
1598
  }
1622
1599
 
1623
- if (subjects)
1600
+ if (subjects) {
1624
1601
  newStore._graphs[graphKey] = { subjects, predicates, objects };
1602
+ newStore._graphCount++;
1603
+ }
1625
1604
  }
1626
1605
  }
1627
1606
  newStore._size = null;
package/src/N3Writer.js CHANGED
@@ -10,15 +10,11 @@ const DEFAULTGRAPH = N3DataFactory.defaultGraph();
10
10
  const { rdf, xsd } = namespaces;
11
11
  const { hasOwnProperty } = Object.prototype;
12
12
 
13
- // Characters that require escaping in Turtle, TriG, and N3,
14
- // where characters outside the Basic Multilingual Plane are escaped as well
15
- const escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/,
16
- escapeAll = /["\\\t\n\r\b\f\u0000-\u0019]|[\ud800-\udbff][\udc00-\udfff]/g,
17
- // Characters that require escaping in canonical N-Triples and N-Quads:
18
- // U+0000–U+001F, `"`, `\`, U+007F, U+FFFE, and U+FFFF.
19
- // A negated class is as fast to test as a shorter class listing characters.
20
- canonicalEscape = /[^ !#-\[\]-~\u0080-\uFFFD]/,
21
- canonicalEscapeAll = /[^ !#-\[\]-~\u0080-\uFFFD]/g,
13
+ // Characters that require escaping, as in canonical N-Triples:
14
+ // U+0000–U+001F, `"`, `\`, U+007F, U+FFFE, and U+FFFF.
15
+ // A negated class is as fast to test as a shorter class listing characters.
16
+ const escape = /[^ !#-\[\]-~\u0080-\uFFFD]/,
17
+ escapeAll = /[^ !#-\[\]-~\u0080-\uFFFD]/g,
22
18
  escapedCharacters = {
23
19
  '\\': '\\\\', '"': '\\"', '\t': '\\t',
24
20
  '\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f',
@@ -72,7 +68,6 @@ export default class N3Writer {
72
68
  this._subject = null;
73
69
  if (!(/triple|quad/i).test(options.format)) {
74
70
  this._lineMode = false;
75
- this._escape = escape, this._escapeAll = escapeAll, this._characterReplacer = characterReplacer;
76
71
  this._graph = DEFAULTGRAPH;
77
72
  if (options.version)
78
73
  this._write(`@version "${options.version}".\n`);
@@ -89,9 +84,6 @@ export default class N3Writer {
89
84
  else {
90
85
  this._lineMode = true;
91
86
  this._writeQuad = this._writeQuadLine;
92
- // N-Triples and N-Quads are written in their canonical form
93
- this._escape = canonicalEscape, this._escapeAll = canonicalEscapeAll;
94
- this._characterReplacer = canonicalCharacterReplacer;
95
87
  if (options.version)
96
88
  this._write(`VERSION "${options.version}"\n`);
97
89
  }
@@ -208,8 +200,8 @@ export default class N3Writer {
208
200
  iri = this._baseIri.toRelative(iri);
209
201
  }
210
202
  // Escape special characters
211
- if (this._escape.test(iri))
212
- iri = iri.replace(this._escapeAll, this._characterReplacer);
203
+ if (escape.test(iri))
204
+ iri = iri.replace(escapeAll, characterReplacer);
213
205
  // Try to represent the IRI as prefixed name, unless no prefixes were added
214
206
  const prefixMatch = this._hasPrefixes ? (this._prefixRegex || this._createPrefixRegex()).exec(iri) : null;
215
207
  return !prefixMatch ? `<${iri}>` : this._prefixIRIs[prefixMatch[1]] + prefixMatch[2];
@@ -219,8 +211,8 @@ export default class N3Writer {
219
211
  _encodeLiteral(literal) {
220
212
  // Escape special characters
221
213
  let value = literal.value;
222
- if (this._escape.test(value))
223
- value = value.replace(this._escapeAll, this._characterReplacer);
214
+ if (escape.test(value))
215
+ value = value.replace(escapeAll, characterReplacer);
224
216
 
225
217
  // Write a language-tagged literal
226
218
  const language = literal.language;
@@ -291,13 +283,11 @@ export default class N3Writer {
291
283
 
292
284
  // ### `_encodeQuad` encodes an RDF-star quad
293
285
  _encodeQuad({ subject, predicate, object, graph }) {
294
- // Canonical N-Triples and N-Quads put spaces inside the delimiters
295
- const space = this._lineMode ? ' ' : '';
296
- return `<<(${space}${
286
+ return `<<( ${
297
287
  this._encodeSubject(subject)} ${
298
288
  this._encodePredicate(predicate)} ${
299
289
  this._encodeObject(object)}${
300
- isDefaultGraph(graph) ? '' : ` ${this._encodeIriOrBlank(graph)}`}${space})>>`;
290
+ isDefaultGraph(graph) ? '' : ` ${this._encodeIriOrBlank(graph)}`} )>>`;
301
291
  }
302
292
 
303
293
  // ### `_blockedWrite` replaces `_write` after the writer has been closed
@@ -440,27 +430,7 @@ export default class N3Writer {
440
430
 
441
431
  // Replaces a character by its escaped version
442
432
  function characterReplacer(character) {
443
- // Replace a single character by its escaped version
444
- let result = escapedCharacters[character];
445
- if (result === undefined) {
446
- // Replace a single character with its 4-bit unicode escape sequence
447
- if (character.length === 1) {
448
- result = character.charCodeAt(0).toString(16);
449
- result = '\\u0000'.substr(0, 6 - result.length) + result;
450
- }
451
- // Replace a surrogate pair with its 8-bit unicode escape sequence
452
- else {
453
- result = ((character.charCodeAt(0) - 0xD800) * 0x400 +
454
- character.charCodeAt(1) + 0x2400).toString(16);
455
- result = '\\U00000000'.substr(0, 10 - result.length) + result;
456
- }
457
- }
458
- return result;
459
- }
460
-
461
- // Replaces a character by its canonical N-Triples escape:
462
- // a short escape where one exists, and an upper-case `\uXXXX` escape otherwise
463
- function canonicalCharacterReplacer(character) {
433
+ // Use a short escape where one exists, and an upper-case `\uXXXX` escape otherwise
464
434
  let result = escapedCharacters[character];
465
435
  if (result === undefined) {
466
436
  result = character.charCodeAt(0).toString(16).toUpperCase();