n3 2.10.7 → 2.11.1

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/lib/BaseIRI.js CHANGED
@@ -9,7 +9,9 @@ var _Util = require("./Util");
9
9
  // - file: IRIs (which could also use backslashes)
10
10
  // - IRIs containing /. or /.. or //
11
11
  const BASE_UNSUPPORTED = /^:?[^:?#]*(?:[?#]|$)|^file:|^[^:]*:\/*[^?#]+?\/(?:\.\.?(?:\/|$)|\/)/i;
12
- const SUFFIX_SUPPORTED = /^(?:(?:[^/?#]{3,}|\.?[^/?#.]\.?)(?:\/[^/?#]{3,}|\.?[^/?#.]\.?)*\/?)?(?:[?#]|$)/;
12
+ // Supported suffixes have a first path segment other than . or ..,
13
+ // and further path segments of at least 3 characters
14
+ const SUFFIX_SUPPORTED = /^(?:(?!\.\.?(?:[/?#]|$))[^/?#]+(?:\/[^/?#]{3,})*\/?)?(?:[?#]|$)/;
13
15
  const CURRENT = './';
14
16
  const PARENT = '../';
15
17
  const QUERY = '?';
@@ -80,8 +82,9 @@ class BaseIRI {
80
82
  const suffix = iri.substring(length);
81
83
  // Don't abbreviate unsupported path
82
84
  if (parentPath !== QUERY && !SUFFIX_SUPPORTED.test(suffix)) return iri;
83
- // Omit ./ with fragment or query string
84
- if (parentPath === CURRENT && /^[^?#]/.test(suffix)) return suffix;
85
+ // Omit ./ with fragment or query string,
86
+ // unless the first segment has a colon and would be read as a scheme
87
+ if (parentPath === CURRENT && /^[^?#]/.test(suffix) && (suffix.indexOf(':') < 0 || !/^[^/?#]*:/.test(suffix))) return suffix;
85
88
  // Append suffix to relative parent path
86
89
  return parentPath + suffix;
87
90
  }
@@ -427,7 +427,7 @@ function literal(value, languageOrDataType) {
427
427
  return new Literal(`"${value}"@${languageOrDataType.language.toLowerCase()}${languageOrDataType.direction ? `--${languageOrDataType.direction.toLowerCase()}` : ''}`);
428
428
  }
429
429
 
430
- // Automatically determine datatype for booleans, numbers, and dates
430
+ // Automatically determine datatype for booleans, numbers, BigInts, and dates
431
431
  let datatype = languageOrDataType ? languageOrDataType.value : '';
432
432
  if (datatype === '') {
433
433
  // Convert a boolean
@@ -443,6 +443,8 @@ function literal(value, languageOrDataType) {
443
443
  if (!Number.isNaN(value)) value = value > 0 ? 'INF' : '-INF';
444
444
  }
445
445
  }
446
+ // Convert a BigInt, whose decimal string is always a valid xsd:integer lexical
447
+ else if (typeof value === 'bigint') datatype = xsd.integer;
446
448
  // Convert a valid date
447
449
  else if (value instanceof Date && !Number.isNaN(value.getTime())) {
448
450
  datatype = xsd.dateTime;
@@ -482,7 +484,10 @@ function fromTerm(term) {
482
484
  case 'DefaultGraph':
483
485
  return DEFAULTGRAPH;
484
486
  case 'Literal':
485
- return literal(term.value, term.language || term.datatype);
487
+ return literal(term.value, !term.language ? term.datatype : term.direction ? {
488
+ language: term.language,
489
+ direction: term.direction
490
+ } : term.language);
486
491
  case 'Quad':
487
492
  return fromQuad(term);
488
493
  default:
package/lib/N3Lexer.js CHANGED
@@ -53,6 +53,8 @@ const localNameEscapeReplacements = {
53
53
  '%': '%'
54
54
  };
55
55
  const illegalIriChars = /[\x00-\x20<>\\"\{\}\|\^\`]/;
56
+ // Characters that cannot occur in a prefixed name, not even escaped
57
+ const nonPrefixedNameChar = /[\s<>"{}|^`]/;
56
58
 
57
59
  // A valid code point is a Unicode scalar value: at most U+10FFFF and not a surrogate
58
60
  function isValidCodePoint(charCode) {
@@ -80,10 +82,10 @@ class N3Lexer {
80
82
  this._simpleApostropheString = /^'([^'\\\r\n]*)'(?=[^'])/;
81
83
  this._langcode = /^@([a-z]+(?:-[a-z0-9]+)*)(?=[^a-z0-9])/i;
82
84
  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<])/;
83
- 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-: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-: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-: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#()\[\]\{\}"'<>]))/;
84
- 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-: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#()\[\]\{\}"'<>])/;
85
+ 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#()\[\]\{\}"'<>]))/;
86
+ 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#()\[\]\{\}"'<>])/;
85
87
  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#()\[\]\{\}"'<>]))/;
86
- this._number = /^[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?|\d*(\.)?)\d+(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/;
88
+ this._number = /^[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/;
87
89
  this._boolean = /^(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/;
88
90
  this._atKeyword = /^@[a-z]+(?=[\s#<:])/i;
89
91
  this._keyword = /^(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/i;
@@ -532,7 +534,12 @@ class N3Lexer {
532
534
 
533
535
  // A prefix can start with a verb and continue with characters that are also
534
536
  // valid verb boundaries. Prefer the longer prefixed name when it is complete.
535
- if (this._prefixed.exec(input) || this._prefixed.exec(`${input} `)) return null;
537
+ if (this._prefixed.exec(input)) return null;
538
+ // Appending to the input only matters when a prefixed name could run up to
539
+ // its end, which a character that cannot occur in prefixed names rules out.
540
+ // This avoids copying the rest of the document for every such verb.
541
+ if (nonPrefixedNameChar.test(input)) return verb;
542
+ if (this._prefixed.exec(`${input} `)) return null;
536
543
 
537
544
  // If a stream chunk ends partway through such a prefix, wait for the colon
538
545
  // instead of prematurely emitting the verb. Appending ": " lets the prefix
package/lib/N3Parser.js CHANGED
@@ -24,7 +24,7 @@ class N3Parser {
24
24
  options.factory && initDataFactory(this, options.factory);
25
25
 
26
26
  // Set supported features depending on the format
27
- const format = typeof options.format === 'string' ? options.format.match(/\w*$/)[0].toLowerCase() : '',
27
+ const format = typeof options.format === 'string' ? options.format.match(/(?:^|\W)(\w*)$/)[1].toLowerCase() : '',
28
28
  isTurtle = /turtle/.test(format),
29
29
  isTriG = /trig/.test(format),
30
30
  isNTriples = /triple/.test(format),
@@ -87,7 +87,9 @@ class N3Parser {
87
87
  if (fragmentPos >= 0) baseIRI = baseIRI.substr(0, fragmentPos);
88
88
  // Set base IRI and its components
89
89
  this._base = baseIRI;
90
- this._basePath = baseIRI.indexOf('/') < 0 ? baseIRI : baseIRI.replace(/[^\/?]*(?:\?.*)?$/, '');
90
+ const queryPos = baseIRI.indexOf('?');
91
+ const path = queryPos < 0 ? baseIRI : baseIRI.substr(0, queryPos);
92
+ this._basePath = baseIRI.indexOf('/') < 0 ? baseIRI : path.substr(0, path.lastIndexOf('/') + 1);
91
93
  baseIRI = baseIRI.match(/^(?:([a-z][a-z0-9+.-]*:))?(?:\/\/[^\/]*)?/i);
92
94
  this._baseRoot = baseIRI[0];
93
95
  this._baseScheme = baseIRI[1];
@@ -117,14 +119,15 @@ class N3Parser {
117
119
  inverse: this._inversePredicate,
118
120
  expectOf: this._expectOf,
119
121
  blankPrefix: this._prefixes._,
120
- quantified: this._quantified,
122
+ prefixChanges: this._prefixChanges,
123
+ quantifiedChanges: this._quantifiedChanges,
121
124
  emptyFormula: this._emptyFormula
122
125
  };
123
- // Prefix and base declarations are scoped to their formula
126
+ // Prefix and base declarations are scoped to their formula,
127
+ // so record prefix changes to undo them when the formula ends
124
128
  if (type === 'formula') {
125
- context.prefixes = this._prefixes;
126
129
  context.base = [this._base, this._basePath, this._baseRoot, this._baseScheme];
127
- this._prefixes = Object.create(this._prefixes);
130
+ this._prefixChanges = [];
128
131
  }
129
132
  this._contextStack.push(context);
130
133
  // Every new scope resets the predicate direction
@@ -137,7 +140,7 @@ class N3Parser {
137
140
  // Label the scope with the enclosing formula's blank node
138
141
  // (using a dot as separator, as a blank node label cannot start with it)
139
142
  this._prefixes._ = this._graph ? `${this._graph.value}.` : '.';
140
- this._quantified = Object.create(this._quantified);
143
+ this._quantifiedChanges = [];
141
144
  }
142
145
  // A formula starts empty and must not inherit its parent's subject
143
146
  if (type === 'formula') {
@@ -163,11 +166,16 @@ class N3Parser {
163
166
  if (this._n3Mode) {
164
167
  this._inversePredicate = context.inverse;
165
168
  this._expectOf = context.expectOf;
166
- if (type === 'formula') {
167
- this._prefixes = context.prefixes;
168
- [this._base, this._basePath, this._baseRoot, this._baseScheme] = context.base;
169
- } else this._prefixes._ = context.blankPrefix;
170
- this._quantified = context.quantified;
169
+ if (type === 'formula') [this._base, this._basePath, this._baseRoot, this._baseScheme] = context.base;
170
+ if (this._prefixChanges !== context.prefixChanges) {
171
+ undoChanges(this._prefixes, this._prefixChanges);
172
+ this._prefixChanges = context.prefixChanges;
173
+ }
174
+ this._prefixes._ = context.blankPrefix;
175
+ if (this._quantifiedChanges !== context.quantifiedChanges) {
176
+ undoChanges(this._quantified, this._quantifiedChanges);
177
+ this._quantifiedChanges = context.quantifiedChanges;
178
+ }
171
179
  this._emptyFormula = context.emptyFormula;
172
180
  }
173
181
  }
@@ -976,6 +984,7 @@ class N3Parser {
976
984
  _readPrefixIRI(token) {
977
985
  if (token.type !== 'IRI') return this._error(`Expected IRI to follow prefix "${this._prefix}:"`, token);
978
986
  const prefixNode = this._readEntity(token);
987
+ if (this._prefixChanges !== null) this._prefixChanges.push(this._prefix, this._prefixes[this._prefix]);
979
988
  this._prefixes[this._prefix] = prefixNode.value;
980
989
  this._prefixCallback(this._prefix, prefixNode);
981
990
  return this._readDeclarationPunctuation;
@@ -1046,7 +1055,10 @@ class N3Parser {
1046
1055
  return this._error(`Unexpected ${token.type}`, token);
1047
1056
  }
1048
1057
  // Without explicit quantifiers, map entities to a quantified entity
1049
- if (!this._explicitQuantifiers) this._quantified[entity.id] = this._factory[this._quantifier](this._factory.blankNode().value);
1058
+ if (!this._explicitQuantifiers) {
1059
+ if (this._quantifiedChanges !== null) this._quantifiedChanges.push(entity.id, this._quantified[entity.id]);
1060
+ this._quantified[entity.id] = this._factory[this._quantifier](this._factory.blankNode().value);
1061
+ }
1050
1062
  // With explicit quantifiers, output the reified quantifier
1051
1063
  else {
1052
1064
  // If this is the first item, start a new quantifier list
@@ -1407,7 +1419,10 @@ class N3Parser {
1407
1419
  return this._base + iri;
1408
1420
  // Resolve relative query string IRIs by replacing the query string
1409
1421
  case '?':
1410
- return this._base.replace(/(?:\?.*)?$/, iri);
1422
+ {
1423
+ const queryPos = this._base.indexOf('?');
1424
+ return (queryPos < 0 ? this._base : this._base.substr(0, queryPos)) + iri;
1425
+ }
1411
1426
  // Resolve root-relative IRIs at the root of the base IRI
1412
1427
  case '/':
1413
1428
  // Resolve scheme-relative IRIs to the scheme
@@ -1513,6 +1528,9 @@ class N3Parser {
1513
1528
  this._inversePredicate = false;
1514
1529
  this._expectOf = false;
1515
1530
  this._quantified = Object.create(null);
1531
+ // Changes to prefixes and quantifiers in nested scopes, to undo when leaving them
1532
+ this._prefixChanges = null;
1533
+ this._quantifiedChanges = null;
1516
1534
  this._emptyFormula = false;
1517
1535
  let readToken = token => {
1518
1536
  return this._readCallback = this._readCallback(token);
@@ -1569,8 +1587,15 @@ class N3Parser {
1569
1587
  }
1570
1588
  }
1571
1589
 
1572
- // The empty function
1590
+ // ### `undoChanges` restores the entries of a map from a list of key and previous value pairs
1573
1591
  exports.default = N3Parser;
1592
+ function undoChanges(map, changes) {
1593
+ for (let i = changes.length - 2; i >= 0; i -= 2) {
1594
+ if (changes[i + 1] === undefined) delete map[changes[i]];else map[changes[i]] = changes[i + 1];
1595
+ }
1596
+ }
1597
+
1598
+ // The empty function
1574
1599
  function noop() {}
1575
1600
 
1576
1601
  // Initializes the parser with the given data factory
package/lib/N3Store.js CHANGED
@@ -37,7 +37,10 @@ function merge(target, source, depth = 4) {
37
37
  if (!(key in target)) {
38
38
  size++;
39
39
  target[key] = depth === 0 ? null : merge(Object.create(null), source[key], depth - 1);
40
- } else if (depth !== 0) target[key] = merge(target[key], source[key], depth - 1);
40
+ }
41
+ // Merge into the existing object in place,
42
+ // as graph objects are frozen and cannot be reassigned
43
+ else if (depth !== 0) merge(target[key], source[key], depth - 1);
41
44
  }
42
45
  // Depth 2 is the level of the `subjects`, `predicates`, and `objects` indexes.
43
46
  if (depth <= 2) target[SIZE] = size;
@@ -945,16 +948,20 @@ class N3Store {
945
948
  }
946
949
 
947
950
  // Store the list item and continue with parent
948
- if (!first) malformed = onError(current, 'has no list head');else items.unshift(first.object);
951
+ if (!first) malformed = onError(current, 'has no list head');else items.push(first.object);
949
952
  current = parent && parent.subject;
950
953
  }
951
954
 
952
955
  // Don't remove any quads if the list is malformed
953
956
  if (malformed) remove = false;else {
957
+ // Items were collected from the tail to the head
958
+ items.reverse();
954
959
  // Store the list under the value of its head
955
960
  if (head) lists[head[headPos].value] = items;
956
961
  // Leave lists with extra arcs fully intact; otherwise queue this list once.
957
- if (remove && !extraArcs) toRemove.push(...listQuads);
962
+ if (remove && !extraArcs) {
963
+ for (let i = 0; i < listQuads.length; i++) toRemove.push(listQuads[i]);
964
+ }
958
965
  }
959
966
  });
960
967
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "2.10.7",
3
+ "version": "2.11.1",
4
4
  "description": "Lightning fast, asynchronous, streaming Turtle / N3 / RDF library.",
5
5
  "author": "Ruben Verborgh <ruben.verborgh@gmail.com>",
6
6
  "keywords": [
@@ -83,6 +83,17 @@
83
83
  "spec-1-2-earl-nquads": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-n-quads/syntax/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-nquads.ttl",
84
84
  "spec-1-2-earl-turtle": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-turtle/syntax/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-turtle.ttl",
85
85
  "spec-1-2-earl-trig": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-trig/syntax/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-trig.ttl",
86
+ "spec-writer": "npm run spec-writer-1-1-turtle && npm run spec-writer-1-1-ntriples && npm run spec-writer-1-1-nquads && npm run spec-writer-1-1-trig && npm run spec-writer-1-2-turtle && npm run spec-writer-1-2-ntriples && npm run spec-writer-1-2-nquads && npm run spec-writer-1-2-trig && npm run spec-writer-1-2-turtle-eval && npm run spec-writer-1-2-trig-eval",
87
+ "spec-writer-1-1-turtle": "rdf-test-suite spec/writer.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-turtle/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/ --skip \"(bad-numeric-escape|bareword_decimal)\"",
88
+ "spec-writer-1-1-ntriples": "rdf-test-suite spec/writer.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-triples/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/",
89
+ "spec-writer-1-1-nquads": "rdf-test-suite spec/writer.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-quads/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/",
90
+ "spec-writer-1-1-trig": "rdf-test-suite spec/writer.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-trig/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/ --skip \"(bad-numeric-escape|IRI-resolution-01)\"",
91
+ "spec-writer-1-2-turtle": "rdf-test-suite spec/writer.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-turtle/syntax/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/",
92
+ "spec-writer-1-2-ntriples": "rdf-test-suite spec/writer.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-n-triples/syntax/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/",
93
+ "spec-writer-1-2-nquads": "rdf-test-suite spec/writer.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-n-quads/syntax/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/",
94
+ "spec-writer-1-2-trig": "rdf-test-suite spec/writer.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-trig/syntax/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/",
95
+ "spec-writer-1-2-turtle-eval": "rdf-test-suite spec/writer.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-turtle/eval/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/",
96
+ "spec-writer-1-2-trig-eval": "rdf-test-suite spec/writer.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-trig/eval/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/",
86
97
  "spec-n3": "npm run spec-n3-parser && npm run spec-n3-extended",
87
98
  "spec-n3-fetch": "sh scripts/fetch-n3-tests.sh",
88
99
  "spec-n3-parser": "npm run spec-n3-fetch && rdf-test-suite spec/parser.js https://w3c-cg.github.io/N3/tests/N3Tests/manifest-parser.ttl -i '{ \"format\": \"text/n3\" }' -m 'https://w3c-cg.github.io/N3/tests/~.n3-tests/tests/'",
@@ -126,7 +137,28 @@
126
137
  },
127
138
  "release": {
128
139
  "branches": [
129
- "main"
140
+ "main",
141
+ {
142
+ "name": "next-major",
143
+ "prerelease": "alpha",
144
+ "channel": "alpha"
145
+ }
146
+ ],
147
+ "plugins": [
148
+ [
149
+ "@semantic-release/commit-analyzer",
150
+ {
151
+ "preset": "conventionalcommits"
152
+ }
153
+ ],
154
+ [
155
+ "@semantic-release/release-notes-generator",
156
+ {
157
+ "preset": "conventionalcommits"
158
+ }
159
+ ],
160
+ "@semantic-release/npm",
161
+ "@semantic-release/github"
130
162
  ]
131
163
  },
132
164
  "publishConfig": {
package/src/BaseIRI.js CHANGED
@@ -4,7 +4,9 @@ import { escapeRegex } from './Util';
4
4
  // - file: IRIs (which could also use backslashes)
5
5
  // - IRIs containing /. or /.. or //
6
6
  const BASE_UNSUPPORTED = /^:?[^:?#]*(?:[?#]|$)|^file:|^[^:]*:\/*[^?#]+?\/(?:\.\.?(?:\/|$)|\/)/i;
7
- const SUFFIX_SUPPORTED = /^(?:(?:[^/?#]{3,}|\.?[^/?#.]\.?)(?:\/[^/?#]{3,}|\.?[^/?#.]\.?)*\/?)?(?:[?#]|$)/;
7
+ // Supported suffixes have a first path segment other than . or ..,
8
+ // and further path segments of at least 3 characters
9
+ const SUFFIX_SUPPORTED = /^(?:(?!\.\.?(?:[/?#]|$))[^/?#]+(?:\/[^/?#]{3,})*\/?)?(?:[?#]|$)/;
8
10
  const CURRENT = './';
9
11
  const PARENT = '../';
10
12
  const QUERY = '?';
@@ -88,8 +90,10 @@ export default class BaseIRI {
88
90
  // Don't abbreviate unsupported path
89
91
  if (parentPath !== QUERY && !SUFFIX_SUPPORTED.test(suffix))
90
92
  return iri;
91
- // Omit ./ with fragment or query string
92
- if (parentPath === CURRENT && /^[^?#]/.test(suffix))
93
+ // Omit ./ with fragment or query string,
94
+ // unless the first segment has a colon and would be read as a scheme
95
+ if (parentPath === CURRENT && /^[^?#]/.test(suffix) &&
96
+ (suffix.indexOf(':') < 0 || !/^[^/?#]*:/.test(suffix)))
93
97
  return suffix;
94
98
  // Append suffix to relative parent path
95
99
  return parentPath + suffix;
@@ -439,7 +439,7 @@ function literal(value, languageOrDataType) {
439
439
  return new Literal(`"${value}"@${languageOrDataType.language.toLowerCase()}${languageOrDataType.direction ? `--${languageOrDataType.direction.toLowerCase()}` : ''}`);
440
440
  }
441
441
 
442
- // Automatically determine datatype for booleans, numbers, and dates
442
+ // Automatically determine datatype for booleans, numbers, BigInts, and dates
443
443
  let datatype = languageOrDataType ? languageOrDataType.value : '';
444
444
  if (datatype === '') {
445
445
  // Convert a boolean
@@ -458,6 +458,9 @@ function literal(value, languageOrDataType) {
458
458
  value = value > 0 ? 'INF' : '-INF';
459
459
  }
460
460
  }
461
+ // Convert a BigInt, whose decimal string is always a valid xsd:integer lexical
462
+ else if (typeof value === 'bigint')
463
+ datatype = xsd.integer;
461
464
  // Convert a valid date
462
465
  else if (value instanceof Date && !Number.isNaN(value.getTime())) {
463
466
  datatype = xsd.dateTime;
@@ -496,7 +499,8 @@ export function fromTerm(term) {
496
499
  case 'BlankNode': return blankNode(term.value);
497
500
  case 'Variable': return variable(term.value);
498
501
  case 'DefaultGraph': return DEFAULTGRAPH;
499
- case 'Literal': return literal(term.value, term.language || term.datatype);
502
+ case 'Literal': return literal(term.value, !term.language ? term.datatype :
503
+ term.direction ? { language: term.language, direction: term.direction } : term.language);
500
504
  case 'Quad': return fromQuad(term);
501
505
  default: throw new Error(`Unexpected termType: ${term.termType}`);
502
506
  }
package/src/N3Lexer.js CHANGED
@@ -17,6 +17,8 @@ const localNameEscapeReplacements = {
17
17
  '=': '=', '/': '/', '?': '?', '#': '#', '@': '@', '%': '%',
18
18
  };
19
19
  const illegalIriChars = /[\x00-\x20<>\\"\{\}\|\^\`]/;
20
+ // Characters that cannot occur in a prefixed name, not even escaped
21
+ const nonPrefixedNameChar = /[\s<>"{}|^`]/;
20
22
 
21
23
  // A valid code point is a Unicode scalar value: at most U+10FFFF and not a surrogate
22
24
  function isValidCodePoint(charCode) {
@@ -45,10 +47,10 @@ export default class N3Lexer {
45
47
  this._simpleApostropheString = /^'([^'\\\r\n]*)'(?=[^'])/;
46
48
  this._langcode = /^@([a-z]+(?:-[a-z0-9]+)*)(?=[^a-z0-9])/i;
47
49
  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<])/;
48
- 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-: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-: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-: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#()\[\]\{\}"'<>]))/;
49
- 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-: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#()\[\]\{\}"'<>])/;
50
+ 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#()\[\]\{\}"'<>]))/;
51
+ 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#()\[\]\{\}"'<>])/;
50
52
  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#()\[\]\{\}"'<>]))/;
51
- this._number = /^[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?|\d*(\.)?)\d+(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/;
53
+ this._number = /^[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/;
52
54
  this._boolean = /^(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/;
53
55
  this._atKeyword = /^@[a-z]+(?=[\s#<:])/i;
54
56
  this._keyword = /^(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/i;
@@ -542,7 +544,14 @@ export default class N3Lexer {
542
544
 
543
545
  // A prefix can start with a verb and continue with characters that are also
544
546
  // valid verb boundaries. Prefer the longer prefixed name when it is complete.
545
- if (this._prefixed.exec(input) || this._prefixed.exec(`${input} `))
547
+ if (this._prefixed.exec(input))
548
+ return null;
549
+ // Appending to the input only matters when a prefixed name could run up to
550
+ // its end, which a character that cannot occur in prefixed names rules out.
551
+ // This avoids copying the rest of the document for every such verb.
552
+ if (nonPrefixedNameChar.test(input))
553
+ return verb;
554
+ if (this._prefixed.exec(`${input} `))
546
555
  return null;
547
556
 
548
557
  // If a stream chunk ends partway through such a prefix, wait for the colon
package/src/N3Parser.js CHANGED
@@ -18,7 +18,7 @@ export default class N3Parser {
18
18
 
19
19
  // Set supported features depending on the format
20
20
  const format = (typeof options.format === 'string') ?
21
- options.format.match(/\w*$/)[0].toLowerCase() : '',
21
+ options.format.match(/(?:^|\W)(\w*)$/)[1].toLowerCase() : '',
22
22
  isTurtle = /turtle/.test(format), isTriG = /trig/.test(format),
23
23
  isNTriples = /triple/.test(format), isNQuads = /quad/.test(format),
24
24
  isN3 = this._n3Mode = /n3/.test(format),
@@ -78,8 +78,10 @@ export default class N3Parser {
78
78
  baseIRI = baseIRI.substr(0, fragmentPos);
79
79
  // Set base IRI and its components
80
80
  this._base = baseIRI;
81
+ const queryPos = baseIRI.indexOf('?');
82
+ const path = queryPos < 0 ? baseIRI : baseIRI.substr(0, queryPos);
81
83
  this._basePath = baseIRI.indexOf('/') < 0 ? baseIRI :
82
- baseIRI.replace(/[^\/?]*(?:\?.*)?$/, '');
84
+ path.substr(0, path.lastIndexOf('/') + 1);
83
85
  baseIRI = baseIRI.match(/^(?:([a-z][a-z0-9+.-]*:))?(?:\/\/[^\/]*)?/i);
84
86
  this._baseRoot = baseIRI[0];
85
87
  this._baseScheme = baseIRI[1];
@@ -100,14 +102,15 @@ export default class N3Parser {
100
102
  inverse: this._inversePredicate,
101
103
  expectOf: this._expectOf,
102
104
  blankPrefix: this._prefixes._,
103
- quantified: this._quantified,
105
+ prefixChanges: this._prefixChanges,
106
+ quantifiedChanges: this._quantifiedChanges,
104
107
  emptyFormula: this._emptyFormula,
105
108
  };
106
- // Prefix and base declarations are scoped to their formula
109
+ // Prefix and base declarations are scoped to their formula,
110
+ // so record prefix changes to undo them when the formula ends
107
111
  if (type === 'formula') {
108
- context.prefixes = this._prefixes;
109
112
  context.base = [this._base, this._basePath, this._baseRoot, this._baseScheme];
110
- this._prefixes = Object.create(this._prefixes);
113
+ this._prefixChanges = [];
111
114
  }
112
115
  this._contextStack.push(context);
113
116
  // Every new scope resets the predicate direction
@@ -120,7 +123,7 @@ export default class N3Parser {
120
123
  // Label the scope with the enclosing formula's blank node
121
124
  // (using a dot as separator, as a blank node label cannot start with it)
122
125
  this._prefixes._ = (this._graph ? `${this._graph.value}.` : '.');
123
- this._quantified = Object.create(this._quantified);
126
+ this._quantifiedChanges = [];
124
127
  }
125
128
  // A formula starts empty and must not inherit its parent's subject
126
129
  if (type === 'formula') {
@@ -147,13 +150,17 @@ export default class N3Parser {
147
150
  if (this._n3Mode) {
148
151
  this._inversePredicate = context.inverse;
149
152
  this._expectOf = context.expectOf;
150
- if (type === 'formula') {
151
- this._prefixes = context.prefixes;
153
+ if (type === 'formula')
152
154
  [this._base, this._basePath, this._baseRoot, this._baseScheme] = context.base;
155
+ if (this._prefixChanges !== context.prefixChanges) {
156
+ undoChanges(this._prefixes, this._prefixChanges);
157
+ this._prefixChanges = context.prefixChanges;
158
+ }
159
+ this._prefixes._ = context.blankPrefix;
160
+ if (this._quantifiedChanges !== context.quantifiedChanges) {
161
+ undoChanges(this._quantified, this._quantifiedChanges);
162
+ this._quantifiedChanges = context.quantifiedChanges;
153
163
  }
154
- else
155
- this._prefixes._ = context.blankPrefix;
156
- this._quantified = context.quantified;
157
164
  this._emptyFormula = context.emptyFormula;
158
165
  }
159
166
  }
@@ -1055,6 +1062,8 @@ export default class N3Parser {
1055
1062
  if (token.type !== 'IRI')
1056
1063
  return this._error(`Expected IRI to follow prefix "${this._prefix}:"`, token);
1057
1064
  const prefixNode = this._readEntity(token);
1065
+ if (this._prefixChanges !== null)
1066
+ this._prefixChanges.push(this._prefix, this._prefixes[this._prefix]);
1058
1067
  this._prefixes[this._prefix] = prefixNode.value;
1059
1068
  this._prefixCallback(this._prefix, prefixNode);
1060
1069
  return this._readDeclarationPunctuation;
@@ -1133,8 +1142,11 @@ export default class N3Parser {
1133
1142
  return this._error(`Unexpected ${token.type}`, token);
1134
1143
  }
1135
1144
  // Without explicit quantifiers, map entities to a quantified entity
1136
- if (!this._explicitQuantifiers)
1145
+ if (!this._explicitQuantifiers) {
1146
+ if (this._quantifiedChanges !== null)
1147
+ this._quantifiedChanges.push(entity.id, this._quantified[entity.id]);
1137
1148
  this._quantified[entity.id] = this._factory[this._quantifier](this._factory.blankNode().value);
1149
+ }
1138
1150
  // With explicit quantifiers, output the reified quantifier
1139
1151
  else {
1140
1152
  // If this is the first item, start a new quantifier list
@@ -1521,7 +1533,10 @@ export default class N3Parser {
1521
1533
  // Resolve relative fragment IRIs against the base IRI
1522
1534
  case '#': return this._base + iri;
1523
1535
  // Resolve relative query string IRIs by replacing the query string
1524
- case '?': return this._base.replace(/(?:\?.*)?$/, iri);
1536
+ case '?': {
1537
+ const queryPos = this._base.indexOf('?');
1538
+ return (queryPos < 0 ? this._base : this._base.substr(0, queryPos)) + iri;
1539
+ }
1525
1540
  // Resolve root-relative IRIs at the root of the base IRI
1526
1541
  case '/':
1527
1542
  // Resolve scheme-relative IRIs to the scheme
@@ -1632,6 +1647,9 @@ export default class N3Parser {
1632
1647
  this._inversePredicate = false;
1633
1648
  this._expectOf = false;
1634
1649
  this._quantified = Object.create(null);
1650
+ // Changes to prefixes and quantifiers in nested scopes, to undo when leaving them
1651
+ this._prefixChanges = null;
1652
+ this._quantifiedChanges = null;
1635
1653
  this._emptyFormula = false;
1636
1654
 
1637
1655
  let readToken = token => {
@@ -1694,6 +1712,16 @@ export default class N3Parser {
1694
1712
  }
1695
1713
  }
1696
1714
 
1715
+ // ### `undoChanges` restores the entries of a map from a list of key and previous value pairs
1716
+ function undoChanges(map, changes) {
1717
+ for (let i = changes.length - 2; i >= 0; i -= 2) {
1718
+ if (changes[i + 1] === undefined)
1719
+ delete map[changes[i]];
1720
+ else
1721
+ map[changes[i]] = changes[i + 1];
1722
+ }
1723
+ }
1724
+
1697
1725
  // The empty function
1698
1726
  function noop() {}
1699
1727
 
package/src/N3Store.js CHANGED
@@ -30,8 +30,10 @@ function merge(target, source, depth = 4) {
30
30
  size++;
31
31
  target[key] = depth === 0 ? null : merge(Object.create(null), source[key], depth - 1);
32
32
  }
33
+ // Merge into the existing object in place,
34
+ // as graph objects are frozen and cannot be reassigned
33
35
  else if (depth !== 0)
34
- target[key] = merge(target[key], source[key], depth - 1);
36
+ merge(target[key], source[key], depth - 1);
35
37
  }
36
38
  // Depth 2 is the level of the `subjects`, `predicates`, and `objects` indexes.
37
39
  if (depth <= 2)
@@ -1010,7 +1012,7 @@ export default class N3Store {
1010
1012
  if (!first)
1011
1013
  malformed = onError(current, 'has no list head');
1012
1014
  else
1013
- items.unshift(first.object);
1015
+ items.push(first.object);
1014
1016
  current = parent && parent.subject;
1015
1017
  }
1016
1018
 
@@ -1018,12 +1020,16 @@ export default class N3Store {
1018
1020
  if (malformed)
1019
1021
  remove = false;
1020
1022
  else {
1023
+ // Items were collected from the tail to the head
1024
+ items.reverse();
1021
1025
  // Store the list under the value of its head
1022
1026
  if (head)
1023
1027
  lists[head[headPos].value] = items;
1024
1028
  // Leave lists with extra arcs fully intact; otherwise queue this list once.
1025
- if (remove && !extraArcs)
1026
- toRemove.push(...listQuads);
1029
+ if (remove && !extraArcs) {
1030
+ for (let i = 0; i < listQuads.length; i++)
1031
+ toRemove.push(listQuads[i]);
1032
+ }
1027
1033
  }
1028
1034
  });
1029
1035