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/src/N3Parser.js CHANGED
@@ -47,7 +47,16 @@ export default class N3Parser {
47
47
  this._resolveRelativeIRI = iri => { return null; };
48
48
  this._blankNodePrefix = typeof options.blankNodePrefix !== 'string' ? '' :
49
49
  options.blankNodePrefix.replace(/^(?!_:)/, '_:');
50
- this._lexer = options.lexer || new N3Lexer({ lineMode: isLineMode, n3: isN3, isImpliedBy: this._isImpliedBy });
50
+ // Map the token types of additional directives (@name and NAME) to their names
51
+ this._directives = null;
52
+ if (options.directives) {
53
+ this._directives = Object.create(null);
54
+ for (const name of options.directives)
55
+ this._directives[`@${name.toLowerCase()}`] = this._directives[name.toUpperCase()] = name;
56
+ }
57
+ this._lexer = options.lexer || new N3Lexer({
58
+ lineMode: isLineMode, n3: isN3, isImpliedBy: this._isImpliedBy, directives: options.directives,
59
+ });
51
60
  // Disable explicit quantifiers by default
52
61
  this._explicitQuantifiers = !!options.explicitQuantifiers;
53
62
  // Scope blank node labels to formulas only, unless explicitly disabled
@@ -55,6 +64,10 @@ export default class N3Parser {
55
64
  // Disable parsing of unsupported versions by default
56
65
  this._parseUnsupportedVersions = !!options.parseUnsupportedVersions;
57
66
  this._version = options.version;
67
+ // Maximum nesting depth of triple terms (default 1024; Infinity disables the limit)
68
+ const maxTripleTermDepth = options.maxTripleTermDepth;
69
+ this._maxTripleTermDepth = maxTripleTermDepth === Infinity ||
70
+ Number.isInteger(maxTripleTermDepth) && maxTripleTermDepth >= 0 ? maxTripleTermDepth : 1024;
58
71
  }
59
72
 
60
73
  // ## Static class methods
@@ -83,12 +96,30 @@ export default class N3Parser {
83
96
  const path = queryPos < 0 ? baseIRI : baseIRI.substr(0, queryPos);
84
97
  this._basePath = baseIRI.indexOf('/') < 0 ? baseIRI :
85
98
  path.substr(0, path.lastIndexOf('/') + 1);
86
- baseIRI = baseIRI.match(/^(?:([a-z][a-z0-9+.-]*:))?(?:\/\/[^\/]*)?/i);
99
+ const base = baseIRI;
100
+ baseIRI = baseIRI.match(/^(?:([a-z][a-z0-9+.-]*:))?(\/\/[^\/?#]*)?/i);
87
101
  this._baseRoot = baseIRI[0];
88
102
  this._baseScheme = baseIRI[1];
103
+ // If the base has an authority but an empty path,
104
+ // relative IRIs merge under the path '/' (RFC 3986 §5.2.3)
105
+ if (baseIRI[2] !== undefined && (base.length === this._baseRoot.length || base[this._baseRoot.length] === '?'))
106
+ this._basePath = `${this._baseRoot}/`;
89
107
  }
90
108
  }
91
109
 
110
+ // ### `_enterTripleTerm` stores the current parsing context when entering a triple term,
111
+ // failing if triple terms nest deeper than the maximum depth
112
+ _enterTripleTerm(type, subject, predicate, token) {
113
+ if (this._tripleTermDepth >= this._maxTripleTermDepth) {
114
+ this._error(`Triple terms nested deeper than ${this._maxTripleTermDepth} level${this._maxTripleTermDepth === 1 ? '' : 's'}`, token);
115
+ return false;
116
+ }
117
+ this._tripleTermDepth++;
118
+ this._saveContext(type, this._graph, subject, predicate, null);
119
+ this._graph = null;
120
+ return true;
121
+ }
122
+
92
123
  // ### `_saveContext` stores the current parsing context
93
124
  // when entering a new scope (list, blank node, formula)
94
125
  _saveContext(type, graph, subject, predicate, object) {
@@ -207,8 +238,13 @@ export default class N3Parser {
207
238
  case 'GRAPH':
208
239
  if (this._supportsNamedGraphs)
209
240
  return this._readNamedGraphLabel;
210
- // Otherwise, the next token must be a subject
241
+ // Otherwise, the next token must be a subject, unless it is an additional directive
211
242
  default:
243
+ if (this._directives !== null && token.type in this._directives && this._graph === null) {
244
+ this._sparqlStyle = token.type[0] !== '@';
245
+ this._directiveCallback(this._directives[token.type]);
246
+ return this._readDeclarationPunctuation;
247
+ }
212
248
  return this._readSubject(token);
213
249
  }
214
250
  }
@@ -339,13 +375,9 @@ export default class N3Parser {
339
375
  case '<<(':
340
376
  if (!this._n3Mode)
341
377
  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;
378
+ return this._enterTripleTerm('<<(', null, null, token) && this._readSubject;
345
379
  case '<<':
346
- this._saveContext('<<', this._graph, null, null, null);
347
- this._graph = null;
348
- return this._readSubject;
380
+ return this._enterTripleTerm('<<', null, null, token) && this._readSubject;
349
381
  default:
350
382
  // Read the subject entity
351
383
  if ((this._subject = this._readEntity(token)) === undefined)
@@ -490,13 +522,9 @@ export default class N3Parser {
490
522
  this._graph = this._factory.blankNode());
491
523
  return this._readInFormulaContext;
492
524
  case '<<(':
493
- this._saveContext('<<(', this._graph, this._subject, this._predicate, null);
494
- this._graph = null;
495
- return this._readSubject;
525
+ return this._enterTripleTerm('<<(', this._subject, this._predicate, token) && this._readSubject;
496
526
  case '<<':
497
- this._saveContext('<<', this._graph, this._subject, this._predicate, null);
498
- this._graph = null;
499
- return this._readSubject;
527
+ return this._enterTripleTerm('<<', this._subject, this._predicate, token) && this._readSubject;
500
528
  default:
501
529
  // Read the object entity
502
530
  if ((this._object = this._readEntity(token)) === undefined)
@@ -716,13 +744,9 @@ export default class N3Parser {
716
744
  this._subject = null;
717
745
  return this._readInFormulaContext;
718
746
  case '<<(':
719
- this._saveContext('<<(', this._graph, null, null, null);
720
- this._graph = null;
721
- next = this._readSubject;
722
- break;
723
747
  case '<<':
724
- this._saveContext('<<', this._graph, null, null, null);
725
- this._graph = null;
748
+ if (!this._enterTripleTerm(token.type, null, null, token))
749
+ return;
726
750
  next = this._readSubject;
727
751
  break;
728
752
  default:
@@ -1272,6 +1296,7 @@ export default class N3Parser {
1272
1296
  const quad = this._createQuad(this._subject, this._predicate, this._object,
1273
1297
  this._graph, this._inversePredicate);
1274
1298
  this._restoreContext('<<(', token);
1299
+ this._tripleTermDepth--;
1275
1300
 
1276
1301
  // If we're in a list, continue processing that list
1277
1302
  const stack = this._contextStack, parent = stack.length && stack[stack.length - 1];
@@ -1307,6 +1332,7 @@ export default class N3Parser {
1307
1332
  this._tripleTerm = null;
1308
1333
  const reifier = this._readTripleTerm();
1309
1334
  this._restoreContext('<<', token);
1335
+ this._tripleTermDepth--;
1310
1336
 
1311
1337
  // // If we're in a list, continue processing that list
1312
1338
  const stack = this._contextStack, parent = stack.length && stack[stack.length - 1];
@@ -1540,8 +1566,13 @@ export default class N3Parser {
1540
1566
  }
1541
1567
  // Resolve root-relative IRIs at the root of the base IRI
1542
1568
  case '/':
1543
- // Resolve scheme-relative IRIs to the scheme
1544
- return (iri[1] === '/' ? this._baseScheme : this._baseRoot) + this._removeDotSegments(iri);
1569
+ // Resolve scheme-relative IRIs to the scheme,
1570
+ // keeping the reference's authority intact (RFC 3986 §5.2.4)
1571
+ if (iri[1] !== '/')
1572
+ return this._baseRoot + this._removeDotSegments(iri);
1573
+ // Without a base scheme, a leading ':' marks where the authority starts
1574
+ return this._baseScheme ? this._removeDotSegments(this._baseScheme + iri) :
1575
+ this._removeDotSegments(`:${iri}`).substr(1);
1545
1576
  // Resolve all other IRIs at the base IRI's path
1546
1577
  default:
1547
1578
  // Relative IRIs cannot contain a colon in the first path segment
@@ -1566,8 +1597,8 @@ export default class N3Parser {
1566
1597
  if (pathStart < 0) {
1567
1598
  // Skip two slashes before the authority
1568
1599
  if (iri[++i] === '/' && iri[++i] === '/')
1569
- // Skip to slash after the authority
1570
- while ((pathStart = i + 1) < length && iri[pathStart] !== '/')
1600
+ // Skip to the end of the authority
1601
+ while ((pathStart = i + 1) < length && iri[pathStart] !== '/' && iri[pathStart] !== '?' && iri[pathStart] !== '#')
1571
1602
  i = pathStart;
1572
1603
  }
1573
1604
  break;
@@ -1618,13 +1649,14 @@ export default class N3Parser {
1618
1649
  parse(input, quadCallback, prefixCallback, versionCallback) {
1619
1650
  // The second parameter also accepts an object of named callbacks.
1620
1651
  // As a second and third parameter it still accepts a separate quadCallback and prefixCallback for backward compatibility as well
1621
- let onQuad, onPrefix, onComment, onVersion, onToken, onTokenEnd;
1652
+ let onQuad, onPrefix, onComment, onVersion, onDirective, onToken, onTokenEnd;
1622
1653
  if (quadCallback && (quadCallback.onQuad || quadCallback.onPrefix || quadCallback.onComment || quadCallback.onVersion ||
1623
- quadCallback.onToken || quadCallback.onTokenEnd)) {
1654
+ quadCallback.onDirective || quadCallback.onToken || quadCallback.onTokenEnd)) {
1624
1655
  onQuad = quadCallback.onQuad;
1625
1656
  onPrefix = quadCallback.onPrefix;
1626
1657
  onComment = quadCallback.onComment;
1627
1658
  onVersion = quadCallback.onVersion;
1659
+ onDirective = quadCallback.onDirective;
1628
1660
  onToken = quadCallback.onToken;
1629
1661
  onTokenEnd = quadCallback.onTokenEnd;
1630
1662
  }
@@ -1645,6 +1677,7 @@ export default class N3Parser {
1645
1677
  this._prefixes[''] = this._resolveIRI('#');
1646
1678
  this._prefixCallback = onPrefix || noop;
1647
1679
  this._versionCallback = onVersion || noop;
1680
+ this._directiveCallback = onDirective || noop;
1648
1681
  this._inversePredicate = false;
1649
1682
  this._expectOf = false;
1650
1683
  this._quantified = Object.create(null);
@@ -1652,6 +1685,7 @@ export default class N3Parser {
1652
1685
  this._prefixChanges = null;
1653
1686
  this._quantifiedChanges = null;
1654
1687
  this._emptyFormula = false;
1688
+ this._tripleTermDepth = 0;
1655
1689
 
1656
1690
  let readToken = token => {
1657
1691
  return this._readCallback = this._readCallback(token);
package/src/N3Reasoner.js CHANGED
@@ -156,7 +156,7 @@ export default class N3Reasoner {
156
156
  }
157
157
 
158
158
  _createRule({ premise, conclusion }) {
159
- const varMapping = {};
159
+ const varMapping = Object.create(null);
160
160
 
161
161
  const toId = value => value.termType === 'Variable' ?
162
162
  // If the term is a variable, then create an empty object that values can be placed into
package/src/N3Store.js CHANGED
@@ -118,6 +118,35 @@ function difference(s1, s2, depth = 4) {
118
118
  return target;
119
119
  }
120
120
 
121
+ // Returns the key of a term in the entity index.
122
+ // Keys mark the term type by their first character, so the IRI of a named node
123
+ // that starts with such a marker (as relative IRIs can) is wrapped in < and >.
124
+ const markedIRI = /^[?_"[.<]/;
125
+ function entityKey(term) {
126
+ // Strings are term ids, which only need wrapping when they are IRIs starting with <
127
+ if (typeof term === 'string')
128
+ return term.charCodeAt(0) !== 0x3C || term === '<>' ? term : `<${term}>`;
129
+ // IDs of IRIs usually start with a lowercase scheme letter, which never marks a term type
130
+ const id = termToId(term);
131
+ if (id.charCodeAt(0) >= 0x61 || !term)
132
+ return id;
133
+ // Keys of quoted triples are built from the keys of their components
134
+ if (term.termType === 'Quad')
135
+ return JSON.stringify(quadKeyParts(term));
136
+ return term.termType !== 'NamedNode' || !markedIRI.test(term.value) ? id : `<${term.value}>`;
137
+ }
138
+
139
+ // Returns the keys of the components of a quad, nested like the parts of its internal id
140
+ function quadKeyParts(quad) {
141
+ const parts = [nestedKey(quad.subject), nestedKey(quad.predicate), nestedKey(quad.object)];
142
+ if (quad.graph && !isDefaultGraph(quad.graph))
143
+ parts.push(nestedKey(quad.graph));
144
+ return parts;
145
+ }
146
+ function nestedKey(term) {
147
+ return term.termType === 'Quad' ? quadKeyParts(term) : entityKey(term);
148
+ }
149
+
121
150
  // ## Constructor
122
151
  export class N3EntityIndex {
123
152
  constructor(options = {}) {
@@ -135,6 +164,10 @@ export class N3EntityIndex {
135
164
  }
136
165
 
137
166
  _termFromId(id) {
167
+ // A key in < and > is a named node
168
+ if (id[0] === '<')
169
+ return this._factory.namedNode(id.substring(1, id.length - 1));
170
+ // A key starting with . is a quoted triple
138
171
  if (id[0] === '.') {
139
172
  const entities = this._entities;
140
173
  const terms = id.split('.');
@@ -159,18 +192,34 @@ export class N3EntityIndex {
159
192
  return s && p && o && (isDefaultGraph(term.graph) || (g = this._termToNumericId(term.graph))) &&
160
193
  this._ids[g ? `.${s}.${p}.${o}.${g}` : `.${s}.${p}.${o}`];
161
194
  }
162
- return this._ids[termToId(term)];
195
+ return typeof term === 'string' ? this._stringToNumericId(term) : this._ids[entityKey(term)];
196
+ }
197
+
198
+ // Returns the numeric id of a term given as a string id
199
+ _stringToNumericId(term) {
200
+ // The string is the key unless it is an IRI starting with <. Read the first character
201
+ // from the stored key after a match, which is cheaper than from a concatenated string.
202
+ const id = this._ids[term];
203
+ if (id ? this._entities[id].charCodeAt(0) !== 0x3C : term.charCodeAt(0) !== 0x3C)
204
+ return id;
205
+ return term === '<>' ? id : this._ids[`<${term}>`];
163
206
  }
164
207
 
165
208
  _termToNewNumericId(term) {
209
+ if (typeof term === 'string')
210
+ return this._stringToNumericId(term) || this._addEntity(entityKey(term));
166
211
  // This assumes that no graph term is present - we may wish to error if there is one
167
212
  const str = term && term.termType === 'Quad' ?
168
213
  `.${this._termToNewNumericId(term.subject)}.${this._termToNewNumericId(term.predicate)}.${this._termToNewNumericId(term.object)}${
169
214
  isDefaultGraph(term.graph) ? '' : `.${this._termToNewNumericId(term.graph)}`
170
215
  }`
171
- : termToId(term);
216
+ : entityKey(term);
217
+
218
+ return this._ids[str] || this._addEntity(str);
219
+ }
172
220
 
173
- return this._ids[str] || (this._ids[this._entities[++this._id] = str] = this._id);
221
+ _addEntity(key) {
222
+ return this._ids[this._entities[++this._id] = key] = this._id;
174
223
  }
175
224
 
176
225
  createBlankNode(suggestedName) {
@@ -1382,7 +1431,7 @@ function intersectMatchPatterns(left, right) {
1382
1431
  const leftTerm = left[i], rightTerm = right[i];
1383
1432
  if (leftTerm === null || leftTerm === undefined)
1384
1433
  result[i] = rightTerm;
1385
- else if (rightTerm === null || rightTerm === undefined || termToId(leftTerm) === termToId(rightTerm))
1434
+ else if (rightTerm === null || rightTerm === undefined || entityKey(leftTerm) === entityKey(rightTerm))
1386
1435
  result[i] = leftTerm;
1387
1436
  else
1388
1437
  return false;
@@ -1433,10 +1482,10 @@ class DatasetCoreAndReadableStream extends Readable {
1433
1482
  _matchesQuad(quad) {
1434
1483
  const { subject, predicate, object, graph } = this;
1435
1484
  return !this._matchesNothing &&
1436
- (subject === null || subject === undefined || termToId(subject) === termToId(quad.subject)) &&
1437
- (predicate === null || predicate === undefined || termToId(predicate) === termToId(quad.predicate)) &&
1438
- (object === null || object === undefined || termToId(object) === termToId(quad.object)) &&
1439
- (graph === null || graph === undefined || termToId(graph) === termToId(quad.graph));
1485
+ (subject === null || subject === undefined || entityKey(subject) === entityKey(quad.subject)) &&
1486
+ (predicate === null || predicate === undefined || entityKey(predicate) === entityKey(quad.predicate)) &&
1487
+ (object === null || object === undefined || entityKey(object) === entityKey(quad.object)) &&
1488
+ (graph === null || graph === undefined || entityKey(graph) === entityKey(quad.graph));
1440
1489
  }
1441
1490
 
1442
1491
  // ### `_assertMatchesPattern` rejects a Quad outside this view.
@@ -16,6 +16,8 @@ export default class N3StreamParser extends Transform {
16
16
  onQuad: (error, quad) => { error && this.emit('error', error) || quad && this.push(quad); },
17
17
  // Emit prefixes through the `prefix` event
18
18
  onPrefix: (prefix, uri) => { this.emit('prefix', prefix, uri); },
19
+ // Emit additional directives through the `directive` event
20
+ onDirective: name => { this.emit('directive', name); },
19
21
  };
20
22
 
21
23
  if (options && options.comments)
package/src/N3Writer.js CHANGED
@@ -8,10 +8,17 @@ import { escapeRegex } from './Util';
8
8
  const DEFAULTGRAPH = N3DataFactory.defaultGraph();
9
9
 
10
10
  const { rdf, xsd } = namespaces;
11
+ const { hasOwnProperty } = Object.prototype;
11
12
 
12
- // Characters in literals that require escaping
13
+ // Characters that require escaping in Turtle, TriG, and N3,
14
+ // where characters outside the Basic Multilingual Plane are escaped as well
13
15
  const escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/,
14
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,
15
22
  escapedCharacters = {
16
23
  '\\': '\\\\', '"': '\\"', '\t': '\\t',
17
24
  '\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f',
@@ -57,8 +64,11 @@ export default class N3Writer {
57
64
  this._subject = null;
58
65
  if (!(/triple|quad/i).test(options.format)) {
59
66
  this._lineMode = false;
67
+ this._escape = escape, this._escapeAll = escapeAll, this._characterReplacer = characterReplacer;
60
68
  this._graph = DEFAULTGRAPH;
61
69
  this._prefixIRIs = Object.create(null);
70
+ // Escaped prefix IRIs and names for the prefix matcher, computed once per prefix
71
+ this._prefixPatterns = Object.create(null);
62
72
  if (options.baseIRI) {
63
73
  this._baseIri = new BaseIRI(options.baseIRI);
64
74
  if (options.writeBase !== false)
@@ -69,6 +79,9 @@ export default class N3Writer {
69
79
  else {
70
80
  this._lineMode = true;
71
81
  this._writeQuad = this._writeQuadLine;
82
+ // N-Triples and N-Quads are written in their canonical form
83
+ this._escape = canonicalEscape, this._escapeAll = canonicalEscapeAll;
84
+ this._characterReplacer = canonicalCharacterReplacer;
72
85
  }
73
86
  }
74
87
 
@@ -161,7 +174,7 @@ export default class N3Writer {
161
174
  // A blank node or list is represented as-is
162
175
  if (entity.termType !== 'NamedNode') {
163
176
  // If it is a list head, pretty-print it
164
- if (this._lists && (entity.value in this._lists))
177
+ if (this._lists && hasOwnProperty.call(this._lists, entity.value))
165
178
  entity = this.list(this._lists[entity.value]);
166
179
  // Terms from this library already hold their serialization as id
167
180
  if (entity instanceof Term)
@@ -174,20 +187,19 @@ export default class N3Writer {
174
187
  iri = this._baseIri.toRelative(iri);
175
188
  }
176
189
  // Escape special characters
177
- if (escape.test(iri))
178
- iri = iri.replace(escapeAll, characterReplacer);
190
+ if (this._escape.test(iri))
191
+ iri = iri.replace(this._escapeAll, this._characterReplacer);
179
192
  // Try to represent the IRI as prefixed name, unless no prefixes were added
180
- const prefixMatch = this._hasPrefixes ? this._prefixRegex.exec(iri) : null;
181
- return !prefixMatch ? `<${iri}>` :
182
- (!prefixMatch[1] ? iri : this._prefixIRIs[prefixMatch[1]] + prefixMatch[2]);
193
+ const prefixMatch = this._hasPrefixes ? (this._prefixRegex || this._createPrefixRegex()).exec(iri) : null;
194
+ return !prefixMatch ? `<${iri}>` : this._prefixIRIs[prefixMatch[1]] + prefixMatch[2];
183
195
  }
184
196
 
185
197
  // ### `_encodeLiteral` represents a literal
186
198
  _encodeLiteral(literal) {
187
199
  // Escape special characters
188
200
  let value = literal.value;
189
- if (escape.test(value))
190
- value = value.replace(escapeAll, characterReplacer);
201
+ if (this._escape.test(value))
202
+ value = value.replace(this._escapeAll, this._characterReplacer);
191
203
 
192
204
  // Write a language-tagged literal
193
205
  const language = literal.language;
@@ -258,11 +270,13 @@ export default class N3Writer {
258
270
 
259
271
  // ### `_encodeQuad` encodes an RDF-star quad
260
272
  _encodeQuad({ subject, predicate, object, graph }) {
261
- return `<<(${
273
+ // Canonical N-Triples and N-Quads put spaces inside the delimiters
274
+ const space = this._lineMode ? ' ' : '';
275
+ return `<<(${space}${
262
276
  this._encodeSubject(subject)} ${
263
277
  this._encodePredicate(predicate)} ${
264
278
  this._encodeObject(object)}${
265
- isDefaultGraph(graph) ? '' : ` ${this._encodeIriOrBlank(graph)}`})>>`;
279
+ isDefaultGraph(graph) ? '' : ` ${this._encodeIriOrBlank(graph)}`}${space})>>`;
266
280
  }
267
281
 
268
282
  // ### `_blockedWrite` replaces `_write` after the writer has been closed
@@ -316,24 +330,27 @@ export default class N3Writer {
316
330
  }
317
331
  // Store and write the prefix
318
332
  this._prefixIRIs[iri] = (prefix += ':');
333
+ this._prefixPatterns[iri] = escapeRegex(iri);
319
334
  this._write(`@prefix ${prefix} <${iri}>.\n`);
320
335
  }
321
- // Recreate the prefix matcher
336
+ // Recreate the prefix matcher when it is next needed, so that adding
337
+ // prefixes one by one does not rebuild it for every prefix
322
338
  if (hasPrefixes) {
323
339
  this._hasPrefixes = true;
324
- let IRIlist = '', prefixList = '';
325
- for (const prefixIRI in this._prefixIRIs) {
326
- IRIlist += IRIlist ? `|${prefixIRI}` : prefixIRI;
327
- prefixList += (prefixList ? '|' : '') + this._prefixIRIs[prefixIRI];
328
- }
329
- IRIlist = escapeRegex(IRIlist, /[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
330
- this._prefixRegex = new RegExp(`^(?:${prefixList})[^\/]*$|` +
331
- `^(${IRIlist})([_a-zA-Z0-9](?:\\.?[\\-_a-zA-Z0-9])*)$`);
340
+ this._prefixRegex = null;
332
341
  }
333
342
  // End a prefix block with a newline
334
343
  this._write(hasPrefixes ? '\n' : '', done);
335
344
  }
336
345
 
346
+ // ### `_createPrefixRegex` creates the matcher for the current prefixes
347
+ _createPrefixRegex() {
348
+ let IRIlist = '';
349
+ for (const prefixIRI in this._prefixPatterns)
350
+ IRIlist += IRIlist ? `|${this._prefixPatterns[prefixIRI]}` : this._prefixPatterns[prefixIRI];
351
+ return this._prefixRegex = new RegExp(`^(${IRIlist})([_a-zA-Z0-9](?:\\.?[\\-_a-zA-Z0-9])*)$`);
352
+ }
353
+
337
354
  // ### `blank` creates a blank node with the given content
338
355
  blank(predicate, object) {
339
356
  let children = predicate, child, length;
@@ -425,3 +442,14 @@ function characterReplacer(character) {
425
442
  }
426
443
  return result;
427
444
  }
445
+
446
+ // Replaces a character by its canonical N-Triples escape:
447
+ // a short escape where one exists, and an upper-case `\uXXXX` escape otherwise
448
+ function canonicalCharacterReplacer(character) {
449
+ let result = escapedCharacters[character];
450
+ if (result === undefined) {
451
+ result = character.charCodeAt(0).toString(16).toUpperCase();
452
+ result = '\\u0000'.substr(0, 6 - result.length) + result;
453
+ }
454
+ return result;
455
+ }
package/src/Util.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export function escapeRegex(regex) {
2
- return regex.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
2
+ return regex.replace(/[.*+?^${}()|[\]\\/-]/g, '\\$&');
3
3
  }