n3 2.12.1 → 2.13.0

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
@@ -116,8 +116,8 @@ class N3Lexer {
116
116
  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#()\[\]\{\}"'<>]))/y;
117
117
  this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
118
118
  this._boolean = /(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
119
- this._atKeyword = /@[a-z]+(?=[\s#<:])/iy;
120
- this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/iy;
119
+ this._atKeyword = /@[a-z]+(?=[\s#<:"'])/iy;
120
+ this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<"'])/iy;
121
121
  this._n3Verb = /(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/y;
122
122
  this._n3Id = /id(?=[\s#<])/y;
123
123
  this._shortPredicates = /a(?=[\s#()\[\]\{\}"'<>])/y;
@@ -135,6 +135,8 @@ class N3Lexer {
135
135
  for (const key in this) {
136
136
  if (!(key in lineModeRegExps) && this[key] instanceof RegExp) this[key] = invalidRegExp;
137
137
  }
138
+ // The only keyword in N-Triples and N-Quads is VERSION, which is case-sensitive
139
+ this._keyword = /VERSION(?=[\s#<])/y;
138
140
  }
139
141
  // When not in line mode, enable N3 functionality by default
140
142
  else {
@@ -236,7 +238,8 @@ class N3Lexer {
236
238
  matchLength = 0,
237
239
  lexicalLength = 0,
238
240
  finalLineLength = 0,
239
- inconclusive = false;
241
+ inconclusive = false,
242
+ tripleQuoted = false;
240
243
  switch (firstChar) {
241
244
  case '^':
242
245
  // A datatype marker separated from its type cannot be followed by another marker
@@ -310,7 +313,8 @@ class N3Lexer {
310
313
  ({
311
314
  value,
312
315
  matchLength,
313
- finalLineLength
316
+ finalLineLength,
317
+ tripleQuoted
314
318
  } = this._parseLiteral(input, pos));
315
319
  if (value === null) return reportSyntaxError(this, input, pos);
316
320
  }
@@ -328,7 +332,8 @@ class N3Lexer {
328
332
  ({
329
333
  value,
330
334
  matchLength,
331
- finalLineLength
335
+ finalLineLength,
336
+ tripleQuoted
332
337
  } = this._parseLiteral(input, pos));
333
338
  if (value === null) return reportSyntaxError(this, input, pos);
334
339
  }
@@ -521,7 +526,21 @@ class N3Lexer {
521
526
  line,
522
527
  start,
523
528
  end: finalLineLength,
524
- endLine: this._line
529
+ endLine: this._line,
530
+ tripleQuoted
531
+ };
532
+ callback(null, token);
533
+ }
534
+ // Triple-quoted strings are marked, since version declarations do not allow them
535
+ else if (tripleQuoted) {
536
+ token = {
537
+ type,
538
+ value,
539
+ prefix,
540
+ line,
541
+ start,
542
+ end: start + length,
543
+ tripleQuoted
525
544
  };
526
545
  callback(null, token);
527
546
  } else token = emitToken(type, value, prefix, line, start, lexicalLength || length);
@@ -671,7 +690,8 @@ class N3Lexer {
671
690
  return {
672
691
  value: this._unescape(raw, stringEscapeReplacements),
673
692
  matchLength,
674
- finalLineLength
693
+ finalLineLength,
694
+ tripleQuoted: openingLength === 3
675
695
  };
676
696
  }
677
697
  closingPos++;
@@ -681,7 +701,8 @@ class N3Lexer {
681
701
  return {
682
702
  value: '',
683
703
  matchLength: 0,
684
- finalLineLength: 0
704
+ finalLineLength: 0,
705
+ tripleQuoted: false
685
706
  };
686
707
  }
687
708
 
package/lib/N3Parser.js CHANGED
@@ -1025,7 +1025,8 @@ class N3Parser {
1025
1025
  // ### `_readVersion` reads version string declaration
1026
1026
  _readVersion(token) {
1027
1027
  if (token.type !== 'literal') return this._error('Expected literal to follow version declaration', token);
1028
- if (token.end - token.start !== token.value.length + 2) return this._error('Version declarations must use single quotes', token);
1028
+ // Only short strings are allowed, so no numbers or booleans (which have a datatype prefix)
1029
+ if (token.prefix !== '' || token.tripleQuoted) return this._error('Version declarations must use single quotes', token);
1029
1030
  this._versionCallback(token.value);
1030
1031
  if (!this._isValidVersion(token.value)) return this._error(`Detected unsupported version: "${token.value}"`, token);
1031
1032
  return this._readDeclarationPunctuation;
package/lib/N3Writer.js CHANGED
@@ -41,6 +41,9 @@ const escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/,
41
41
  '\f': '\\f'
42
42
  };
43
43
 
44
+ // Characters that a version label cannot contain
45
+ const invalidVersionLabel = /["\\\u0000-\u001f\u007f]|[\ud800-\udbff](?![\udc00-\udfff])|(?:^|[^\ud800-\udbff])[\udc00-\udfff]/;
46
+
44
47
  // A local name (PN_LOCAL) as written in a prefixed name, without backslash escapes,
45
48
  // so that it denotes the same characters as the IRI it is taken from.
46
49
  // Characters outside the Basic Multilingual Plane are escaped before matching, so they never occur.
@@ -88,12 +91,17 @@ class N3Writer {
88
91
  this._endStream = options.end === undefined ? true : !!options.end;
89
92
  }
90
93
 
94
+ // A version label is written as-is, so it cannot contain characters that need escaping,
95
+ // nor unpaired surrogates (which cannot be encoded)
96
+ if (options.version && invalidVersionLabel.test(options.version)) throw new Error(`Invalid version label: ${JSON.stringify(options.version)}`);
97
+
91
98
  // Initialize writer, depending on the format
92
99
  this._subject = null;
93
100
  if (!/triple|quad/i.test(options.format)) {
94
101
  this._lineMode = false;
95
102
  this._escape = escape, this._escapeAll = escapeAll, this._characterReplacer = characterReplacer;
96
103
  this._graph = DEFAULTGRAPH;
104
+ if (options.version) this._write(`@version "${options.version}".\n`);
97
105
  this._prefixIRIs = Object.create(null);
98
106
  // Escaped prefix IRIs and names for the prefix matcher, computed once per prefix
99
107
  this._prefixPatterns = Object.create(null);
@@ -108,6 +116,7 @@ class N3Writer {
108
116
  // N-Triples and N-Quads are written in their canonical form
109
117
  this._escape = canonicalEscape, this._escapeAll = canonicalEscapeAll;
110
118
  this._characterReplacer = canonicalCharacterReplacer;
119
+ if (options.version) this._write(`VERSION "${options.version}"\n`);
111
120
  }
112
121
  }
113
122
 
@@ -123,6 +132,15 @@ class N3Writer {
123
132
  this._outputStream.write(string, 'utf8', callback);
124
133
  }
125
134
 
135
+ // ### `_endStatement` finishes a pending statement and closes an open graph block
136
+ _endStatement() {
137
+ if (this._subject !== null) {
138
+ this._write(this._inDefaultGraph ? '.\n' : '\n}\n');
139
+ this._subject = null;
140
+ this._graph = DEFAULTGRAPH;
141
+ }
142
+ }
143
+
126
144
  // ### `_writeQuad` writes the quad to the output stream
127
145
  _writeQuad(subject, predicate, object, graph, done) {
128
146
  try {
@@ -325,10 +343,7 @@ class N3Writer {
325
343
  if (typeof iri !== 'string') iri = iri.value;
326
344
  hasPrefixes = true;
327
345
  // Finish a possible pending quad
328
- if (this._subject !== null) {
329
- this._write(this._inDefaultGraph ? '.\n' : '\n}\n');
330
- this._subject = null, this._graph = '';
331
- }
346
+ this._endStatement();
332
347
  // Store and write the prefix
333
348
  this._prefixIRIs[iri] = prefix += ':';
334
349
  this._prefixPatterns[iri] = [(0, _Util.escapeRegex)(iri), (0, _Util.escapeRegex)(prefix)];
@@ -407,10 +422,7 @@ class N3Writer {
407
422
  // ### `end` signals the end of the output stream
408
423
  end(done) {
409
424
  // Finish a possible pending quad
410
- if (this._subject !== null) {
411
- this._write(this._inDefaultGraph ? '.\n' : '\n}\n');
412
- this._subject = null;
413
- }
425
+ this._endStatement();
414
426
  // Disallow further writing
415
427
  this._write = this._blockedWrite;
416
428
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "2.12.1",
3
+ "version": "2.13.0",
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
@@ -81,8 +81,8 @@ export default class N3Lexer {
81
81
  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#()\[\]\{\}"'<>]))/y;
82
82
  this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
83
83
  this._boolean = /(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
84
- this._atKeyword = /@[a-z]+(?=[\s#<:])/iy;
85
- this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/iy;
84
+ this._atKeyword = /@[a-z]+(?=[\s#<:"'])/iy;
85
+ this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<"'])/iy;
86
86
  this._n3Verb = /(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/y;
87
87
  this._n3Id = /id(?=[\s#<])/y;
88
88
  this._shortPredicates = /a(?=[\s#()\[\]\{\}"'<>])/y;
@@ -101,6 +101,8 @@ export default class N3Lexer {
101
101
  if (!(key in lineModeRegExps) && this[key] instanceof RegExp)
102
102
  this[key] = invalidRegExp;
103
103
  }
104
+ // The only keyword in N-Triples and N-Quads is VERSION, which is case-sensitive
105
+ this._keyword = /VERSION(?=[\s#<])/y;
104
106
  }
105
107
  // When not in line mode, enable N3 functionality by default
106
108
  else {
@@ -208,7 +210,7 @@ export default class N3Lexer {
208
210
  const line = this._line, firstChar = input[pos];
209
211
  let type = '', value = '', prefix = '',
210
212
  match = null, matchLength = 0, lexicalLength = 0,
211
- finalLineLength = 0, inconclusive = false;
213
+ finalLineLength = 0, inconclusive = false, tripleQuoted = false;
212
214
  switch (firstChar) {
213
215
  case '^':
214
216
  // A datatype marker separated from its type cannot be followed by another marker
@@ -293,7 +295,7 @@ export default class N3Lexer {
293
295
  value = match[1];
294
296
  // Try to find a literal wrapped in three pairs of quotes
295
297
  else {
296
- ({ value, matchLength, finalLineLength } = this._parseLiteral(input, pos));
298
+ ({ value, matchLength, finalLineLength, tripleQuoted } = this._parseLiteral(input, pos));
297
299
  if (value === null)
298
300
  return reportSyntaxError(this, input, pos);
299
301
  }
@@ -310,7 +312,7 @@ export default class N3Lexer {
310
312
  value = match[1];
311
313
  // Try to find a literal wrapped in three pairs of quotes
312
314
  else {
313
- ({ value, matchLength, finalLineLength } = this._parseLiteral(input, pos));
315
+ ({ value, matchLength, finalLineLength, tripleQuoted } = this._parseLiteral(input, pos));
314
316
  if (value === null)
315
317
  return reportSyntaxError(this, input, pos);
316
318
  }
@@ -550,10 +552,15 @@ export default class N3Lexer {
550
552
  if (finalLineLength) {
551
553
  token = {
552
554
  type, value, prefix, line, start,
553
- end: finalLineLength, endLine: this._line,
555
+ end: finalLineLength, endLine: this._line, tripleQuoted,
554
556
  };
555
557
  callback(null, token);
556
558
  }
559
+ // Triple-quoted strings are marked, since version declarations do not allow them
560
+ else if (tripleQuoted) {
561
+ token = { type, value, prefix, line, start, end: start + length, tripleQuoted };
562
+ callback(null, token);
563
+ }
557
564
  else
558
565
  token = emitToken(type, value, prefix, line, start, lexicalLength || length);
559
566
  this.previousToken = token;
@@ -705,13 +712,16 @@ export default class N3Lexer {
705
712
  break;
706
713
  this._line += lineCount;
707
714
  const finalLineLength = lineCount === 0 ? 0 : lines[lines.length - 1].length + openingLength;
708
- return { value: this._unescape(raw, stringEscapeReplacements), matchLength, finalLineLength };
715
+ return {
716
+ value: this._unescape(raw, stringEscapeReplacements), matchLength, finalLineLength,
717
+ tripleQuoted: openingLength === 3,
718
+ };
709
719
  }
710
720
  closingPos++;
711
721
  }
712
722
  this._literalClosingPos = input.length - pos - openingLength + 1;
713
723
  }
714
- return { value: '', matchLength: 0, finalLineLength: 0 };
724
+ return { value: '', matchLength: 0, finalLineLength: 0, tripleQuoted: false };
715
725
  }
716
726
 
717
727
  // ### `_tryTokenizeToEnd` tokenizes as far as possible, reporting failures through the callback
package/src/N3Parser.js CHANGED
@@ -1111,7 +1111,8 @@ export default class N3Parser {
1111
1111
  _readVersion(token) {
1112
1112
  if (token.type !== 'literal')
1113
1113
  return this._error('Expected literal to follow version declaration', token);
1114
- if ((token.end - token.start) !== token.value.length + 2)
1114
+ // Only short strings are allowed, so no numbers or booleans (which have a datatype prefix)
1115
+ if (token.prefix !== '' || token.tripleQuoted)
1115
1116
  return this._error('Version declarations must use single quotes', token);
1116
1117
  this._versionCallback(token.value);
1117
1118
  if (!this._isValidVersion(token.value))
package/src/N3Writer.js CHANGED
@@ -24,6 +24,9 @@ const escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/,
24
24
  '\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f',
25
25
  };
26
26
 
27
+ // Characters that a version label cannot contain
28
+ const invalidVersionLabel = /["\\\u0000-\u001f\u007f]|[\ud800-\udbff](?![\udc00-\udfff])|(?:^|[^\ud800-\udbff])[\udc00-\udfff]/;
29
+
27
30
  // A local name (PN_LOCAL) as written in a prefixed name, without backslash escapes,
28
31
  // so that it denotes the same characters as the IRI it is taken from.
29
32
  // Characters outside the Basic Multilingual Plane are escaped before matching, so they never occur.
@@ -70,12 +73,19 @@ export default class N3Writer {
70
73
  this._endStream = options.end === undefined ? true : !!options.end;
71
74
  }
72
75
 
76
+ // A version label is written as-is, so it cannot contain characters that need escaping,
77
+ // nor unpaired surrogates (which cannot be encoded)
78
+ if (options.version && invalidVersionLabel.test(options.version))
79
+ throw new Error(`Invalid version label: ${JSON.stringify(options.version)}`);
80
+
73
81
  // Initialize writer, depending on the format
74
82
  this._subject = null;
75
83
  if (!(/triple|quad/i).test(options.format)) {
76
84
  this._lineMode = false;
77
85
  this._escape = escape, this._escapeAll = escapeAll, this._characterReplacer = characterReplacer;
78
86
  this._graph = DEFAULTGRAPH;
87
+ if (options.version)
88
+ this._write(`@version "${options.version}".\n`);
79
89
  this._prefixIRIs = Object.create(null);
80
90
  // Escaped prefix IRIs and names for the prefix matcher, computed once per prefix
81
91
  this._prefixPatterns = Object.create(null);
@@ -92,6 +102,8 @@ export default class N3Writer {
92
102
  // N-Triples and N-Quads are written in their canonical form
93
103
  this._escape = canonicalEscape, this._escapeAll = canonicalEscapeAll;
94
104
  this._characterReplacer = canonicalCharacterReplacer;
105
+ if (options.version)
106
+ this._write(`VERSION "${options.version}"\n`);
95
107
  }
96
108
  }
97
109
 
@@ -107,6 +119,15 @@ export default class N3Writer {
107
119
  this._outputStream.write(string, 'utf8', callback);
108
120
  }
109
121
 
122
+ // ### `_endStatement` finishes a pending statement and closes an open graph block
123
+ _endStatement() {
124
+ if (this._subject !== null) {
125
+ this._write(this._inDefaultGraph ? '.\n' : '\n}\n');
126
+ this._subject = null;
127
+ this._graph = DEFAULTGRAPH;
128
+ }
129
+ }
130
+
110
131
  // ### `_writeQuad` writes the quad to the output stream
111
132
  _writeQuad(subject, predicate, object, graph, done) {
112
133
  try {
@@ -340,10 +361,7 @@ export default class N3Writer {
340
361
  iri = iri.value;
341
362
  hasPrefixes = true;
342
363
  // Finish a possible pending quad
343
- if (this._subject !== null) {
344
- this._write(this._inDefaultGraph ? '.\n' : '\n}\n');
345
- this._subject = null, this._graph = '';
346
- }
364
+ this._endStatement();
347
365
  // Store and write the prefix
348
366
  this._prefixIRIs[iri] = (prefix += ':');
349
367
  this._prefixPatterns[iri] = [escapeRegex(iri), escapeRegex(prefix)];
@@ -426,10 +444,7 @@ export default class N3Writer {
426
444
  // ### `end` signals the end of the output stream
427
445
  end(done) {
428
446
  // Finish a possible pending quad
429
- if (this._subject !== null) {
430
- this._write(this._inDefaultGraph ? '.\n' : '\n}\n');
431
- this._subject = null;
432
- }
447
+ this._endStatement();
433
448
  // Disallow further writing
434
449
  this._write = this._blockedWrite;
435
450