n3 3.0.0-alpha.3 → 3.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/N3Parser.js CHANGED
@@ -54,10 +54,17 @@ class N3Parser {
54
54
  return null;
55
55
  };
56
56
  this._blankNodePrefix = typeof options.blankNodePrefix !== 'string' ? '' : options.blankNodePrefix.replace(/^(?!_:)/, '_:');
57
+ // Map the token types of additional directives (@name and NAME) to their names
58
+ this._directives = null;
59
+ if (options.directives) {
60
+ this._directives = Object.create(null);
61
+ for (const name of options.directives) this._directives[`@${name.toLowerCase()}`] = this._directives[name.toUpperCase()] = name;
62
+ }
57
63
  this._lexer = options.lexer || new _N3Lexer.default({
58
64
  lineMode: isLineMode,
59
65
  n3: isN3,
60
- isImpliedBy: this._isImpliedBy
66
+ isImpliedBy: this._isImpliedBy,
67
+ directives: options.directives
61
68
  });
62
69
  // Disable explicit quantifiers by default
63
70
  this._explicitQuantifiers = !!options.explicitQuantifiers;
@@ -66,6 +73,9 @@ class N3Parser {
66
73
  // Disable parsing of unsupported versions by default
67
74
  this._parseUnsupportedVersions = !!options.parseUnsupportedVersions;
68
75
  this._version = options.version;
76
+ // Maximum nesting depth of triple terms (default 1024; Infinity disables the limit)
77
+ const maxTripleTermDepth = options.maxTripleTermDepth;
78
+ this._maxTripleTermDepth = maxTripleTermDepth === Infinity || Number.isInteger(maxTripleTermDepth) && maxTripleTermDepth >= 0 ? maxTripleTermDepth : 1024;
69
79
  }
70
80
 
71
81
  // ## Static class methods
