n3 2.11.2 → 2.11.4

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/N3Lexer.js CHANGED
@@ -89,6 +89,11 @@ function execAtEnd(regExp, input, pos) {
89
89
  return regExp.exec(`${input.slice(pos)} `);
90
90
  }
91
91
 
92
+ // Whitespace or the start of a comment
93
+ function isSeparatorCode(code) {
94
+ return code === SPACE || code === TAB || code === LF || code === CR || code === HASH;
95
+ }
96
+
92
97
  // ## Constructor
93
98
  class N3Lexer {
94
99
  constructor(options) {
@@ -194,6 +199,8 @@ class N3Lexer {
194
199
  }
195
200
  }
196
201
  if (pos >= input.length) {
202
+ // A datatype marker needs a type
203
+ if (inputFinished && this._previousMarker === '^^') return reportSyntaxError(this, input, pos);
197
204
  this._linePosition = currentLineLength;
198
205
  if (inputFinished) {
199
206
  emitToken('eof', '', '', this._line, currentLineLength, 0);
@@ -215,6 +222,8 @@ class N3Lexer {
215
222
  inconclusive = false;
216
223
  switch (firstChar) {
217
224
  case '^':
225
+ // A datatype marker separated from its type cannot be followed by another marker
226
+ if (this._previousMarker === '^^') return reportSyntaxError(this, input, pos);
218
227
  // We need at least 3 tokens lookahead to distinguish ^^<IRI> and ^^pre:fixed
219
228
  if (input.length - pos < 3) break;
220
229
  // Try to match a type
@@ -223,6 +232,8 @@ class N3Lexer {
223
232
  // Move to type IRI or prefixed name
224
233
  pos += 2;
225
234
  if (input[pos] !== '<') {
235
+ // Whitespace and comments may separate the marker from the type
236
+ if (isSeparatorCode(input.charCodeAt(pos))) continue; // eslint-disable-line no-continue
226
237
  inconclusive = true;
227
238
  break;
228
239
  }
package/lib/N3Parser.js CHANGED
@@ -65,6 +65,9 @@ class N3Parser {
65
65
  // Disable parsing of unsupported versions by default
66
66
  this._parseUnsupportedVersions = !!options.parseUnsupportedVersions;
67
67
  this._version = options.version;
68
+ // Maximum nesting depth of triple terms (default 1024; Infinity disables the limit)
69
+ const maxTripleTermDepth = options.maxTripleTermDepth;
70
+ this._maxTripleTermDepth = maxTripleTermDepth === Infinity || Number.isInteger(maxTripleTermDepth) && maxTripleTermDepth >= 0 ? maxTripleTermDepth : 1024;
68
71
  }
69
72
 
70
73
  // ## Static class methods
@@ -96,6 +99,19 @@ class N3Parser {
96
99
  }
97
100
  }
98
101
 
102
+ // ### `_enterTripleTerm` stores the current parsing context when entering a triple term,
103
+ // failing if triple terms nest deeper than the maximum depth
104
+ _enterTripleTerm(type, subject, predicate, token) {
105
+ if (this._tripleTermDepth >= this._maxTripleTermDepth) {
106
+ this._error(`Triple terms nested deeper than ${this._maxTripleTermDepth} level${this._maxTripleTermDepth === 1 ? '' : 's'}`, token);
107
+ return false;
108
+ }
109
+ this._tripleTermDepth++;
110
+ this._saveContext(type, this._graph, subject, predicate, null);
111
+ this._graph = null;
112
+ return true;
113
+ }
114
+
99
115
  // ### `_saveContext` stores the current parsing context
100
116
  // when entering a new scope (list, blank node, formula)
101
117
  _saveContext(type, graph, subject, predicate, object) {
@@ -338,13 +354,9 @@ class N3Parser {
338
354
  }
339
355
  case '<<(':
340
356
  if (!this._n3Mode) return this._error('Disallowed triple term as subject', token);
341
- this._saveContext('<<(', this._graph, null, null, null);
342
- this._graph = null;
343
- return this._readSubject;
357
+ return this._enterTripleTerm('<<(', null, null, token) && this._readSubject;
344
358
  case '<<':
345
- this._saveContext('<<', this._graph, null, null, null);
346
- this._graph = null;
347
- return this._readSubject;
359
+ return this._enterTripleTerm('<<', null, null, token) && this._readSubject;
348
360
  default:
349
361
  // Read the subject entity
350
362
  if ((this._subject = this._readEntity(token)) === undefined) return;
@@ -467,13 +479,9 @@ class N3Parser {
467
479
  this._saveContext('formula', this._graph, this._subject, this._predicate, this._graph = this._factory.blankNode());
468
480
  return this._readInFormulaContext;
469
481
  case '<<(':
470
- this._saveContext('<<(', this._graph, this._subject, this._predicate, null);
471
- this._graph = null;
472
- return this._readSubject;
482
+ return this._enterTripleTerm('<<(', this._subject, this._predicate, token) && this._readSubject;
473
483
  case '<<':
474
- this._saveContext('<<', this._graph, this._subject, this._predicate, null);
475
- this._graph = null;
476
- return this._readSubject;
484
+ return this._enterTripleTerm('<<', this._subject, this._predicate, token) && this._readSubject;
477
485
  default:
478
486
  // Read the object entity
479
487
  if ((this._object = this._readEntity(token)) === undefined) return;
@@ -668,13 +676,8 @@ class N3Parser {
668
676
  this._subject = null;
669
677
  return this._readInFormulaContext;
670
678
  case '<<(':
671
- this._saveContext('<<(', this._graph, null, null, null);
672
- this._graph = null;
673
- next = this._readSubject;
674
- break;
675
679
  case '<<':
676
- this._saveContext('<<', this._graph, null, null, null);
677
- this._graph = null;
680
+ if (!this._enterTripleTerm(token.type, null, null, token)) return;
678
681
  next = this._readSubject;
679
682
  break;
680
683
  default:
@@ -1166,6 +1169,7 @@ class N3Parser {
1166
1169
  // Read the quad and restore the previous context
1167
1170
  const quad = this._createQuad(this._subject, this._predicate, this._object, this._graph, this._inversePredicate);
1168
1171
  this._restoreContext('<<(', token);
1172
+ this._tripleTermDepth--;
1169
1173
 
1170
1174
  // If we're in a list, continue processing that list
1171
1175
  const stack = this._contextStack,
@@ -1201,6 +1205,7 @@ class N3Parser {
1201
1205
  this._tripleTerm = null;
1202
1206
  const reifier = this._readTripleTerm();
1203
1207
  this._restoreContext('<<', token);
1208
+ this._tripleTermDepth--;
1204
1209
 
1205
1210
  // // If we're in a list, continue processing that list
1206
1211
  const stack = this._contextStack,
@@ -1532,6 +1537,7 @@ class N3Parser {
1532
1537
  this._prefixChanges = null;
1533
1538
  this._quantifiedChanges = null;
1534
1539
  this._emptyFormula = false;
1540
+ this._tripleTermDepth = 0;
1535
1541
  let readToken = token => {
1536
1542
  return this._readCallback = this._readCallback(token);
1537
1543
  };
package/lib/N3Writer.js CHANGED
@@ -19,9 +19,15 @@ const {
19
19
  xsd
20
20
  } = _IRIs.default;
21
21
 
22
- // Characters in literals that require escaping
22
+ // Characters that require escaping in Turtle, TriG, and N3,
23
+ // where characters outside the Basic Multilingual Plane are escaped as well
23
24
  const escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/,
24
25
  escapeAll = /["\\\t\n\r\b\f\u0000-\u0019]|[\ud800-\udbff][\udc00-\udfff]/g,
26
+ // Characters that require escaping in canonical N-Triples and N-Quads:
27
+ // U+0000–U+001F, `"`, `\`, U+007F, U+FFFE, and U+FFFF.
28
+ // A negated class is as fast to test as a shorter class listing characters.
29
+ canonicalEscape = /[^ !#-\[\]-~\u0080-\uFFFD]/,
30
+ canonicalEscapeAll = /[^ !#-\[\]-~\u0080-\uFFFD]/g,
25
31
  escapedCharacters = {
26
32
  '\\': '\\\\',
27
33
  '"': '\\"',
@@ -75,8 +81,11 @@ class N3Writer {
75
81
  this._subject = null;
76
82
  if (!/triple|quad/i.test(options.format)) {
77
83
  this._lineMode = false;
84
+ this._escape = escape, this._escapeAll = escapeAll, this._characterReplacer = characterReplacer;
78
85
  this._graph = DEFAULTGRAPH;
79
86
  this._prefixIRIs = Object.create(null);
87
+ // Escaped prefix IRIs and names for the prefix matcher, computed once per prefix
88
+ this._prefixPatterns = Object.create(null);
80
89
  if (options.baseIRI) {
81
90
  this._baseIri = new _BaseIRI.default(options.baseIRI);
82
91
  if (options.writeBase) this._write(`@base <${options.baseIRI}>.\n`);
@@ -85,6 +94,9 @@ class N3Writer {
85
94
  } else {
86
95
  this._lineMode = true;
87
96
  this._writeQuad = this._writeQuadLine;
97
+ // N-Triples and N-Quads are written in their canonical form
98
+ this._escape = canonicalEscape, this._escapeAll = canonicalEscapeAll;
99
+ this._characterReplacer = canonicalCharacterReplacer;
88
100
  }
89
101
  }
90
102
 
@@ -174,7 +186,7 @@ class N3Writer {
174
186
  iri = this._baseIri.toRelative(iri);
175
187
  }
176
188
  // Escape special characters
177
- if (escape.test(iri)) iri = iri.replace(escapeAll, characterReplacer);
189
+ if (this._escape.test(iri)) iri = iri.replace(this._escapeAll, this._characterReplacer);
178
190
  // Try to represent the IRI as prefixed name, unless no prefixes were added
179
191
  const prefixMatch = this._hasPrefixes ? this._prefixRegex.exec(iri) : null;
180
192
  return !prefixMatch ? `<${iri}>` : !prefixMatch[1] ? iri : this._prefixIRIs[prefixMatch[1]] + prefixMatch[2];
@@ -184,7 +196,7 @@ class N3Writer {
184
196
  _encodeLiteral(literal) {
185
197
  // Escape special characters
186
198
  let value = literal.value;
187
- if (escape.test(value)) value = value.replace(escapeAll, characterReplacer);
199
+ if (this._escape.test(value)) value = value.replace(this._escapeAll, this._characterReplacer);
188
200
 
189
201
  // Write a language-tagged literal
190
202
  const language = literal.language;
@@ -254,7 +266,9 @@ class N3Writer {
254
266
  object,
255
267
  graph
256
268
  }) {
257
- return `<<(${this._encodeSubject(subject)} ${this._encodePredicate(predicate)} ${this._encodeObject(object)}${(0, _N3Util.isDefaultGraph)(graph) ? '' : ` ${this._encodeIriOrBlank(graph)}`})>>`;
269
+ // Canonical N-Triples and N-Quads put spaces inside the delimiters
270
+ const space = this._lineMode ? ' ' : '';
271
+ return `<<(${space}${this._encodeSubject(subject)} ${this._encodePredicate(predicate)} ${this._encodeObject(object)}${(0, _N3Util.isDefaultGraph)(graph) ? '' : ` ${this._encodeIriOrBlank(graph)}`}${space})>>`;
258
272
  }
259
273
 
260
274
  // ### `_blockedWrite` replaces `_write` after the writer has been closed
@@ -302,6 +316,7 @@ class N3Writer {
302
316
  }
303
317
  // Store and write the prefix
304
318
  this._prefixIRIs[iri] = prefix += ':';
319
+ this._prefixPatterns[iri] = [(0, _Util.escapeRegex)(iri), (0, _Util.escapeRegex)(prefix)];
305
320
  this._write(`@prefix ${prefix} <${iri}>.\n`);
306
321
  }
307
322
  // Recreate the prefix matcher
@@ -309,12 +324,12 @@ class N3Writer {
309
324
  this._hasPrefixes = true;
310
325
  let IRIlist = '',
311
326
  prefixList = '';
312
- for (const prefixIRI in this._prefixIRIs) {
313
- IRIlist += IRIlist ? `|${prefixIRI}` : prefixIRI;
314
- prefixList += (prefixList ? '|' : '') + this._prefixIRIs[prefixIRI];
327
+ for (const prefixIRI in this._prefixPatterns) {
328
+ const [IRIpattern, prefixPattern] = this._prefixPatterns[prefixIRI];
329
+ IRIlist += IRIlist ? `|${IRIpattern}` : IRIpattern;
330
+ prefixList += prefixList ? `|${prefixPattern}` : prefixPattern;
315
331
  }
316
- IRIlist = (0, _Util.escapeRegex)(IRIlist, /[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
317
- this._prefixRegex = new RegExp(`^(?:${prefixList})[^\/]*$|` + `^(${IRIlist})([_a-zA-Z0-9](?:\\.?[\\-_a-zA-Z0-9])*)$`);
332
+ this._prefixRegex = new RegExp(`^(?:${prefixList})[^/]*$|` + `^(${IRIlist})([_a-zA-Z0-9](?:\\.?[\\-_a-zA-Z0-9])*)$`);
318
333
  }
319
334
  // End a prefix block with a newline
320
335
  this._write(hasPrefixes ? '\n' : '', done);
@@ -409,4 +424,15 @@ function characterReplacer(character) {
409
424
  }
410
425
  }
411
426
  return result;
427
+ }
428
+
429
+ // Replaces a character by its canonical N-Triples escape:
430
+ // a short escape where one exists, and an upper-case `\uXXXX` escape otherwise
431
+ function canonicalCharacterReplacer(character) {
432
+ let result = escapedCharacters[character];
433
+ if (result === undefined) {
434
+ result = character.charCodeAt(0).toString(16).toUpperCase();
435
+ result = '\\u0000'.substr(0, 6 - result.length) + result;
436
+ }
437
+ return result;
412
438
  }
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": "2.11.2",
3
+ "version": "2.11.4",
4
4
  "description": "Lightning fast, asynchronous, streaming Turtle / N3 / RDF library.",
5
5
  "author": "Ruben Verborgh <ruben.verborgh@gmail.com>",
6
6
  "keywords": [
package/src/N3Lexer.js CHANGED
@@ -54,6 +54,11 @@ function execAtEnd(regExp, input, pos) {
54
54
  return regExp.exec(`${input.slice(pos)} `);
55
55
  }
56
56
 
57
+ // Whitespace or the start of a comment
58
+ function isSeparatorCode(code) {
59
+ return code === SPACE || code === TAB || code === LF || code === CR || code === HASH;
60
+ }
61
+
57
62
  // ## Constructor
58
63
  export default class N3Lexer {
59
64
  constructor(options) {
@@ -170,6 +175,9 @@ export default class N3Lexer {
170
175
  }
171
176
  }
172
177
  if (pos >= input.length) {
178
+ // A datatype marker needs a type
179
+ if (inputFinished && this._previousMarker === '^^')
180
+ return reportSyntaxError(this, input, pos);
173
181
  this._linePosition = currentLineLength;
174
182
  if (inputFinished) {
175
183
  emitToken('eof', '', '', this._line, currentLineLength, 0);
@@ -185,6 +193,9 @@ export default class N3Lexer {
185
193
  finalLineLength = 0, inconclusive = false;
186
194
  switch (firstChar) {
187
195
  case '^':
196
+ // A datatype marker separated from its type cannot be followed by another marker
197
+ if (this._previousMarker === '^^')
198
+ return reportSyntaxError(this, input, pos);
188
199
  // We need at least 3 tokens lookahead to distinguish ^^<IRI> and ^^pre:fixed
189
200
  if (input.length - pos < 3)
190
201
  break;
@@ -194,6 +205,9 @@ export default class N3Lexer {
194
205
  // Move to type IRI or prefixed name
195
206
  pos += 2;
196
207
  if (input[pos] !== '<') {
208
+ // Whitespace and comments may separate the marker from the type
209
+ if (isSeparatorCode(input.charCodeAt(pos)))
210
+ continue; // eslint-disable-line no-continue
197
211
  inconclusive = true;
198
212
  break;
199
213
  }
package/src/N3Parser.js CHANGED
@@ -54,6 +54,10 @@ export default class N3Parser {
54
54
  // Disable parsing of unsupported versions by default
55
55
  this._parseUnsupportedVersions = !!options.parseUnsupportedVersions;
56
56
  this._version = options.version;
57
+ // Maximum nesting depth of triple terms (default 1024; Infinity disables the limit)
58
+ const maxTripleTermDepth = options.maxTripleTermDepth;
59
+ this._maxTripleTermDepth = maxTripleTermDepth === Infinity ||
60
+ Number.isInteger(maxTripleTermDepth) && maxTripleTermDepth >= 0 ? maxTripleTermDepth : 1024;
57
61
  }
58
62
 
59
63
  // ## Static class methods
@@ -88,6 +92,19 @@ export default class N3Parser {
88
92
  }
89
93
  }
90
94
 
95
+ // ### `_enterTripleTerm` stores the current parsing context when entering a triple term,
96
+ // failing if triple terms nest deeper than the maximum depth
97
+ _enterTripleTerm(type, subject, predicate, token) {
98
+ if (this._tripleTermDepth >= this._maxTripleTermDepth) {
99
+ this._error(`Triple terms nested deeper than ${this._maxTripleTermDepth} level${this._maxTripleTermDepth === 1 ? '' : 's'}`, token);
100
+ return false;
101
+ }
102
+ this._tripleTermDepth++;
103
+ this._saveContext(type, this._graph, subject, predicate, null);
104
+ this._graph = null;
105
+ return true;
106
+ }
107
+
91
108
  // ### `_saveContext` stores the current parsing context
92
109
  // when entering a new scope (list, blank node, formula)
93
110
  _saveContext(type, graph, subject, predicate, object) {
@@ -338,13 +355,9 @@ export default class N3Parser {
338
355
  case '<<(':
339
356
  if (!this._n3Mode)
340
357
  return this._error('Disallowed triple term as subject', token);
341
- this._saveContext('<<(', this._graph, null, null, null);
342
- this._graph = null;
343
- return this._readSubject;
358
+ return this._enterTripleTerm('<<(', null, null, token) && this._readSubject;
344
359
  case '<<':
345
- this._saveContext('<<', this._graph, null, null, null);
346
- this._graph = null;
347
- return this._readSubject;
360
+ return this._enterTripleTerm('<<', null, null, token) && this._readSubject;
348
361
  default:
349
362
  // Read the subject entity
350
363
  if ((this._subject = this._readEntity(token)) === undefined)
@@ -489,13 +502,9 @@ export default class N3Parser {
489
502
  this._graph = this._factory.blankNode());
490
503
  return this._readInFormulaContext;
491
504
  case '<<(':
492
- this._saveContext('<<(', this._graph, this._subject, this._predicate, null);
493
- this._graph = null;
494
- return this._readSubject;
505
+ return this._enterTripleTerm('<<(', this._subject, this._predicate, token) && this._readSubject;
495
506
  case '<<':
496
- this._saveContext('<<', this._graph, this._subject, this._predicate, null);
497
- this._graph = null;
498
- return this._readSubject;
507
+ return this._enterTripleTerm('<<', this._subject, this._predicate, token) && this._readSubject;
499
508
  default:
500
509
  // Read the object entity
501
510
  if ((this._object = this._readEntity(token)) === undefined)
@@ -715,13 +724,9 @@ export default class N3Parser {
715
724
  this._subject = null;
716
725
  return this._readInFormulaContext;
717
726
  case '<<(':
718
- this._saveContext('<<(', this._graph, null, null, null);
719
- this._graph = null;
720
- next = this._readSubject;
721
- break;
722
727
  case '<<':
723
- this._saveContext('<<', this._graph, null, null, null);
724
- this._graph = null;
728
+ if (!this._enterTripleTerm(token.type, null, null, token))
729
+ return;
725
730
  next = this._readSubject;
726
731
  break;
727
732
  default:
@@ -1271,6 +1276,7 @@ export default class N3Parser {
1271
1276
  const quad = this._createQuad(this._subject, this._predicate, this._object,
1272
1277
  this._graph, this._inversePredicate);
1273
1278
  this._restoreContext('<<(', token);
1279
+ this._tripleTermDepth--;
1274
1280
 
1275
1281
  // If we're in a list, continue processing that list
1276
1282
  const stack = this._contextStack, parent = stack.length && stack[stack.length - 1];
@@ -1306,6 +1312,7 @@ export default class N3Parser {
1306
1312
  this._tripleTerm = null;
1307
1313
  const reifier = this._readTripleTerm();
1308
1314
  this._restoreContext('<<', token);
1315
+ this._tripleTermDepth--;
1309
1316
 
1310
1317
  // // If we're in a list, continue processing that list
1311
1318
  const stack = this._contextStack, parent = stack.length && stack[stack.length - 1];
@@ -1651,6 +1658,7 @@ export default class N3Parser {
1651
1658
  this._prefixChanges = null;
1652
1659
  this._quantifiedChanges = null;
1653
1660
  this._emptyFormula = false;
1661
+ this._tripleTermDepth = 0;
1654
1662
 
1655
1663
  let readToken = token => {
1656
1664
  return this._readCallback = this._readCallback(token);
package/src/N3Writer.js CHANGED
@@ -9,9 +9,15 @@ const DEFAULTGRAPH = N3DataFactory.defaultGraph();
9
9
 
10
10
  const { rdf, xsd } = namespaces;
11
11
 
12
- // Characters in literals that require escaping
12
+ // Characters that require escaping in Turtle, TriG, and N3,
13
+ // where characters outside the Basic Multilingual Plane are escaped as well
13
14
  const escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/,
14
15
  escapeAll = /["\\\t\n\r\b\f\u0000-\u0019]|[\ud800-\udbff][\udc00-\udfff]/g,
16
+ // Characters that require escaping in canonical N-Triples and N-Quads:
17
+ // U+0000–U+001F, `"`, `\`, U+007F, U+FFFE, and U+FFFF.
18
+ // A negated class is as fast to test as a shorter class listing characters.
19
+ canonicalEscape = /[^ !#-\[\]-~\u0080-\uFFFD]/,
20
+ canonicalEscapeAll = /[^ !#-\[\]-~\u0080-\uFFFD]/g,
15
21
  escapedCharacters = {
16
22
  '\\': '\\\\', '"': '\\"', '\t': '\\t',
17
23
  '\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f',
@@ -57,8 +63,11 @@ export default class N3Writer {
57
63
  this._subject = null;
58
64
  if (!(/triple|quad/i).test(options.format)) {
59
65
  this._lineMode = false;
66
+ this._escape = escape, this._escapeAll = escapeAll, this._characterReplacer = characterReplacer;
60
67
  this._graph = DEFAULTGRAPH;
61
68
  this._prefixIRIs = Object.create(null);
69
+ // Escaped prefix IRIs and names for the prefix matcher, computed once per prefix
70
+ this._prefixPatterns = Object.create(null);
62
71
  if (options.baseIRI) {
63
72
  this._baseIri = new BaseIRI(options.baseIRI);
64
73
  if (options.writeBase)
@@ -69,6 +78,9 @@ export default class N3Writer {
69
78
  else {
70
79
  this._lineMode = true;
71
80
  this._writeQuad = this._writeQuadLine;
81
+ // N-Triples and N-Quads are written in their canonical form
82
+ this._escape = canonicalEscape, this._escapeAll = canonicalEscapeAll;
83
+ this._characterReplacer = canonicalCharacterReplacer;
72
84
  }
73
85
  }
74
86
 
@@ -174,8 +186,8 @@ export default class N3Writer {
174
186
  iri = this._baseIri.toRelative(iri);
175
187
  }
176
188
  // Escape special characters
177
- if (escape.test(iri))
178
- iri = iri.replace(escapeAll, characterReplacer);
189
+ if (this._escape.test(iri))
190
+ iri = iri.replace(this._escapeAll, this._characterReplacer);
179
191
  // Try to represent the IRI as prefixed name, unless no prefixes were added
180
192
  const prefixMatch = this._hasPrefixes ? this._prefixRegex.exec(iri) : null;
181
193
  return !prefixMatch ? `<${iri}>` :
@@ -186,8 +198,8 @@ export default class N3Writer {
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,18 +330,19 @@ 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), escapeRegex(prefix)];
319
334
  this._write(`@prefix ${prefix} <${iri}>.\n`);
320
335
  }
321
336
  // Recreate the prefix matcher
322
337
  if (hasPrefixes) {
323
338
  this._hasPrefixes = true;
324
339
  let IRIlist = '', prefixList = '';
325
- for (const prefixIRI in this._prefixIRIs) {
326
- IRIlist += IRIlist ? `|${prefixIRI}` : prefixIRI;
327
- prefixList += (prefixList ? '|' : '') + this._prefixIRIs[prefixIRI];
340
+ for (const prefixIRI in this._prefixPatterns) {
341
+ const [IRIpattern, prefixPattern] = this._prefixPatterns[prefixIRI];
342
+ IRIlist += IRIlist ? `|${IRIpattern}` : IRIpattern;
343
+ prefixList += prefixList ? `|${prefixPattern}` : prefixPattern;
328
344
  }
329
- IRIlist = escapeRegex(IRIlist, /[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
330
- this._prefixRegex = new RegExp(`^(?:${prefixList})[^\/]*$|` +
345
+ this._prefixRegex = new RegExp(`^(?:${prefixList})[^/]*$|` +
331
346
  `^(${IRIlist})([_a-zA-Z0-9](?:\\.?[\\-_a-zA-Z0-9])*)$`);
332
347
  }
333
348
  // End a prefix block with a newline
@@ -425,3 +440,14 @@ function characterReplacer(character) {
425
440
  }
426
441
  return result;
427
442
  }
443
+
444
+ // Replaces a character by its canonical N-Triples escape:
445
+ // a short escape where one exists, and an upper-case `\uXXXX` escape otherwise
446
+ function canonicalCharacterReplacer(character) {
447
+ let result = escapedCharacters[character];
448
+ if (result === undefined) {
449
+ result = character.charCodeAt(0).toString(16).toUpperCase();
450
+ result = '\\u0000'.substr(0, 6 - result.length) + result;
451
+ }
452
+ return result;
453
+ }
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
  }