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/README.md +7 -0
- package/browser/n3.esm.min.js +12 -12
- package/browser/n3.min.js +12 -12
- package/lib/N3Lexer.js +11 -0
- package/lib/N3Parser.js +24 -18
- package/lib/N3Writer.js +35 -9
- package/lib/Util.js +1 -1
- package/package.json +1 -1
- package/src/N3Lexer.js +14 -0
- package/src/N3Parser.js +26 -18
- package/src/N3Writer.js +38 -12
- package/src/Util.js +1 -1
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.
|
|
342
|
-
this._graph = null;
|
|
343
|
-
return this._readSubject;
|
|
357
|
+
return this._enterTripleTerm('<<(', null, null, token) && this._readSubject;
|
|
344
358
|
case '<<':
|
|
345
|
-
this.
|
|
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.
|
|
471
|
-
this._graph = null;
|
|
472
|
-
return this._readSubject;
|
|
482
|
+
return this._enterTripleTerm('<<(', this._subject, this._predicate, token) && this._readSubject;
|
|
473
483
|
case '<<':
|
|
474
|
-
this.
|
|
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.
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
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
package/package.json
CHANGED
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.
|
|
342
|
-
this._graph = null;
|
|
343
|
-
return this._readSubject;
|
|
358
|
+
return this._enterTripleTerm('<<(', null, null, token) && this._readSubject;
|
|
344
359
|
case '<<':
|
|
345
|
-
this.
|
|
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.
|
|
493
|
-
this._graph = null;
|
|
494
|
-
return this._readSubject;
|
|
505
|
+
return this._enterTripleTerm('<<(', this._subject, this._predicate, token) && this._readSubject;
|
|
495
506
|
case '<<':
|
|
496
|
-
this.
|
|
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.
|
|
724
|
-
|
|
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
|
|
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 (
|
|
178
|
-
iri = iri.replace(
|
|
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 (
|
|
190
|
-
value = value.replace(
|
|
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
|
-
|
|
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.
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
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