n3 2.12.2 → 2.13.1

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
@@ -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 {
package/lib/N3Parser.js CHANGED
@@ -11,6 +11,10 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
11
11
  // **N3Parser** parses N3 documents.
12
12
 
13
13
  let blankNodePrefix = 0;
14
+ // Detects `.` and `..` path segments in an IRI
15
+ const dotSegments = /(^|\/)\.\.?($|[/#?])/;
16
+ // Detects `.` and `..` segments in the path of an IRI, ignoring its query and fragment
17
+ const pathDotSegments = /^[^?#]*(?:^|\/)\.\.?(?:$|[/#?])/;
14
18
 
15
19
  // ## Constructor
16
20
  class N3Parser {
@@ -91,6 +95,7 @@ class N3Parser {
91
95
  if (!baseIRI) {
92
96
  this._base = '';
93
97
  this._basePath = '';
98
+ this._basePathHasDotSegments = false;
94
99
  } else {
95
100
  // Remove fragment if present
96
101
  const fragmentPos = baseIRI.indexOf('#');
@@ -107,6 +112,8 @@ class N3Parser {
107
112
  // If the base has an authority but an empty path,
108
113
  // relative IRIs merge under the path '/' (RFC 3986 §5.2.3)
109
114
  if (baseIRI[2] !== undefined && (base.length === this._baseRoot.length || base[this._baseRoot.length] === '?')) this._basePath = `${this._baseRoot}/`;
115
+ // Check once whether resolving against the base path needs to remove dot segments
116
+ this._basePathHasDotSegments = dotSegments.test(this._basePath);
110
117
  }
111
118
  }
112
119
 
@@ -153,7 +160,7 @@ class N3Parser {
153
160
  // Prefix and base declarations are scoped to their formula,
154
161
  // so record prefix changes to undo them when the formula ends
155
162
  if (type === 'formula') {
156
- context.base = [this._base, this._basePath, this._baseRoot, this._baseScheme];
163
+ context.base = [this._base, this._basePath, this._baseRoot, this._baseScheme, this._basePathHasDotSegments];
157
164
  this._prefixChanges = [];
158
165
  }
159
166
  this._contextStack.push(context);
@@ -193,7 +200,7 @@ class N3Parser {
193
200
  if (this._n3Mode) {
194
201
  this._inversePredicate = context.inverse;
195
202
  this._expectOf = context.expectOf;
196
- if (type === 'formula') [this._base, this._basePath, this._baseRoot, this._baseScheme] = context.base;
203
+ if (type === 'formula') [this._base, this._basePath, this._baseRoot, this._baseScheme, this._basePathHasDotSegments] = context.base;
197
204
  if (this._prefixChanges !== context.prefixChanges) {
198
205
  undoChanges(this._prefixes, this._prefixChanges);
199
206
  this._prefixChanges = context.prefixChanges;
@@ -1455,14 +1462,17 @@ class N3Parser {
1455
1462
  // Resolve all other IRIs at the base IRI's path
1456
1463
  default:
1457
1464
  // Relative IRIs cannot contain a colon in the first path segment
1458
- return /^[^/:]*:/.test(iri) ? null : this._removeDotSegments(this._basePath + iri);
1465
+ if (/^[^/:]*:/.test(iri)) return null;
1466
+ // Only scan the joined IRI for dot segments if either part can contain them,
1467
+ // as the base path can be long and the joined IRI would need to be copied
1468
+ return this._basePathHasDotSegments || pathDotSegments.test(iri) ? this._removeDotSegments(this._basePath + iri) : this._basePath + iri;
1459
1469
  }
1460
1470
  }
1461
1471
 
1462
1472
  // ### `_removeDotSegments` resolves './' and '../' path segments in an IRI as per RFC3986
1463
1473
  _removeDotSegments(iri) {
1464
1474
  // Don't modify the IRI if it does not contain any dot segments
1465
- if (!/(^|\/)\.\.?($|[/#?])/.test(iri)) return iri;
1475
+ if (!dotSegments.test(iri)) return iri;
1466
1476
 
1467
1477
  // Start with an imaginary slash before the IRI in order to resolve trailing './' and '../'
1468
1478
  const length = iri.length;
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.2",
3
+ "version": "2.13.1",
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
@@ -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 {
package/src/N3Parser.js CHANGED
@@ -4,6 +4,10 @@ import N3DataFactory from './N3DataFactory';
4
4
  import namespaces from './IRIs';
5
5
 
6
6
  let blankNodePrefix = 0;
7
+ // Detects `.` and `..` path segments in an IRI
8
+ const dotSegments = /(^|\/)\.\.?($|[/#?])/;
9
+ // Detects `.` and `..` segments in the path of an IRI, ignoring its query and fragment
10
+ const pathDotSegments = /^[^?#]*(?:^|\/)\.\.?(?:$|[/#?])/;
7
11
 
8
12
  // ## Constructor
9
13
  export default class N3Parser {
@@ -83,6 +87,7 @@ export default class N3Parser {
83
87
  if (!baseIRI) {
84
88
  this._base = '';
85
89
  this._basePath = '';
90
+ this._basePathHasDotSegments = false;
86
91
  }
87
92
  else {
88
93
  // Remove fragment if present
@@ -103,6 +108,8 @@ export default class N3Parser {
103
108
  // relative IRIs merge under the path '/' (RFC 3986 §5.2.3)
104
109
  if (baseIRI[2] !== undefined && (base.length === this._baseRoot.length || base[this._baseRoot.length] === '?'))
105
110
  this._basePath = `${this._baseRoot}/`;
111
+ // Check once whether resolving against the base path needs to remove dot segments
112
+ this._basePathHasDotSegments = dotSegments.test(this._basePath);
106
113
  }
107
114
  }
108
115
 
@@ -140,7 +147,8 @@ export default class N3Parser {
140
147
  // Prefix and base declarations are scoped to their formula,
141
148
  // so record prefix changes to undo them when the formula ends
142
149
  if (type === 'formula') {
143
- context.base = [this._base, this._basePath, this._baseRoot, this._baseScheme];
150
+ context.base = [this._base, this._basePath, this._baseRoot, this._baseScheme,
151
+ this._basePathHasDotSegments];
144
152
  this._prefixChanges = [];
145
153
  }
146
154
  this._contextStack.push(context);
@@ -182,7 +190,8 @@ export default class N3Parser {
182
190
  this._inversePredicate = context.inverse;
183
191
  this._expectOf = context.expectOf;
184
192
  if (type === 'formula')
185
- [this._base, this._basePath, this._baseRoot, this._baseScheme] = context.base;
193
+ [this._base, this._basePath, this._baseRoot, this._baseScheme,
194
+ this._basePathHasDotSegments] = context.base;
186
195
  if (this._prefixChanges !== context.prefixChanges) {
187
196
  undoChanges(this._prefixes, this._prefixChanges);
188
197
  this._prefixChanges = context.prefixChanges;
@@ -1576,14 +1585,19 @@ export default class N3Parser {
1576
1585
  // Resolve all other IRIs at the base IRI's path
1577
1586
  default:
1578
1587
  // Relative IRIs cannot contain a colon in the first path segment
1579
- return (/^[^/:]*:/.test(iri)) ? null : this._removeDotSegments(this._basePath + iri);
1588
+ if (/^[^/:]*:/.test(iri))
1589
+ return null;
1590
+ // Only scan the joined IRI for dot segments if either part can contain them,
1591
+ // as the base path can be long and the joined IRI would need to be copied
1592
+ return this._basePathHasDotSegments || pathDotSegments.test(iri) ?
1593
+ this._removeDotSegments(this._basePath + iri) : this._basePath + iri;
1580
1594
  }
1581
1595
  }
1582
1596
 
1583
1597
  // ### `_removeDotSegments` resolves './' and '../' path segments in an IRI as per RFC3986
1584
1598
  _removeDotSegments(iri) {
1585
1599
  // Don't modify the IRI if it does not contain any dot segments
1586
- if (!/(^|\/)\.\.?($|[/#?])/.test(iri))
1600
+ if (!dotSegments.test(iri))
1587
1601
  return iri;
1588
1602
 
1589
1603
  // Start with an imaginary slash before the IRI in order to resolve trailing './' and '../'
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