n3 2.2.11 → 2.2.13

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
@@ -13,9 +13,10 @@ const {
13
13
  xsd
14
14
  } = _IRIs.default;
15
15
 
16
- // Regular expression and replacement string to escape N3 strings
16
+ // Regular expression and replacement strings to unescape N3 strings
17
17
  const escapeSequence = /\\u([a-fA-F0-9]{4})|\\U([a-fA-F0-9]{8})|\\([^])/g;
18
- const escapeReplacements = {
18
+ // Fixed escape sequences allowed in string literals (ECHAR)
19
+ const stringEscapeReplacements = {
19
20
  '\\': '\\',
20
21
  "'": "'",
21
22
  '"': '"',
@@ -23,7 +24,10 @@ const escapeReplacements = {
23
24
  'r': '\r',
24
25
  't': '\t',
25
26
  'f': '\f',
26
- 'b': '\b',
27
+ 'b': '\b'
28
+ };
29
+ // Fixed escape sequences allowed in local names of prefixed names (PN_LOCAL_ESC)
30
+ const localNameEscapeReplacements = {
27
31
  '_': '_',
28
32
  '~': '~',
29
33
  '.': '.',
@@ -31,6 +35,7 @@ const escapeReplacements = {
31
35
  '!': '!',
32
36
  '$': '$',
33
37
  '&': '&',
38
+ "'": "'",
34
39
  '(': '(',
35
40
  ')': ')',
36
41
  '*': '*',
@@ -181,7 +186,7 @@ class N3Lexer {
181
186
  if (match = this._unescapedIri.exec(input)) type = 'IRI', value = match[1];
182
187
  // Try to find a full IRI with escape sequences
183
188
  else if (match = this._iri.exec(input)) {
184
- value = this._unescape(match[1]);
189
+ value = this._unescape(match[1], stringEscapeReplacements);
185
190
  if (value === null || illegalIriChars.test(value)) return reportSyntaxError(this);
186
191
  type = 'IRI';
187
192
  }
@@ -364,7 +369,7 @@ class N3Lexer {
364
369
  // Try to find a prefixed name. Since it can contain (but not end with) a dot,
365
370
  // we always need a non-dot character before deciding it is a prefixed name.
366
371
  // Therefore, try inserting a space if we're at the end of the input.
367
- else if ((match = this._prefixed.exec(input)) || inputFinished && (match = this._prefixed.exec(`${input} `))) type = 'prefixed', prefix = match[1] || '', value = this._unescape(match[2]);
372
+ else if ((match = this._prefixed.exec(input)) || inputFinished && (match = this._prefixed.exec(`${input} `))) type = 'prefixed', prefix = match[1] || '', value = this._unescape(match[2], localNameEscapeReplacements);
368
373
  }
369
374
 
370
375
  // A type token is special: it can only be emitted after an IRI or prefixed name is read
@@ -420,8 +425,9 @@ class N3Lexer {
420
425
  }
421
426
  }
422
427
 
423
- // ### `_unescape` replaces N3 escape codes by their corresponding characters
424
- _unescape(item) {
428
+ // ### `_unescape` replaces N3 escape codes by their corresponding characters,
429
+ // allowing only the fixed escape sequences from the given replacement table
430
+ _unescape(item, replacements) {
425
431
  let invalid = false;
426
432
  const replaced = item.replace(escapeSequence, (sequence, unicode4, unicode8, escapedChar) => {
427
433
  // 4-digit unicode character
@@ -443,7 +449,7 @@ class N3Lexer {
443
449
  return charCode <= 0xFFFF ? String.fromCharCode(Number.parseInt(unicode8, 16)) : String.fromCharCode(0xD800 + ((charCode -= 0x10000) >> 10), 0xDC00 + (charCode & 0x3FF));
444
450
  }
445
451
  // fixed escape sequence
446
- if (escapedChar in escapeReplacements) return escapeReplacements[escapedChar];
452
+ if (escapedChar in replacements) return replacements[escapedChar];
447
453
  // invalid escape sequence
448
454
  invalid = true;
449
455
  return '';
@@ -477,7 +483,7 @@ class N3Lexer {
477
483
  if (openingLength === 1 && lines !== 0 || openingLength === 3 && this._lineMode) break;
478
484
  this._line += lines;
479
485
  return {
480
- value: this._unescape(raw),
486
+ value: this._unescape(raw, stringEscapeReplacements),
481
487
  matchLength
482
488
  };
483
489
  }
package/lib/N3Parser.js CHANGED
@@ -36,6 +36,9 @@ class N3Parser {
36
36
  this._supportsQuads = !(isTurtle || isTriG || isNTriples || isN3);
37
37
  // Whether the log:isImpliedBy predicate is supported
38
38
  this._isImpliedBy = options.isImpliedBy;
39
+ // Whether an empty formula is read as the boolean literal true,
40
+ // as in the N3 spec tests (opt-in until the next major version)
41
+ this._emptyFormulaAsTrue = !!options.emptyFormulaAsTrue;
39
42
  // Disable relative IRIs in N-Triples or N-Quads mode
40
43
  if (isLineMode) this._resolveRelativeIRI = iri => {
41
44
  return null;
@@ -92,7 +95,8 @@ class N3Parser {
92
95
  graph,
93
96
  inverse: n3Mode ? this._inversePredicate : false,
94
97
  blankPrefix: n3Mode ? this._prefixes._ : '',
95
- quantified: n3Mode ? this._quantified : null
98
+ quantified: n3Mode ? this._quantified : null,
99
+ emptyFormula: n3Mode ? this._emptyFormula : false
96
100
  });
97
101
  // The settings below only apply to N3 streams
98
102
  if (n3Mode) {
@@ -103,6 +107,13 @@ class N3Parser {
103
107
  this._prefixes._ = this._graph ? `${this._graph.value}.` : '.';
104
108
  // Quantifiers are scoped to a formula
105
109
  this._quantified = Object.create(this._quantified);
110
+ // A formula starts a new statement, so the subject of the
111
+ // enclosing statement must not leak into it,
112
+ // and it is empty until a statement has been read
113
+ if (type === 'formula') {
114
+ this._subject = null;
115
+ this._emptyFormula = true;
116
+ }
106
117
  }
107
118
  }
108
119
 
@@ -124,6 +135,7 @@ class N3Parser {
124
135
  this._inversePredicate = context.inverse;
125
136
  this._prefixes._ = context.blankPrefix;
126
137
  this._quantified = context.quantified;
138
+ this._emptyFormula = context.emptyFormula;
127
139
  }
128
140
  }
129
141
 
@@ -209,6 +221,8 @@ class N3Parser {
209
221
  // ### `_readSubject` reads a quad's subject
210
222
  _readSubject(token) {
211
223
  this._predicate = null;
224
+ // Any statement token means the enclosing formula is not empty
225
+ if (token.type !== '}') this._emptyFormula = false;
212
226
  switch (token.type) {
213
227
  case '[':
214
228
  // Start a new quad with a new blank node as subject
@@ -508,7 +522,23 @@ class N3Parser {
508
522
  case '{':
509
523
  // Start a new formula
510
524
  if (!this._n3Mode) return this._error('Unexpected graph', token);
511
- this._saveContext('formula', this._graph, this._subject, this._predicate, this._graph = this._factory.blankNode());
525
+ // The formula is an item of the list,
526
+ // so it must be linked in the list's graph before the graph changes
527
+ list = this._factory.blankNode();
528
+ item = this._factory.blankNode();
529
+ // Is this the first element of the list?
530
+ if (previousList === null) {
531
+ // This list is either the subject or the object of its parent
532
+ if (parent.predicate === null) parent.subject = list;else parent.object = list;
533
+ } else {
534
+ // Continue the previous list with the current list
535
+ this._emit(previousList, this.RDF_REST, list, this._graph);
536
+ }
537
+ // Output the item
538
+ this._emit(list, this.RDF_FIRST, item, this._graph);
539
+ // Stack the current list quad and start the formula
540
+ this._saveContext('formula', this._graph, list, this.RDF_FIRST, this._graph = item);
541
+ this._subject = null;
512
542
  return this._readSubject;
513
543
  case '<<(':
514
544
  this._saveContext('<<(', this._graph, null, null, null);
@@ -694,9 +724,18 @@ class N3Parser {
694
724
 
695
725
  // Store the last quad of the formula
696
726
  if (this._subject !== null) this._emit(this._subject, this._predicate, this._object, this._graph);
697
-
727
+ const formula = this._graph,
728
+ empty = this._emptyFormula;
698
729
  // Restore the parent context containing this formula
699
730
  this._restoreContext('formula', token);
731
+
732
+ // When the emptyFormulaAsTrue option is set, an empty formula
733
+ // is read as the boolean literal true, following the N3 spec tests
734
+ // and the direction discussed in https://github.com/w3c-cg/N3/issues/185
735
+ if (empty && this._emptyFormulaAsTrue) {
736
+ if (this._subject === formula) this._subject = this.N3_TRUE;else this._object = this.N3_TRUE;
737
+ }
738
+
700
739
  // If the formula was the subject, continue reading the predicate.
701
740
  // If the formula was the object, read punctuation.
702
741
  return this._object === null ? this._readPredicate : this._getContextEndReader();
@@ -1277,6 +1316,7 @@ class N3Parser {
1277
1316
  this._versionCallback = onVersion || noop;
1278
1317
  this._inversePredicate = false;
1279
1318
  this._quantified = Object.create(null);
1319
+ this._emptyFormula = false;
1280
1320
 
1281
1321
  // Parse synchronously if no quad callback is given
1282
1322
  if (!onQuad) {
@@ -1329,6 +1369,7 @@ function initDataFactory(parser, factory) {
1329
1369
  parser.RDF_REIFIES = factory.namedNode(_IRIs.default.rdf.reifies);
1330
1370
  parser.N3_FORALL = factory.namedNode(_IRIs.default.r.forAll);
1331
1371
  parser.N3_FORSOME = factory.namedNode(_IRIs.default.r.forSome);
1372
+ parser.N3_TRUE = factory.literal('true', factory.namedNode(_IRIs.default.xsd.boolean));
1332
1373
  parser.ABBREVIATIONS = {
1333
1374
  'a': factory.namedNode(_IRIs.default.rdf.type),
1334
1375
  '=': factory.namedNode(_IRIs.default.owl.sameAs),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "2.2.11",
3
+ "version": "2.2.13",
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
@@ -4,14 +4,18 @@ import namespaces from './IRIs';
4
4
 
5
5
  const { xsd } = namespaces;
6
6
 
7
- // Regular expression and replacement string to escape N3 strings
7
+ // Regular expression and replacement strings to unescape N3 strings
8
8
  const escapeSequence = /\\u([a-fA-F0-9]{4})|\\U([a-fA-F0-9]{8})|\\([^])/g;
9
- const escapeReplacements = {
9
+ // Fixed escape sequences allowed in string literals (ECHAR)
10
+ const stringEscapeReplacements = {
10
11
  '\\': '\\', "'": "'", '"': '"',
11
12
  'n': '\n', 'r': '\r', 't': '\t', 'f': '\f', 'b': '\b',
13
+ };
14
+ // Fixed escape sequences allowed in local names of prefixed names (PN_LOCAL_ESC)
15
+ const localNameEscapeReplacements = {
12
16
  '_': '_', '~': '~', '.': '.', '-': '-', '!': '!', '$': '$', '&': '&',
13
- '(': '(', ')': ')', '*': '*', '+': '+', ',': ',', ';': ';', '=': '=',
14
- '/': '/', '?': '?', '#': '#', '@': '@', '%': '%',
17
+ "'": "'", '(': '(', ')': ')', '*': '*', '+': '+', ',': ',', ';': ';',
18
+ '=': '=', '/': '/', '?': '?', '#': '#', '@': '@', '%': '%',
15
19
  };
16
20
  const illegalIriChars = /[\x00-\x20<>\\"\{\}\|\^\`]/;
17
21
 
@@ -152,7 +156,7 @@ export default class N3Lexer {
152
156
  type = 'IRI', value = match[1];
153
157
  // Try to find a full IRI with escape sequences
154
158
  else if (match = this._iri.exec(input)) {
155
- value = this._unescape(match[1]);
159
+ value = this._unescape(match[1], stringEscapeReplacements);
156
160
  if (value === null || illegalIriChars.test(value))
157
161
  return reportSyntaxError(this);
158
162
  type = 'IRI';
@@ -382,7 +386,7 @@ export default class N3Lexer {
382
386
  // Therefore, try inserting a space if we're at the end of the input.
383
387
  else if ((match = this._prefixed.exec(input)) ||
384
388
  inputFinished && (match = this._prefixed.exec(`${input} `)))
385
- type = 'prefixed', prefix = match[1] || '', value = this._unescape(match[2]);
389
+ type = 'prefixed', prefix = match[1] || '', value = this._unescape(match[2], localNameEscapeReplacements);
386
390
  }
387
391
 
388
392
  // A type token is special: it can only be emitted after an IRI or prefixed name is read
@@ -427,8 +431,9 @@ export default class N3Lexer {
427
431
  function reportSyntaxError(self) { callback(self._syntaxError(/^\S*/.exec(input)[0])); }
428
432
  }
429
433
 
430
- // ### `_unescape` replaces N3 escape codes by their corresponding characters
431
- _unescape(item) {
434
+ // ### `_unescape` replaces N3 escape codes by their corresponding characters,
435
+ // allowing only the fixed escape sequences from the given replacement table
436
+ _unescape(item, replacements) {
432
437
  let invalid = false;
433
438
  const replaced = item.replace(escapeSequence, (sequence, unicode4, unicode8, escapedChar) => {
434
439
  // 4-digit unicode character
@@ -451,8 +456,8 @@ export default class N3Lexer {
451
456
  String.fromCharCode(0xD800 + ((charCode -= 0x10000) >> 10), 0xDC00 + (charCode & 0x3FF));
452
457
  }
453
458
  // fixed escape sequence
454
- if (escapedChar in escapeReplacements)
455
- return escapeReplacements[escapedChar];
459
+ if (escapedChar in replacements)
460
+ return replacements[escapedChar];
456
461
  // invalid escape sequence
457
462
  invalid = true;
458
463
  return '';
@@ -488,7 +493,7 @@ export default class N3Lexer {
488
493
  openingLength === 3 && this._lineMode)
489
494
  break;
490
495
  this._line += lines;
491
- return { value: this._unescape(raw), matchLength };
496
+ return { value: this._unescape(raw, stringEscapeReplacements), matchLength };
492
497
  }
493
498
  closingPos++;
494
499
  }
package/src/N3Parser.js CHANGED
@@ -29,6 +29,9 @@ export default class N3Parser {
29
29
  this._supportsQuads = !(isTurtle || isTriG || isNTriples || isN3);
30
30
  // Whether the log:isImpliedBy predicate is supported
31
31
  this._isImpliedBy = options.isImpliedBy;
32
+ // Whether an empty formula is read as the boolean literal true,
33
+ // as in the N3 spec tests (opt-in until the next major version)
34
+ this._emptyFormulaAsTrue = !!options.emptyFormulaAsTrue;
32
35
  // Disable relative IRIs in N-Triples or N-Quads mode
33
36
  if (isLineMode)
34
37
  this._resolveRelativeIRI = iri => { return null; };
@@ -82,6 +85,7 @@ export default class N3Parser {
82
85
  inverse: n3Mode ? this._inversePredicate : false,
83
86
  blankPrefix: n3Mode ? this._prefixes._ : '',
84
87
  quantified: n3Mode ? this._quantified : null,
88
+ emptyFormula: n3Mode ? this._emptyFormula : false,
85
89
  });
86
90
  // The settings below only apply to N3 streams
87
91
  if (n3Mode) {
@@ -92,6 +96,13 @@ export default class N3Parser {
92
96
  this._prefixes._ = (this._graph ? `${this._graph.value}.` : '.');
93
97
  // Quantifiers are scoped to a formula
94
98
  this._quantified = Object.create(this._quantified);
99
+ // A formula starts a new statement, so the subject of the
100
+ // enclosing statement must not leak into it,
101
+ // and it is empty until a statement has been read
102
+ if (type === 'formula') {
103
+ this._subject = null;
104
+ this._emptyFormula = true;
105
+ }
95
106
  }
96
107
  }
97
108
 
@@ -114,6 +125,7 @@ export default class N3Parser {
114
125
  this._inversePredicate = context.inverse;
115
126
  this._prefixes._ = context.blankPrefix;
116
127
  this._quantified = context.quantified;
128
+ this._emptyFormula = context.emptyFormula;
117
129
  }
118
130
  }
119
131
 
@@ -205,6 +217,9 @@ export default class N3Parser {
205
217
  // ### `_readSubject` reads a quad's subject
206
218
  _readSubject(token) {
207
219
  this._predicate = null;
220
+ // Any statement token means the enclosing formula is not empty
221
+ if (token.type !== '}')
222
+ this._emptyFormula = false;
208
223
  switch (token.type) {
209
224
  case '[':
210
225
  // Start a new quad with a new blank node as subject
@@ -538,8 +553,28 @@ export default class N3Parser {
538
553
  // Start a new formula
539
554
  if (!this._n3Mode)
540
555
  return this._error('Unexpected graph', token);
541
- this._saveContext('formula', this._graph, this._subject, this._predicate,
542
- this._graph = this._factory.blankNode());
556
+ // The formula is an item of the list,
557
+ // so it must be linked in the list's graph before the graph changes
558
+ list = this._factory.blankNode();
559
+ item = this._factory.blankNode();
560
+ // Is this the first element of the list?
561
+ if (previousList === null) {
562
+ // This list is either the subject or the object of its parent
563
+ if (parent.predicate === null)
564
+ parent.subject = list;
565
+ else
566
+ parent.object = list;
567
+ }
568
+ else {
569
+ // Continue the previous list with the current list
570
+ this._emit(previousList, this.RDF_REST, list, this._graph);
571
+ }
572
+ // Output the item
573
+ this._emit(list, this.RDF_FIRST, item, this._graph);
574
+ // Stack the current list quad and start the formula
575
+ this._saveContext('formula', this._graph, list, this.RDF_FIRST,
576
+ this._graph = item);
577
+ this._subject = null;
543
578
  return this._readSubject;
544
579
  case '<<(':
545
580
  this._saveContext('<<(', this._graph, null, null, null);
@@ -754,8 +789,20 @@ export default class N3Parser {
754
789
  if (this._subject !== null)
755
790
  this._emit(this._subject, this._predicate, this._object, this._graph);
756
791
 
792
+ const formula = this._graph, empty = this._emptyFormula;
757
793
  // Restore the parent context containing this formula
758
794
  this._restoreContext('formula', token);
795
+
796
+ // When the emptyFormulaAsTrue option is set, an empty formula
797
+ // is read as the boolean literal true, following the N3 spec tests
798
+ // and the direction discussed in https://github.com/w3c-cg/N3/issues/185
799
+ if (empty && this._emptyFormulaAsTrue) {
800
+ if (this._subject === formula)
801
+ this._subject = this.N3_TRUE;
802
+ else
803
+ this._object = this.N3_TRUE;
804
+ }
805
+
759
806
  // If the formula was the subject, continue reading the predicate.
760
807
  // If the formula was the object, read punctuation.
761
808
  return this._object === null ? this._readPredicate : this._getContextEndReader();
@@ -1372,6 +1419,7 @@ export default class N3Parser {
1372
1419
  this._versionCallback = onVersion || noop;
1373
1420
  this._inversePredicate = false;
1374
1421
  this._quantified = Object.create(null);
1422
+ this._emptyFormula = false;
1375
1423
 
1376
1424
  // Parse synchronously if no quad callback is given
1377
1425
  if (!onQuad) {
@@ -1431,6 +1479,7 @@ function initDataFactory(parser, factory) {
1431
1479
  parser.RDF_REIFIES = factory.namedNode(namespaces.rdf.reifies);
1432
1480
  parser.N3_FORALL = factory.namedNode(namespaces.r.forAll);
1433
1481
  parser.N3_FORSOME = factory.namedNode(namespaces.r.forSome);
1482
+ parser.N3_TRUE = factory.literal('true', factory.namedNode(namespaces.xsd.boolean));
1434
1483
  parser.ABBREVIATIONS = {
1435
1484
  'a': factory.namedNode(namespaces.rdf.type),
1436
1485
  '=': factory.namedNode(namespaces.owl.sameAs),