n3 2.2.10 → 2.2.12
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 +56 -0
- package/browser/n3.esm.min.js +5 -5
- package/browser/n3.min.js +1 -1
- package/lib/N3Lexer.js +15 -9
- package/lib/N3Parser.js +17 -1
- package/lib/N3StreamWriter.js +58 -4
- package/package.json +1 -1
- package/src/N3Lexer.js +16 -11
- package/src/N3Parser.js +22 -2
- package/src/N3StreamWriter.js +67 -5
package/lib/N3Lexer.js
CHANGED
|
@@ -13,9 +13,10 @@ const {
|
|
|
13
13
|
xsd
|
|
14
14
|
} = _IRIs.default;
|
|
15
15
|
|
|
16
|
-
// Regular expression and replacement
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
@@ -508,7 +508,23 @@ class N3Parser {
|
|
|
508
508
|
case '{':
|
|
509
509
|
// Start a new formula
|
|
510
510
|
if (!this._n3Mode) return this._error('Unexpected graph', token);
|
|
511
|
-
|
|
511
|
+
// The formula is an item of the list,
|
|
512
|
+
// so it must be linked in the list's graph before the graph changes
|
|
513
|
+
list = this._factory.blankNode();
|
|
514
|
+
item = this._factory.blankNode();
|
|
515
|
+
// Is this the first element of the list?
|
|
516
|
+
if (previousList === null) {
|
|
517
|
+
// This list is either the subject or the object of its parent
|
|
518
|
+
if (parent.predicate === null) parent.subject = list;else parent.object = list;
|
|
519
|
+
} else {
|
|
520
|
+
// Continue the previous list with the current list
|
|
521
|
+
this._emit(previousList, this.RDF_REST, list, this._graph);
|
|
522
|
+
}
|
|
523
|
+
// Output the item
|
|
524
|
+
this._emit(list, this.RDF_FIRST, item, this._graph);
|
|
525
|
+
// Stack the current list quad and start the formula
|
|
526
|
+
this._saveContext('formula', this._graph, list, this.RDF_FIRST, this._graph = item);
|
|
527
|
+
this._subject = null;
|
|
512
528
|
return this._readSubject;
|
|
513
529
|
case '<<(':
|
|
514
530
|
this._saveContext('<<(', this._graph, null, null, null);
|
package/lib/N3StreamWriter.js
CHANGED
|
@@ -9,6 +9,9 @@ var _N3Writer = _interopRequireDefault(require("./N3Writer"));
|
|
|
9
9
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
10
|
// **N3StreamWriter** serializes a quad stream into a text stream.
|
|
11
11
|
|
|
12
|
+
const MIN_CHUNK_SIZE = 16 * 1024;
|
|
13
|
+
const DEFAULT_FLUSH_DELAY_MS = 20;
|
|
14
|
+
|
|
12
15
|
// ## Constructor
|
|
13
16
|
class N3StreamWriter extends _readableStream.Transform {
|
|
14
17
|
constructor(options) {
|
|
@@ -17,27 +20,77 @@ class N3StreamWriter extends _readableStream.Transform {
|
|
|
17
20
|
writableObjectMode: true
|
|
18
21
|
});
|
|
19
22
|
|
|
23
|
+
// Coalesce serialized fragments into larger stream chunks
|
|
24
|
+
this._buffer = '';
|
|
25
|
+
|
|
26
|
+
// Flush partial chunks after a bounded delay
|
|
27
|
+
this._flushTimer = null;
|
|
28
|
+
this._flushDelay = options && options.flushDelay !== undefined ? options.flushDelay : DEFAULT_FLUSH_DELAY_MS;
|
|
29
|
+
|
|
20
30
|
// Set up writer with a dummy stream object
|
|
21
31
|
const writer = this._writer = new _N3Writer.default({
|
|
22
|
-
write: (
|
|
23
|
-
this.
|
|
32
|
+
write: (chunk, encoding, callback) => {
|
|
33
|
+
this._buffer += chunk;
|
|
34
|
+
if (this._buffer.length >= MIN_CHUNK_SIZE) this._pushBuffer();else if (this._flushTimer === null) this._armFlushTimer();
|
|
24
35
|
callback && callback();
|
|
25
36
|
},
|
|
26
37
|
end: callback => {
|
|
38
|
+
this._pushBuffer();
|
|
27
39
|
this.push(null);
|
|
28
40
|
callback && callback();
|
|
29
41
|
}
|
|
30
42
|
}, options);
|
|
31
43
|
|
|
32
|
-
//
|
|
44
|
+
// Flush buffered output before serialization errors
|
|
45
|
+
let pendingDone = null;
|
|
46
|
+
const quadDone = error => {
|
|
47
|
+
const done = pendingDone;
|
|
48
|
+
pendingDone = null;
|
|
49
|
+
if (error) this._pushBuffer();
|
|
50
|
+
done(error);
|
|
51
|
+
};
|
|
33
52
|
this._transform = (quad, encoding, done) => {
|
|
34
|
-
|
|
53
|
+
pendingDone = done;
|
|
54
|
+
writer.addQuad(quad, quadDone);
|
|
35
55
|
};
|
|
36
56
|
this._flush = done => {
|
|
37
57
|
writer.end(done);
|
|
38
58
|
};
|
|
39
59
|
}
|
|
40
60
|
|
|
61
|
+
// ### `_pushBuffer` flushes coalesced output to the stream queue
|
|
62
|
+
_pushBuffer() {
|
|
63
|
+
this._clearFlushTimer();
|
|
64
|
+
if (this._buffer !== '') {
|
|
65
|
+
this.push(this._buffer);
|
|
66
|
+
this._buffer = '';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ### `_armFlushTimer` schedules a partial-chunk flush
|
|
71
|
+
_armFlushTimer() {
|
|
72
|
+
this._flushTimer = setTimeout(() => {
|
|
73
|
+
this._flushTimer = null;
|
|
74
|
+
this._pushBuffer();
|
|
75
|
+
}, this._flushDelay);
|
|
76
|
+
// Browser timers do not implement `unref`
|
|
77
|
+
this._flushTimer.unref && this._flushTimer.unref();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ### `_clearFlushTimer` cancels a scheduled flush
|
|
81
|
+
_clearFlushTimer() {
|
|
82
|
+
if (this._flushTimer !== null) {
|
|
83
|
+
clearTimeout(this._flushTimer);
|
|
84
|
+
this._flushTimer = null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ### `_destroy` cancels a scheduled flush, so it cannot fire afterwards
|
|
89
|
+
_destroy(error, callback) {
|
|
90
|
+
this._clearFlushTimer();
|
|
91
|
+
super._destroy(error, callback);
|
|
92
|
+
}
|
|
93
|
+
|
|
41
94
|
// ### Serializes a stream of quads
|
|
42
95
|
import(stream) {
|
|
43
96
|
stream.on('data', quad => {
|
|
@@ -47,6 +100,7 @@ class N3StreamWriter extends _readableStream.Transform {
|
|
|
47
100
|
this.end();
|
|
48
101
|
});
|
|
49
102
|
stream.on('error', error => {
|
|
103
|
+
this._pushBuffer();
|
|
50
104
|
this.emit('error', error);
|
|
51
105
|
});
|
|
52
106
|
stream.on('prefix', (prefix, iri) => {
|
package/package.json
CHANGED
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
455
|
-
return
|
|
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
|
@@ -538,8 +538,28 @@ export default class N3Parser {
|
|
|
538
538
|
// Start a new formula
|
|
539
539
|
if (!this._n3Mode)
|
|
540
540
|
return this._error('Unexpected graph', token);
|
|
541
|
-
|
|
542
|
-
|
|
541
|
+
// The formula is an item of the list,
|
|
542
|
+
// so it must be linked in the list's graph before the graph changes
|
|
543
|
+
list = this._factory.blankNode();
|
|
544
|
+
item = this._factory.blankNode();
|
|
545
|
+
// Is this the first element of the list?
|
|
546
|
+
if (previousList === null) {
|
|
547
|
+
// This list is either the subject or the object of its parent
|
|
548
|
+
if (parent.predicate === null)
|
|
549
|
+
parent.subject = list;
|
|
550
|
+
else
|
|
551
|
+
parent.object = list;
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
// Continue the previous list with the current list
|
|
555
|
+
this._emit(previousList, this.RDF_REST, list, this._graph);
|
|
556
|
+
}
|
|
557
|
+
// Output the item
|
|
558
|
+
this._emit(list, this.RDF_FIRST, item, this._graph);
|
|
559
|
+
// Stack the current list quad and start the formula
|
|
560
|
+
this._saveContext('formula', this._graph, list, this.RDF_FIRST,
|
|
561
|
+
this._graph = item);
|
|
562
|
+
this._subject = null;
|
|
543
563
|
return this._readSubject;
|
|
544
564
|
case '<<(':
|
|
545
565
|
this._saveContext('<<(', this._graph, null, null, null);
|
package/src/N3StreamWriter.js
CHANGED
|
@@ -2,27 +2,89 @@
|
|
|
2
2
|
import { Transform } from 'readable-stream';
|
|
3
3
|
import N3Writer from './N3Writer';
|
|
4
4
|
|
|
5
|
+
const MIN_CHUNK_SIZE = 16 * 1024;
|
|
6
|
+
const DEFAULT_FLUSH_DELAY_MS = 20;
|
|
7
|
+
|
|
5
8
|
// ## Constructor
|
|
6
9
|
export default class N3StreamWriter extends Transform {
|
|
7
10
|
constructor(options) {
|
|
8
11
|
super({ encoding: 'utf8', writableObjectMode: true });
|
|
9
12
|
|
|
13
|
+
// Coalesce serialized fragments into larger stream chunks
|
|
14
|
+
this._buffer = '';
|
|
15
|
+
|
|
16
|
+
// Flush partial chunks after a bounded delay
|
|
17
|
+
this._flushTimer = null;
|
|
18
|
+
this._flushDelay = options && options.flushDelay !== undefined ?
|
|
19
|
+
options.flushDelay : DEFAULT_FLUSH_DELAY_MS;
|
|
20
|
+
|
|
10
21
|
// Set up writer with a dummy stream object
|
|
11
22
|
const writer = this._writer = new N3Writer({
|
|
12
|
-
write: (
|
|
13
|
-
|
|
23
|
+
write: (chunk, encoding, callback) => {
|
|
24
|
+
this._buffer += chunk;
|
|
25
|
+
if (this._buffer.length >= MIN_CHUNK_SIZE)
|
|
26
|
+
this._pushBuffer();
|
|
27
|
+
else if (this._flushTimer === null)
|
|
28
|
+
this._armFlushTimer();
|
|
29
|
+
callback && callback();
|
|
30
|
+
},
|
|
31
|
+
end: callback => { this._pushBuffer(); this.push(null); callback && callback(); },
|
|
14
32
|
}, options);
|
|
15
33
|
|
|
16
|
-
//
|
|
17
|
-
|
|
34
|
+
// Flush buffered output before serialization errors
|
|
35
|
+
let pendingDone = null;
|
|
36
|
+
const quadDone = error => {
|
|
37
|
+
const done = pendingDone;
|
|
38
|
+
pendingDone = null;
|
|
39
|
+
if (error)
|
|
40
|
+
this._pushBuffer();
|
|
41
|
+
done(error);
|
|
42
|
+
};
|
|
43
|
+
this._transform = (quad, encoding, done) => {
|
|
44
|
+
pendingDone = done;
|
|
45
|
+
writer.addQuad(quad, quadDone);
|
|
46
|
+
};
|
|
18
47
|
this._flush = done => { writer.end(done); };
|
|
19
48
|
}
|
|
20
49
|
|
|
50
|
+
// ### `_pushBuffer` flushes coalesced output to the stream queue
|
|
51
|
+
_pushBuffer() {
|
|
52
|
+
this._clearFlushTimer();
|
|
53
|
+
if (this._buffer !== '') {
|
|
54
|
+
this.push(this._buffer);
|
|
55
|
+
this._buffer = '';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ### `_armFlushTimer` schedules a partial-chunk flush
|
|
60
|
+
_armFlushTimer() {
|
|
61
|
+
this._flushTimer = setTimeout(() => {
|
|
62
|
+
this._flushTimer = null;
|
|
63
|
+
this._pushBuffer();
|
|
64
|
+
}, this._flushDelay);
|
|
65
|
+
// Browser timers do not implement `unref`
|
|
66
|
+
this._flushTimer.unref && this._flushTimer.unref();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ### `_clearFlushTimer` cancels a scheduled flush
|
|
70
|
+
_clearFlushTimer() {
|
|
71
|
+
if (this._flushTimer !== null) {
|
|
72
|
+
clearTimeout(this._flushTimer);
|
|
73
|
+
this._flushTimer = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ### `_destroy` cancels a scheduled flush, so it cannot fire afterwards
|
|
78
|
+
_destroy(error, callback) {
|
|
79
|
+
this._clearFlushTimer();
|
|
80
|
+
super._destroy(error, callback);
|
|
81
|
+
}
|
|
82
|
+
|
|
21
83
|
// ### Serializes a stream of quads
|
|
22
84
|
import(stream) {
|
|
23
85
|
stream.on('data', quad => { this.write(quad); });
|
|
24
86
|
stream.on('end', () => { this.end(); });
|
|
25
|
-
stream.on('error', error => { this.emit('error', error); });
|
|
87
|
+
stream.on('error', error => { this._pushBuffer(); this.emit('error', error); });
|
|
26
88
|
stream.on('prefix', (prefix, iri) => { this._writer.addPrefix(prefix, iri); });
|
|
27
89
|
return this;
|
|
28
90
|
}
|