@@ -91,12 +101,29 @@ class N3Parser {
91
101
  const queryPos = baseIRI.indexOf('?');
92
102
  const path = queryPos < 0 ? baseIRI : baseIRI.substr(0, queryPos);
93
103
  this._basePath = baseIRI.indexOf('/') < 0 ? baseIRI : path.substr(0, path.lastIndexOf('/') + 1);
94
- baseIRI = baseIRI.match(/^(?:([a-z][a-z0-9+.-]*:))?(?:\/\/[^\/]*)?/i);
104
+ const base = baseIRI;
105
+ baseIRI = baseIRI.match(/^(?:([a-z][a-z0-9+.-]*:))?(\/\/[^\/?#]*)?/i);
95
106
  this._baseRoot = baseIRI[0];
96
107
  this._baseScheme = baseIRI[1];
108
+ // If the base has an authority but an empty path,
109
+ // relative IRIs merge under the path '/' (RFC 3986 §5.2.3)
110
+ if (baseIRI[2] !== undefined && (base.length === this._baseRoot.length || base[this._baseRoot.length] === '?')) this._basePath = `${this._baseRoot}/`;
97
111
  }
98
112
  }
99
113
 
114
+ // ### `_enterTripleTerm` stores the current parsing context when entering a triple term,
115
+ // failing if triple terms nest deeper than the maximum depth
116
+ _enterTripleTerm(type, subject, predicate, token) {
117
+ if (this._tripleTermDepth >= this._maxTripleTermDepth) {
118
+ this._error(`Triple terms nested deeper than ${this._maxTripleTermDepth} level${this._maxTripleTermDepth === 1 ? '' : 's'}`, token);
119
+ return false;
120
+ }
121
+ this._tripleTermDepth++;
122
+ this._saveContext(type, this._graph, subject, predicate, null);
123
+ this._graph = null;
124
+ return true;
125
+ }
126
+
100
127
  // ### `_saveContext` stores the current parsing context
101
128
  // when entering a new scope (list, blank node, formula)
102
129
  _saveContext(type, graph, subject, predicate, object) {
@@ -219,8 +246,13 @@ class N3Parser {
219
246
  }
220
247
  case 'GRAPH':
221
248
  if (this._supportsNamedGraphs) return this._readNamedGraphLabel;
222
- // Otherwise, the next token must be a subject
249
+ // Otherwise, the next token must be a subject, unless it is an additional directive
223
250
  default:
251
+ if (this._directives !== null && token.type in this._directives && this._graph === null) {
252
+ this._sparqlStyle = token.type[0] !== '@';
253
+ this._directiveCallback(this._directives[token.type]);
254
+ return this._readDeclarationPunctuation;
255
+ }
224
256
  return this._readSubject(token);
225
257
  }
226
258
  }
@@ -339,13 +371,9 @@ class N3Parser {
339
371
  }
340
372
  case '<<(':
341
373
  if (!this._n3Mode) return this._error('Disallowed triple term as subject', token);
342
- this._saveContext('<<(', this._graph, null, null, null);
343
- this._graph = null;
344
- return this._readSubject;
374
+ return this._enterTripleTerm('<<(', null, null, token) && this._readSubject;
345
375
  case '<<':
346
- this._saveContext('<<', this._graph, null, null, null);
347
- this._graph = null;
348
- return this._readSubject;
376
+ return this._enterTripleTerm('<<', null, null, token) && this._readSubject;
349
377
  default:
350
378
  // Read the subject entity
351
379
  if ((this._subject = this._readEntity(token)) === undefined) return;
@@ -468,13 +496,9 @@ class N3Parser {
468
496
  this._saveContext('formula', this._graph, this._subject, this._predicate, this._graph = this._factory.blankNode());
469
497
  return this._readInFormulaContext;
470
498
  case '<<(':
471
- this._saveContext('<<(', this._graph, this._subject, this._predicate, null);
472
- this._graph = null;
473
- return this._readSubject;
499
+ return this._enterTripleTerm('<<(', this._subject, this._predicate, token) && this._readSubject;
474
500
  case '<<':
475
- this._saveContext('<<', this._graph, this._subject, this._predicate, null);
476
- this._graph = null;
477
- return this._readSubject;
501
+ return this._enterTripleTerm('<<', this._subject, this._predicate, token) && this._readSubject;
478
502
  default:
479
503
  // Read the object entity
480
504
  if ((this._object = this._readEntity(token)) === undefined) return;
@@ -669,13 +693,8 @@ class N3Parser {
669
693
  this._subject = null;
670
694
  return this._readInFormulaContext;
671
695
  case '<<(':
672
- this._saveContext('<<(', this._graph, null, null, null);
673
- this._graph = null;
674
- next = this._readSubject;
675
- break;
676
696
  case '<<':
677
- this._saveContext('<<', this._graph, null, null, null);
678
- this._graph = null;
697
+ if (!this._enterTripleTerm(token.type, null, null, token)) return;
679
698
  next = this._readSubject;
680
699
  break;
681
700
  default:
@@ -1167,6 +1186,7 @@ class N3Parser {
1167
1186
  // Read the quad and restore the previous context
1168
1187
  const quad = this._createQuad(this._subject, this._predicate, this._object, this._graph, this._inversePredicate);
1169
1188
  this._restoreContext('<<(', token);
1189
+ this._tripleTermDepth--;
1170
1190
 
1171
1191
  // If we're in a list, continue processing that list
1172
1192
  const stack = this._contextStack,
@@ -1202,6 +1222,7 @@ class N3Parser {
1202
1222
  this._tripleTerm = null;
1203
1223
  const reifier = this._readTripleTerm();
1204
1224
  this._restoreContext('<<', token);
1225
+ this._tripleTermDepth--;
1205
1226
 
1206
1227
  // // If we're in a list, continue processing that list
1207
1228
  const stack = this._contextStack,
@@ -1426,8 +1447,11 @@ class N3Parser {
1426
1447
  }
1427
1448
  // Resolve root-relative IRIs at the root of the base IRI
1428
1449
  case '/':
1429
- // Resolve scheme-relative IRIs to the scheme
1430
- return (iri[1] === '/' ? this._baseScheme : this._baseRoot) + this._removeDotSegments(iri);
1450
+ // Resolve scheme-relative IRIs to the scheme,
1451
+ // keeping the reference's authority intact (RFC 3986 §5.2.4)
1452
+ if (iri[1] !== '/') return this._baseRoot + this._removeDotSegments(iri);
1453
+ // Without a base scheme, a leading ':' marks where the authority starts
1454
+ return this._baseScheme ? this._removeDotSegments(this._baseScheme + iri) : this._removeDotSegments(`:${iri}`).substr(1);
1431
1455
  // Resolve all other IRIs at the base IRI's path
1432
1456
  default:
1433
1457
  // Relative IRIs cannot contain a colon in the first path segment
@@ -1454,8 +1478,8 @@ class N3Parser {
1454
1478
  if (pathStart < 0) {
1455
1479
  // Skip two slashes before the authority
1456
1480
  if (iri[++i] === '/' && iri[++i] === '/')
1457
- // Skip to slash after the authority
1458
- while ((pathStart = i + 1) < length && iri[pathStart] !== '/') i = pathStart;
1481
+ // Skip to the end of the authority
1482
+ while ((pathStart = i + 1) < length && iri[pathStart] !== '/' && iri[pathStart] !== '?' && iri[pathStart] !== '#') i = pathStart;
1459
1483
  }
1460
1484
  break;
1461
1485
  // Don't modify a query string or fragment
@@ -1503,12 +1527,13 @@ class N3Parser {
1503
1527
  parse(input, quadCallback, prefixCallback, versionCallback) {
1504
1528
  // The second parameter also accepts an object of named callbacks.
1505
1529
  // As a second and third parameter it still accepts a separate quadCallback and prefixCallback for backward compatibility as well
1506
- let onQuad, onPrefix, onComment, onVersion, onToken, onTokenEnd;
1507
- if (quadCallback && (quadCallback.onQuad || quadCallback.onPrefix || quadCallback.onComment || quadCallback.onVersion || quadCallback.onToken || quadCallback.onTokenEnd)) {
1530
+ let onQuad, onPrefix, onComment, onVersion, onDirective, onToken, onTokenEnd;
1531
+ if (quadCallback && (quadCallback.onQuad || quadCallback.onPrefix || quadCallback.onComment || quadCallback.onVersion || quadCallback.onDirective || quadCallback.onToken || quadCallback.onTokenEnd)) {
1508
1532
  onQuad = quadCallback.onQuad;
1509
1533
  onPrefix = quadCallback.onPrefix;
1510
1534
  onComment = quadCallback.onComment;
1511
1535
  onVersion = quadCallback.onVersion;
1536
+ onDirective = quadCallback.onDirective;
1512
1537
  onToken = quadCallback.onToken;
1513
1538
  onTokenEnd = quadCallback.onTokenEnd;
1514
1539
  } else {
@@ -1526,6 +1551,7 @@ class N3Parser {
1526
1551
  if (this._n3Mode && this._implicitEmptyPrefix && this._base) this._prefixes[''] = this._resolveIRI('#');
1527
1552
  this._prefixCallback = onPrefix || noop;
1528
1553
  this._versionCallback = onVersion || noop;
1554
+ this._directiveCallback = onDirective || noop;
1529
1555
  this._inversePredicate = false;
1530
1556
  this._expectOf = false;
1531
1557
  this._quantified = Object.create(null);
@@ -1533,6 +1559,7 @@ class N3Parser {
1533
1559
  this._prefixChanges = null;
1534
1560
  this._quantifiedChanges = null;
1535
1561
  this._emptyFormula = false;
1562
+ this._tripleTermDepth = 0;
1536
1563
  let readToken = token => {
1537
1564
  return this._readCallback = this._readCallback(token);
1538
1565
  };
package/lib/N3Reasoner.js CHANGED
@@ -154,7 +154,7 @@ class N3Reasoner {
154
154
  premise,
155
155
  conclusion
156
156
  }) {
157
- const varMapping = {};
157
+ const varMapping = Object.create(null);
158
158
  const toId = value => value.termType === 'Variable' ?
159
159
  // If the term is a variable, then create an empty object that values can be placed into
160
160
  varMapping[value.value] = varMapping[value.value] || {} :
package/lib/N3Store.js CHANGED
@@ -116,6 +116,31 @@ function difference(s1, s2, depth = 4) {
116
116
  return target;
117
117
  }
118
118
 
119
+ // Returns the key of a term in the entity index.
120
+ // Keys mark the term type by their first character, so the IRI of a named node
121
+ // that starts with such a marker (as relative IRIs can) is wrapped in < and >.
122
+ const markedIRI = /^[?_"[.<]/;
123
+ function entityKey(term) {
124
+ // Strings are term ids, which only need wrapping when they are IRIs starting with <
125
+ if (typeof term === 'string') return term.charCodeAt(0) !== 0x3C || term === '<>' ? term : `<${term}>`;
126
+ // IDs of IRIs usually start with a lowercase scheme letter, which never marks a term type
127
+ const id = (0, _N3DataFactory.termToId)(term);
128
+ if (id.charCodeAt(0) >= 0x61 || !term) return id;
129
+ // Keys of quoted triples are built from the keys of their components
130
+ if (term.termType === 'Quad') return JSON.stringify(quadKeyParts(term));
131
+ return term.termType !== 'NamedNode' || !markedIRI.test(term.value) ? id : `<${term.value}>`;
132
+ }
133
+
134
+ // Returns the keys of the components of a quad, nested like the parts of its internal id
135
+ function quadKeyParts(quad) {
136
+ const parts = [nestedKey(quad.subject), nestedKey(quad.predicate), nestedKey(quad.object)];
137
+ if (quad.graph && !(0, _N3Util.isDefaultGraph)(quad.graph)) parts.push(nestedKey(quad.graph));
138
+ return parts;
139
+ }
140
+ function nestedKey(term) {
141
+ return term.termType === 'Quad' ? quadKeyParts(term) : entityKey(term);
142
+ }
143
+
119
144
  // ## Constructor
120
145
  class N3EntityIndex {
121
146
  constructor(options = {}) {
@@ -132,6 +157,9 @@ class N3EntityIndex {
132
157
  this._factory = options.factory || _N3DataFactory.default;
133
158
  }
134
159
  _termFromId(id) {
160
+ // A key in < and > is a named node
161
+ if (id[0] === '<') return this._factory.namedNode(id.substring(1, id.length - 1));
162
+ // A key starting with . is a quoted triple
135
163
  if (id[0] === '.') {
136
164
  const entities = this._entities;
137
165
  const terms = id.split('.');
@@ -148,12 +176,25 @@ class N3EntityIndex {
148
176
  let g;
149
177
  return s && p && o && ((0, _N3Util.isDefaultGraph)(term.graph) || (g = this._termToNumericId(term.graph))) && this._ids[g ? `.${s}.${p}.${o}.${g}` : `.${s}.${p}.${o}`];
150
178
  }
151
- return this._ids[(0, _N3DataFactory.termToId)(term)];
179
+ return typeof term === 'string' ? this._stringToNumericId(term) : this._ids[entityKey(term)];
180
+ }
181
+
182
+ // Returns the numeric id of a term given as a string id
183
+ _stringToNumericId(term) {
184
+ // The string is the key unless it is an IRI starting with <. Read the first character
185
+ // from the stored key after a match, which is cheaper than from a concatenated string.
186
+ const id = this._ids[term];
187
+ if (id ? this._entities[id].charCodeAt(0) !== 0x3C : term.charCodeAt(0) !== 0x3C) return id;
188
+ return term === '<>' ? id : this._ids[`<${term}>`];
152
189
  }
153
190
  _termToNewNumericId(term) {
191
+ if (typeof term === 'string') return this._stringToNumericId(term) || this._addEntity(entityKey(term));
154
192
  // This assumes that no graph term is present - we may wish to error if there is one
155
- const str = term && term.termType === 'Quad' ? `.${this._termToNewNumericId(term.subject)}.${this._termToNewNumericId(term.predicate)}.${this._termToNewNumericId(term.object)}${(0, _N3Util.isDefaultGraph)(term.graph) ? '' : `.${this._termToNewNumericId(term.graph)}`}` : (0, _N3DataFactory.termToId)(term);
156
- return this._ids[str] || (this._ids[this._entities[++this._id] = str] = this._id);
193
+ const str = term && term.termType === 'Quad' ? `.${this._termToNewNumericId(term.subject)}.${this._termToNewNumericId(term.predicate)}.${this._termToNewNumericId(term.object)}${(0, _N3Util.isDefaultGraph)(term.graph) ? '' : `.${this._termToNewNumericId(term.graph)}`}` : entityKey(term);
194
+ return this._ids[str] || this._addEntity(str);
195
+ }
196
+ _addEntity(key) {
197
+ return this._ids[this._entities[++this._id] = key] = this._id;
157
198
  }
158
199
  createBlankNode(suggestedName) {
159
200
  let name, index;
@@ -1305,7 +1346,7 @@ function intersectMatchPatterns(left, right) {
1305
1346
  for (let i = 0; i < 4; i++) {
1306
1347
  const leftTerm = left[i],
1307
1348
  rightTerm = right[i];
1308
- if (leftTerm === null || leftTerm === undefined) result[i] = rightTerm;else if (rightTerm === null || rightTerm === undefined || (0, _N3DataFactory.termToId)(leftTerm) === (0, _N3DataFactory.termToId)(rightTerm)) result[i] = leftTerm;else return false;
1349
+ if (leftTerm === null || leftTerm === undefined) result[i] = rightTerm;else if (rightTerm === null || rightTerm === undefined || entityKey(leftTerm) === entityKey(rightTerm)) result[i] = leftTerm;else return false;
1309
1350
  }
1310
1351
  return result;
1311
1352
  }
@@ -1368,7 +1409,7 @@ class DatasetCoreAndReadableStream extends _readableStream.Readable {
1368
1409
  object,
1369
1410
  graph
1370
1411
  } = this;
1371
- return !this._matchesNothing && (subject === null || subject === undefined || (0, _N3DataFactory.termToId)(subject) === (0, _N3DataFactory.termToId)(quad.subject)) && (predicate === null || predicate === undefined || (0, _N3DataFactory.termToId)(predicate) === (0, _N3DataFactory.termToId)(quad.predicate)) && (object === null || object === undefined || (0, _N3DataFactory.termToId)(object) === (0, _N3DataFactory.termToId)(quad.object)) && (graph === null || graph === undefined || (0, _N3DataFactory.termToId)(graph) === (0, _N3DataFactory.termToId)(quad.graph));
1412
+ return !this._matchesNothing && (subject === null || subject === undefined || entityKey(subject) === entityKey(quad.subject)) && (predicate === null || predicate === undefined || entityKey(predicate) === entityKey(quad.predicate)) && (object === null || object === undefined || entityKey(object) === entityKey(quad.object)) && (graph === null || graph === undefined || entityKey(graph) === entityKey(quad.graph));
1372
1413
  }
1373
1414
 
1374
1415
  // ### `_assertMatchesPattern` rejects a Quad outside this view.
@@ -28,6 +28,10 @@ class N3StreamParser extends _readableStream.Transform {
28
28
  // Emit prefixes through the `prefix` event
29
29
  onPrefix: (prefix, uri) => {
30
30
  this.emit('prefix', prefix, uri);
31
+ },
32
+ // Emit additional directives through the `directive` event
33
+ onDirective: name => {
34
+ this.emit('directive', name);
31
35
  }
32
36
  };
33
37
  if (options && options.comments) callbacks.onComment = comment => {
package/lib/N3Writer.js CHANGED
@@ -18,10 +18,19 @@ const {
18
18
  rdf,
19
19
  xsd
20
20
  } = _IRIs.default;
21
+ const {
22
+ hasOwnProperty
23
+ } = Object.prototype;
21
24
 
22
- // Characters in literals that require escaping
25
+ // Characters that require escaping in Turtle, TriG, and N3,
26
+ // where characters outside the Basic Multilingual Plane are escaped as well
23
27
  const escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/,
24
28
  escapeAll = /["\\\t\n\r\b\f\u0000-\u0019]|[\ud800-\udbff][\udc00-\udfff]/g,
29
+ // Characters that require escaping in canonical N-Triples and N-Quads:
30
+ // U+0000–U+001F, `"`, `\`, U+007F, U+FFFE, and U+FFFF.
31
+ // A negated class is as fast to test as a shorter class listing characters.
32
+ canonicalEscape = /[^ !#-\[\]-~\u0080-\uFFFD]/,
33
+ canonicalEscapeAll = /[^ !#-\[\]-~\u0080-\uFFFD]/g,
25
34
  escapedCharacters = {
26
35
  '\\': '\\\\',
27
36
  '"': '\\"',
@@ -75,8 +84,11 @@ class N3Writer {
75
84
  this._subject = null;
76
85
  if (!/triple|quad/i.test(options.format)) {
77
86
  this._lineMode = false;
87
+ this._escape = escape, this._escapeAll = escapeAll, this._characterReplacer = characterReplacer;
78
88
  this._graph = DEFAULTGRAPH;
79
89
  this._prefixIRIs = Object.create(null);
90
+ // Escaped prefix IRIs and names for the prefix matcher, computed once per prefix
91
+ this._prefixPatterns = Object.create(null);
80
92
  if (options.baseIRI) {
81
93
  this._baseIri = new _BaseIRI.default(options.baseIRI);
82
94
  if (options.writeBase !== false) this._write(`@base <${options.baseIRI}>.\n`);
@@ -85,6 +97,9 @@ class N3Writer {
85
97
  } else {
86
98
  this._lineMode = true;
87
99
  this._writeQuad = this._writeQuadLine;
100
+ // N-Triples and N-Quads are written in their canonical form
101
+ this._escape = canonicalEscape, this._escapeAll = canonicalEscapeAll;
102
+ this._characterReplacer = canonicalCharacterReplacer;
88
103
  }
89
104
  }
90
105
 
@@ -163,7 +178,7 @@ class N3Writer {
163
178
  // A blank node or list is represented as-is
164
179
  if (entity.termType !== 'NamedNode') {
165
180
  // If it is a list head, pretty-print it
166
- if (this._lists && entity.value in this._lists) entity = this.list(this._lists[entity.value]);
181
+ if (this._lists && hasOwnProperty.call(this._lists, entity.value)) entity = this.list(this._lists[entity.value]);
167
182
  // Terms from this library already hold their serialization as id
168
183
  if (entity instanceof _N3DataFactory.Term) return entity.id;
169
184
  return entity.termType === 'Variable' ? `?${entity.value}` : `_:${entity.value}`;
@@ -174,17 +189,17 @@ class N3Writer {
174
189
  iri = this._baseIri.toRelative(iri);
175
190
  }
176
191
  // Escape special characters
177
- if (escape.test(iri)) iri = iri.replace(escapeAll, characterReplacer);
192
+ if (this._escape.test(iri)) iri = iri.replace(this._escapeAll, this._characterReplacer);
178
193
  // Try to represent the IRI as prefixed name, unless no prefixes were added
179
- const prefixMatch = this._hasPrefixes ? this._prefixRegex.exec(iri) : null;
180
- return !prefixMatch ? `<${iri}>` : !prefixMatch[1] ? iri : this._prefixIRIs[prefixMatch[1]] + prefixMatch[2];
194
+ const prefixMatch = this._hasPrefixes ? (this._prefixRegex || this._createPrefixRegex()).exec(iri) : null;
195
+ return !prefixMatch ? `<${iri}>` : this._prefixIRIs[prefixMatch[1]] + prefixMatch[2];
181
196
  }
182
197
 
183
198
  // ### `_encodeLiteral` represents a literal
184
199
  _encodeLiteral(literal) {
185
200
  // Escape special characters
186
201
  let value = literal.value;
187
- if (escape.test(value)) value = value.replace(escapeAll, characterReplacer);
202
+ if (this._escape.test(value)) value = value.replace(this._escapeAll, this._characterReplacer);
188
203
 
189
204
  // Write a language-tagged literal
190
205
  const language = literal.language;
@@ -254,7 +269,9 @@ class N3Writer {
254
269
  object,
255
270
  graph
256
271
  }) {
257
- return `<<(${this._encodeSubject(subject)} ${this._encodePredicate(predicate)} ${this._encodeObject(object)}${(0, _N3Util.isDefaultGraph)(graph) ? '' : ` ${this._encodeIriOrBlank(graph)}`})>>`;
272
+ // Canonical N-Triples and N-Quads put spaces inside the delimiters
273
+ const space = this._lineMode ? ' ' : '';
274
+ return `<<(${space}${this._encodeSubject(subject)} ${this._encodePredicate(predicate)} ${this._encodeObject(object)}${(0, _N3Util.isDefaultGraph)(graph) ? '' : ` ${this._encodeIriOrBlank(graph)}`}${space})>>`;
258
275
  }
259
276
 
260
277
  // ### `_blockedWrite` replaces `_write` after the writer has been closed
@@ -302,24 +319,26 @@ class N3Writer {
302
319
  }
303
320
  // Store and write the prefix
304
321
  this._prefixIRIs[iri] = prefix += ':';
322
+ this._prefixPatterns[iri] = (0, _Util.escapeRegex)(iri);
305
323
  this._write(`@prefix ${prefix} <${iri}>.\n`);
306
324
  }
307
- // Recreate the prefix matcher
325
+ // Recreate the prefix matcher when it is next needed, so that adding
326
+ // prefixes one by one does not rebuild it for every prefix
308
327
  if (hasPrefixes) {
309
328
  this._hasPrefixes = true;
310
- let IRIlist = '',
311
- prefixList = '';
312
- for (const prefixIRI in this._prefixIRIs) {
313
- IRIlist += IRIlist ? `|${prefixIRI}` : prefixIRI;
314
- prefixList += (prefixList ? '|' : '') + this._prefixIRIs[prefixIRI];
315
- }
316
- IRIlist = (0, _Util.escapeRegex)(IRIlist, /[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
317
- this._prefixRegex = new RegExp(`^(?:${prefixList})[^\/]*$|` + `^(${IRIlist})([_a-zA-Z0-9](?:\\.?[\\-_a-zA-Z0-9])*)$`);
329
+ this._prefixRegex = null;
318
330
  }
319
331
  // End a prefix block with a newline
320
332
  this._write(hasPrefixes ? '\n' : '', done);
321
333
  }
322
334
 
335
+ // ### `_createPrefixRegex` creates the matcher for the current prefixes
336
+ _createPrefixRegex() {
337
+ let IRIlist = '';
338
+ for (const prefixIRI in this._prefixPatterns) IRIlist += IRIlist ? `|${this._prefixPatterns[prefixIRI]}` : this._prefixPatterns[prefixIRI];
339
+ return this._prefixRegex = new RegExp(`^(${IRIlist})([_a-zA-Z0-9](?:\\.?[\\-_a-zA-Z0-9])*)$`);
340
+ }
341
+
323
342
  // ### `blank` creates a blank node with the given content
324
343
  blank(predicate, object) {
325
344
  let children = predicate,
@@ -409,4 +428,15 @@ function characterReplacer(character) {
409
428
  }
410
429
  }
411
430
  return result;
431
+ }
432
+
433
+ // Replaces a character by its canonical N-Triples escape:
434
+ // a short escape where one exists, and an upper-case `\uXXXX` escape otherwise
435
+ function canonicalCharacterReplacer(character) {
436
+ let result = escapedCharacters[character];
437
+ if (result === undefined) {
438
+ result = character.charCodeAt(0).toString(16).toUpperCase();
439
+ result = '\\u0000'.substr(0, 6 - result.length) + result;
440
+ }
441
+ return result;
412
442
  }
package/lib/Util.js CHANGED
@@ -5,5 +5,5 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.escapeRegex = escapeRegex;
7
7
  function escapeRegex(regex) {
8
- return regex.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
8
+ return regex.replace(/[.*+?^${}()|[\]\\/-]/g, '\\$&');
9
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "3.0.0-alpha.3",
3
+ "version": "3.0.0-alpha.5",
4
4
  "description": "Lightning fast, asynchronous, streaming Turtle / N3 / RDF library.",
5
5
  "author": "Ruben Verborgh <ruben.verborgh@gmail.com>",
6
6
  "keywords": [
@@ -60,44 +60,17 @@
60
60
  "test": "jest",
61
61
  "lint": "eslint src perf test spec",
62
62
  "prepare": "npm run build",
63
- "spec": "npm run spec-1-1 && npm run spec-1-2 && npm run spec-n3",
64
- "spec-1-1": "npm run spec-1-1-turtle && npm run spec-1-1-ntriples && npm run spec-1-1-nquads && npm run spec-1-1-trig",
63
+ "spec": "node spec/run.js",
65
64
  "spec-1-1-earl": "npm run spec-1-1-earl-turtle && npm run spec-1-1-earl-ntriples && npm run spec-1-1-earl-nquads && npm run spec-1-1-earl-trig",
66
- "spec-1-1-ntriples": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-triples/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/",
67
- "spec-1-1-nquads": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-quads/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/",
68
- "spec-1-1-turtle": "rdf-test-suite spec/parser.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)\"",
69
- "spec-1-1-trig": "rdf-test-suite spec/parser.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)\"",
70
65
  "spec-1-1-earl-ntriples": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-triples/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-ntriples.ttl",
71
66
  "spec-1-1-earl-nquads": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-quads/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-nquads.ttl",
72
67
  "spec-1-1-earl-turtle": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-turtle/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-turtle.ttl",
73
68
  "spec-1-1-earl-trig": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-trig/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-trig.ttl",
74
- "spec-1-2": "npm run spec-1-2-turtle && npm run spec-1-2-ntriples && npm run spec-1-2-nquads && npm run spec-1-2-trig && npm run spec-1-2-turtle-eval && npm run spec-1-2-trig-eval",
75
69
  "spec-1-2-earl": "npm run spec-1-2-earl-turtle && npm run spec-1-2-earl-ntriples && npm run spec-1-2-earl-nquads && npm run spec-1-2-earl-trig",
76
- "spec-1-2-ntriples": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-n-triples/syntax/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/",
77
- "spec-1-2-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/",
78
- "spec-1-2-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/",
79
- "spec-1-2-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/",
80
- "spec-1-2-turtle-eval": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-turtle/eval/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/",
81
- "spec-1-2-trig-eval": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-trig/eval/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/",
82
70
  "spec-1-2-earl-ntriples": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-n-triples/syntax/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-ntriples.ttl",
83
71
  "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
72
  "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
73
  "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/",
97
- "spec-n3": "npm run spec-n3-parser && npm run spec-n3-extended",
98
- "spec-n3-fetch": "sh scripts/fetch-n3-tests.sh",
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/'",
100
- "spec-n3-extended": "npm run spec-n3-fetch && rdf-test-suite spec/parser.js https://w3c-cg.github.io/N3/tests/N3Tests/manifest-extended.ttl -i '{ \"format\": \"text/n3\" }' -m 'https://w3c-cg.github.io/N3/tests/~.n3-tests/tests/'",
101
74
  "spec-clean": "rm -rf .rdf-test-suite-cache/ .n3-tests/",
102
75
  "docs": "cd src && docco *.js -o ../docs && cd .."
103
76
  },