n3 2.11.8 → 2.12.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/README.md +25 -0
- package/browser/n3.esm.min.js +9 -9
- package/browser/n3.min.js +8 -8
- package/lib/N3Lexer.js +45 -7
- package/lib/N3Parser.js +18 -4
- package/lib/N3StreamParser.js +4 -0
- package/lib/N3Writer.js +18 -3
- package/package.json +1 -1
- package/src/N3Lexer.js +53 -7
- package/src/N3Parser.js +20 -4
- package/src/N3StreamParser.js +2 -0
- package/src/N3Writer.js +21 -5
package/lib/N3Lexer.js
CHANGED
|
@@ -93,6 +93,9 @@ function isSeparatorCode(code) {
|
|
|
93
93
|
return code === SPACE || code === TAB || code === LF || code === CR || code === HASH;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// Words with a fixed meaning in the grammar, which cannot name an additional directive
|
|
97
|
+
const reservedWords = /^(?:prefix|base|version|graph|forsome|forall|iri|a|true|false|has|is|of|id)$/i;
|
|
98
|
+
|
|
96
99
|
// Unfinished input in a stream up to this length is tokenized again with every chunk
|
|
97
100
|
const MIN_RESCAN_LENGTH = 1024;
|
|
98
101
|
|
|
@@ -137,6 +140,18 @@ class N3Lexer {
|
|
|
137
140
|
else {
|
|
138
141
|
this._n3Mode = options.n3 !== false;
|
|
139
142
|
}
|
|
143
|
+
// Recognize additional directive keywords, such as MESSAGE
|
|
144
|
+
// (the @-form of a directive is always tokenized as an @-keyword)
|
|
145
|
+
this._directive = null;
|
|
146
|
+
if (options.directives && options.directives.length !== 0) {
|
|
147
|
+
for (const name of options.directives) {
|
|
148
|
+
if (!/^[a-z]+$/i.test(name) || reservedWords.test(name)) throw new Error(`Invalid directive name: "${name}"`);
|
|
149
|
+
}
|
|
150
|
+
this._directive = new RegExp(`(?:${options.directives.join('|')})(?=[\\s#<])`, 'iy');
|
|
151
|
+
this._directiveMaxLength = Math.max(...options.directives.map(name => name.length));
|
|
152
|
+
// The first characters of directive names, so other words skip the regular expression
|
|
153
|
+
this._directiveStarts = options.directives.map(name => name[0].toLowerCase() + name[0].toUpperCase()).join('');
|
|
154
|
+
}
|
|
140
155
|
// Don't output comment tokens by default
|
|
141
156
|
this.comments = !!options.comments;
|
|
142
157
|
// Cache the last tested closing position of long literals
|
|
@@ -457,6 +472,9 @@ class N3Lexer {
|
|
|
457
472
|
if (inconclusive) {
|
|
458
473
|
// Try to find a prefix
|
|
459
474
|
if ((this._previousMarker === '@prefix' || this._previousMarker === 'PREFIX') && (match = execAt(this._prefix, input, pos))) type = 'prefix', value = match[1] || '';
|
|
475
|
+
// Try to find an additional directive keyword
|
|
476
|
+
// (at the end of the input, only a short final word can be one)
|
|
477
|
+
else if (this._directive !== null && this._directiveStarts.includes(firstChar) && ((match = execAt(this._directive, input, pos)) || inputFinished && input.length - pos <= this._directiveMaxLength && (match = execAtEnd(this._directive, input, pos)))) type = match[0].toUpperCase();
|
|
460
478
|
// Try to find a prefixed name. Since it can contain (but not end with) a dot,
|
|
461
479
|
// we always need a non-dot character before deciding it is a prefixed name.
|
|
462
480
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
@@ -508,7 +526,8 @@ class N3Lexer {
|
|
|
508
526
|
callback(null, token);
|
|
509
527
|
} else token = emitToken(type, value, prefix, line, start, lexicalLength || length);
|
|
510
528
|
this.previousToken = token;
|
|
511
|
-
|
|
529
|
+
// The string of a version declaration cannot take a language tag, so a following @keyword is a keyword
|
|
530
|
+
this._previousMarker = type === 'literal' && (this._previousMarker === 'VERSION' || this._previousMarker === '@version') ? 'version' : type;
|
|
512
531
|
|
|
513
532
|
// Advance to next part to tokenize
|
|
514
533
|
pos = Math.min(pos + length, input.length);
|
|
@@ -666,10 +685,29 @@ class N3Lexer {
|
|
|
666
685
|
};
|
|
667
686
|
}
|
|
668
687
|
|
|
688
|
+
// ### `_tryTokenizeToEnd` tokenizes as far as possible, reporting failures through the callback
|
|
689
|
+
_tryTokenizeToEnd(callback, inputFinished) {
|
|
690
|
+
// Keep track of errors thrown by the callback, which must reach the caller unchanged
|
|
691
|
+
let callbackError;
|
|
692
|
+
try {
|
|
693
|
+
this._tokenizeToEnd((error, token) => {
|
|
694
|
+
try {
|
|
695
|
+
return callback(error, token);
|
|
696
|
+
} catch (thrown) {
|
|
697
|
+
throw callbackError = thrown;
|
|
698
|
+
}
|
|
699
|
+
}, inputFinished);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
// Matching an extremely long token can exhaust the regular expression stack
|
|
702
|
+
if (error === callbackError || !(error instanceof RangeError)) throw error;
|
|
703
|
+
callback(this._syntaxError(null, `Token too long on line ${this._line}.`));
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
669
707
|
// ### `_syntaxError` creates a syntax error for the given issue
|
|
670
|
-
_syntaxError(issue) {
|
|
708
|
+
_syntaxError(issue, message = `Unexpected "${issue}" on line ${this._line}.`) {
|
|
671
709
|
this._input = null;
|
|
672
|
-
const err = new Error(
|
|
710
|
+
const err = new Error(message);
|
|
673
711
|
err.context = {
|
|
674
712
|
token: undefined,
|
|
675
713
|
line: this._line,
|
|
@@ -710,13 +748,13 @@ class N3Lexer {
|
|
|
710
748
|
this._input = this._readStartingBom(input);
|
|
711
749
|
// If a callback was passed, asynchronously call it
|
|
712
750
|
if (typeof callback === 'function') queueMicrotask(() => {
|
|
713
|
-
if (this._tokenization === tokenization) this.
|
|
751
|
+
if (this._tokenization === tokenization) this._tryTokenizeToEnd(callback, true);
|
|
714
752
|
});
|
|
715
753
|
// If no callback was passed, tokenize synchronously and return
|
|
716
754
|
else {
|
|
717
755
|
const tokens = [];
|
|
718
756
|
let error;
|
|
719
|
-
this.
|
|
757
|
+
this._tryTokenizeToEnd((e, t) => e ? error = e : tokens.push(t), true);
|
|
720
758
|
if (error) throw error;
|
|
721
759
|
return tokens;
|
|
722
760
|
}
|
|
@@ -743,7 +781,7 @@ class N3Lexer {
|
|
|
743
781
|
// Tokenize as far as possible. When a previous attempt left a long unfinished token,
|
|
744
782
|
// wait until the buffered input has doubled, so the token is not rescanned for every chunk.
|
|
745
783
|
if (this._input.length >= retryLength) {
|
|
746
|
-
this.
|
|
784
|
+
this._tryTokenizeToEnd(callback, false);
|
|
747
785
|
retryLength = this._input !== null && this._input.length > MIN_RESCAN_LENGTH ? 2 * this._input.length : 0;
|
|
748
786
|
}
|
|
749
787
|
}
|
|
@@ -754,7 +792,7 @@ class N3Lexer {
|
|
|
754
792
|
// Decode any incomplete character left at the end
|
|
755
793
|
const rest = decoder ? decoder.decode() : '';
|
|
756
794
|
if (rest) this._input = typeof this._input === 'string' ? this._input + rest : rest;
|
|
757
|
-
if (typeof this._input === 'string') this.
|
|
795
|
+
if (typeof this._input === 'string') this._tryTokenizeToEnd(callback, true);
|
|
758
796
|
}
|
|
759
797
|
});
|
|
760
798
|
input.on('error', error => {
|
package/lib/N3Parser.js
CHANGED
|
@@ -53,10 +53,17 @@ class N3Parser {
|
|
|
53
53
|
return null;
|
|
54
54
|
};
|
|
55
55
|
this._blankNodePrefix = typeof options.blankNodePrefix !== 'string' ? '' : options.blankNodePrefix.replace(/^(?!_:)/, '_:');
|
|
56
|
+
// Map the token types of additional directives (@name and NAME) to their names
|
|
57
|
+
this._directives = null;
|
|
58
|
+
if (options.directives) {
|
|
59
|
+
this._directives = Object.create(null);
|
|
60
|
+
for (const name of options.directives) this._directives[`@${name.toLowerCase()}`] = this._directives[name.toUpperCase()] = name;
|
|
61
|
+
}
|
|
56
62
|
this._lexer = options.lexer || new _N3Lexer.default({
|
|
57
63
|
lineMode: isLineMode,
|
|
58
64
|
n3: isN3,
|
|
59
|
-
isImpliedBy: this._isImpliedBy
|
|
65
|
+
isImpliedBy: this._isImpliedBy,
|
|
66
|
+
directives: options.directives
|
|
60
67
|
});
|
|
61
68
|
// Disable explicit quantifiers by default
|
|
62
69
|
this._explicitQuantifiers = !!options.explicitQuantifiers;
|
|
@@ -238,8 +245,13 @@ class N3Parser {
|
|
|
238
245
|
}
|
|
239
246
|
case 'GRAPH':
|
|
240
247
|
if (this._supportsNamedGraphs) return this._readNamedGraphLabel;
|
|
241
|
-
// Otherwise, the next token must be a subject
|
|
248
|
+
// Otherwise, the next token must be a subject, unless it is an additional directive
|
|
242
249
|
default:
|
|
250
|
+
if (this._directives !== null && token.type in this._directives && this._graph === null) {
|
|
251
|
+
this._sparqlStyle = token.type[0] !== '@';
|
|
252
|
+
this._directiveCallback(this._directives[token.type]);
|
|
253
|
+
return this._readDeclarationPunctuation;
|
|
254
|
+
}
|
|
243
255
|
return this._readSubject(token);
|
|
244
256
|
}
|
|
245
257
|
}
|
|
@@ -1514,12 +1526,13 @@ class N3Parser {
|
|
|
1514
1526
|
parse(input, quadCallback, prefixCallback, versionCallback) {
|
|
1515
1527
|
// The second parameter also accepts an object of named callbacks.
|
|
1516
1528
|
// As a second and third parameter it still accepts a separate quadCallback and prefixCallback for backward compatibility as well
|
|
1517
|
-
let onQuad, onPrefix, onComment, onVersion, onToken, onTokenEnd;
|
|
1518
|
-
if (quadCallback && (quadCallback.onQuad || quadCallback.onPrefix || quadCallback.onComment || quadCallback.onVersion || quadCallback.onToken || quadCallback.onTokenEnd)) {
|
|
1529
|
+
let onQuad, onPrefix, onComment, onVersion, onDirective, onToken, onTokenEnd;
|
|
1530
|
+
if (quadCallback && (quadCallback.onQuad || quadCallback.onPrefix || quadCallback.onComment || quadCallback.onVersion || quadCallback.onDirective || quadCallback.onToken || quadCallback.onTokenEnd)) {
|
|
1519
1531
|
onQuad = quadCallback.onQuad;
|
|
1520
1532
|
onPrefix = quadCallback.onPrefix;
|
|
1521
1533
|
onComment = quadCallback.onComment;
|
|
1522
1534
|
onVersion = quadCallback.onVersion;
|
|
1535
|
+
onDirective = quadCallback.onDirective;
|
|
1523
1536
|
onToken = quadCallback.onToken;
|
|
1524
1537
|
onTokenEnd = quadCallback.onTokenEnd;
|
|
1525
1538
|
} else {
|
|
@@ -1537,6 +1550,7 @@ class N3Parser {
|
|
|
1537
1550
|
if (this._n3Mode && this._implicitEmptyPrefix && this._base) this._prefixes[''] = this._resolveIRI('#');
|
|
1538
1551
|
this._prefixCallback = onPrefix || noop;
|
|
1539
1552
|
this._versionCallback = onVersion || noop;
|
|
1553
|
+
this._directiveCallback = onDirective || noop;
|
|
1540
1554
|
this._inversePredicate = false;
|
|
1541
1555
|
this._expectOf = false;
|
|
1542
1556
|
this._quantified = Object.create(null);
|
package/lib/N3StreamParser.js
CHANGED
|
@@ -28,6 +28,10 @@ class N3StreamParser extends _readableStream.Transform {
|
|
|
28
28
|
// Emit prefixes through the `prefix` event
|
|
29
29
|
onPrefix: (prefix, uri) => {
|
|
30
30
|
this.emit('prefix', prefix, uri);
|
|
31
|
+
},
|
|
32
|
+
// Emit additional directives through the `directive` event
|
|
33
|
+
onDirective: name => {
|
|
34
|
+
this.emit('directive', name);
|
|
31
35
|
}
|
|
32
36
|
};
|
|
33
37
|
if (options && options.comments) callbacks.onComment = comment => {
|
package/lib/N3Writer.js
CHANGED
|
@@ -18,6 +18,9 @@ const {
|
|
|
18
18
|
rdf,
|
|
19
19
|
xsd
|
|
20
20
|
} = _IRIs.default;
|
|
21
|
+
const {
|
|
22
|
+
hasOwnProperty
|
|
23
|
+
} = Object.prototype;
|
|
21
24
|
|
|
22
25
|
// Characters that require escaping in Turtle, TriG, and N3,
|
|
23
26
|
// where characters outside the Basic Multilingual Plane are escaped as well
|
|
@@ -38,6 +41,14 @@ const escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/,
|
|
|
38
41
|
'\f': '\\f'
|
|
39
42
|
};
|
|
40
43
|
|
|
44
|
+
// A local name (PN_LOCAL) as written in a prefixed name, without backslash escapes,
|
|
45
|
+
// so that it denotes the same characters as the IRI it is taken from.
|
|
46
|
+
// Characters outside the Basic Multilingual Plane are escaped before matching, so they never occur.
|
|
47
|
+
const localNameStart = 'A-Za-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF' + '\\u200C\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD_:0-9',
|
|
48
|
+
localNameChar = `${localNameStart}\\-\\u00B7\\u0300-\\u036F\\u203F\\u2040`,
|
|
49
|
+
percent = '%[0-9A-Fa-f]{2}',
|
|
50
|
+
localName = new RegExp(`^(?:(?:[${localNameStart}]|${percent})` + `(?:(?:[${localNameChar}.]|${percent})*(?:[${localNameChar}]|${percent}))?)?$`);
|
|
51
|
+
|
|
41
52
|
// ## Placeholder class to represent already pretty-printed terms
|
|
42
53
|
class SerializedTerm extends _N3DataFactory.Term {
|
|
43
54
|
// Pretty-printed nodes are not equal to any other node
|
|
@@ -175,7 +186,7 @@ class N3Writer {
|
|
|
175
186
|
// A blank node or list is represented as-is
|
|
176
187
|
if (entity.termType !== 'NamedNode') {
|
|
177
188
|
// If it is a list head, pretty-print it
|
|
178
|
-
if (this._lists &&
|
|
189
|
+
if (this._lists && hasOwnProperty.call(this._lists, entity.value)) entity = this.list(this._lists[entity.value]);
|
|
179
190
|
// Terms from this library already hold their serialization as id
|
|
180
191
|
if (entity instanceof _N3DataFactory.Term) return entity.id;
|
|
181
192
|
return entity.termType === 'Variable' ? `?${entity.value}` : `_:${entity.value}`;
|
|
@@ -189,7 +200,11 @@ class N3Writer {
|
|
|
189
200
|
if (this._escape.test(iri)) iri = iri.replace(this._escapeAll, this._characterReplacer);
|
|
190
201
|
// Try to represent the IRI as prefixed name, unless no prefixes were added
|
|
191
202
|
const prefixMatch = this._hasPrefixes ? (this._prefixRegex || this._createPrefixRegex()).exec(iri) : null;
|
|
192
|
-
|
|
203
|
+
if (!prefixMatch) return `<${iri}>`;
|
|
204
|
+
// An IRI that starts with a registered prefix IRI
|
|
205
|
+
if (prefixMatch[1]) return this._prefixIRIs[prefixMatch[1]] + prefixMatch[2];
|
|
206
|
+
// An IRI that already looks like a prefixed name, if the rest is a valid local name
|
|
207
|
+
return localName.test(iri.slice(prefixMatch[0].length)) ? iri : `<${iri}>`;
|
|
193
208
|
}
|
|
194
209
|
|
|
195
210
|
// ### `_encodeLiteral` represents a literal
|
|
@@ -338,7 +353,7 @@ class N3Writer {
|
|
|
338
353
|
IRIlist += IRIlist ? `|${IRIpattern}` : IRIpattern;
|
|
339
354
|
prefixList += prefixList ? `|${prefixPattern}` : prefixPattern;
|
|
340
355
|
}
|
|
341
|
-
return this._prefixRegex = new RegExp(`^(
|
|
356
|
+
return this._prefixRegex = new RegExp(`^(${IRIlist})([_a-zA-Z0-9](?:\\.?[\\-_a-zA-Z0-9])*)$|` + `^(?:${prefixList})`);
|
|
342
357
|
}
|
|
343
358
|
|
|
344
359
|
// ### `blank` creates a blank node with the given content
|
package/package.json
CHANGED
package/src/N3Lexer.js
CHANGED
|
@@ -58,6 +58,9 @@ function isSeparatorCode(code) {
|
|
|
58
58
|
return code === SPACE || code === TAB || code === LF || code === CR || code === HASH;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// Words with a fixed meaning in the grammar, which cannot name an additional directive
|
|
62
|
+
const reservedWords = /^(?:prefix|base|version|graph|forsome|forall|iri|a|true|false|has|is|of|id)$/i;
|
|
63
|
+
|
|
61
64
|
// Unfinished input in a stream up to this length is tokenized again with every chunk
|
|
62
65
|
const MIN_RESCAN_LENGTH = 1024;
|
|
63
66
|
|
|
@@ -103,6 +106,19 @@ export default class N3Lexer {
|
|
|
103
106
|
else {
|
|
104
107
|
this._n3Mode = options.n3 !== false;
|
|
105
108
|
}
|
|
109
|
+
// Recognize additional directive keywords, such as MESSAGE
|
|
110
|
+
// (the @-form of a directive is always tokenized as an @-keyword)
|
|
111
|
+
this._directive = null;
|
|
112
|
+
if (options.directives && options.directives.length !== 0) {
|
|
113
|
+
for (const name of options.directives) {
|
|
114
|
+
if (!/^[a-z]+$/i.test(name) || reservedWords.test(name))
|
|
115
|
+
throw new Error(`Invalid directive name: "${name}"`);
|
|
116
|
+
}
|
|
117
|
+
this._directive = new RegExp(`(?:${options.directives.join('|')})(?=[\\s#<])`, 'iy');
|
|
118
|
+
this._directiveMaxLength = Math.max(...options.directives.map(name => name.length));
|
|
119
|
+
// The first characters of directive names, so other words skip the regular expression
|
|
120
|
+
this._directiveStarts = options.directives.map(name => name[0].toLowerCase() + name[0].toUpperCase()).join('');
|
|
121
|
+
}
|
|
106
122
|
// Don't output comment tokens by default
|
|
107
123
|
this.comments = !!options.comments;
|
|
108
124
|
// Cache the last tested closing position of long literals
|
|
@@ -486,6 +502,13 @@ export default class N3Lexer {
|
|
|
486
502
|
if ((this._previousMarker === '@prefix' || this._previousMarker === 'PREFIX') &&
|
|
487
503
|
(match = execAt(this._prefix, input, pos)))
|
|
488
504
|
type = 'prefix', value = match[1] || '';
|
|
505
|
+
// Try to find an additional directive keyword
|
|
506
|
+
// (at the end of the input, only a short final word can be one)
|
|
507
|
+
else if (this._directive !== null && this._directiveStarts.includes(firstChar) &&
|
|
508
|
+
((match = execAt(this._directive, input, pos)) ||
|
|
509
|
+
inputFinished && input.length - pos <= this._directiveMaxLength &&
|
|
510
|
+
(match = execAtEnd(this._directive, input, pos))))
|
|
511
|
+
type = match[0].toUpperCase();
|
|
489
512
|
// Try to find a prefixed name. Since it can contain (but not end with) a dot,
|
|
490
513
|
// we always need a non-dot character before deciding it is a prefixed name.
|
|
491
514
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
@@ -534,7 +557,8 @@ export default class N3Lexer {
|
|
|
534
557
|
else
|
|
535
558
|
token = emitToken(type, value, prefix, line, start, lexicalLength || length);
|
|
536
559
|
this.previousToken = token;
|
|
537
|
-
|
|
560
|
+
// The string of a version declaration cannot take a language tag, so a following @keyword is a keyword
|
|
561
|
+
this._previousMarker = type === 'literal' && (this._previousMarker === 'VERSION' || this._previousMarker === '@version') ? 'version' : type;
|
|
538
562
|
|
|
539
563
|
// Advance to next part to tokenize
|
|
540
564
|
pos = Math.min(pos + length, input.length);
|
|
@@ -690,10 +714,32 @@ export default class N3Lexer {
|
|
|
690
714
|
return { value: '', matchLength: 0, finalLineLength: 0 };
|
|
691
715
|
}
|
|
692
716
|
|
|
717
|
+
// ### `_tryTokenizeToEnd` tokenizes as far as possible, reporting failures through the callback
|
|
718
|
+
_tryTokenizeToEnd(callback, inputFinished) {
|
|
719
|
+
// Keep track of errors thrown by the callback, which must reach the caller unchanged
|
|
720
|
+
let callbackError;
|
|
721
|
+
try {
|
|
722
|
+
this._tokenizeToEnd((error, token) => {
|
|
723
|
+
try {
|
|
724
|
+
return callback(error, token);
|
|
725
|
+
}
|
|
726
|
+
catch (thrown) {
|
|
727
|
+
throw (callbackError = thrown);
|
|
728
|
+
}
|
|
729
|
+
}, inputFinished);
|
|
730
|
+
}
|
|
731
|
+
catch (error) {
|
|
732
|
+
// Matching an extremely long token can exhaust the regular expression stack
|
|
733
|
+
if (error === callbackError || !(error instanceof RangeError))
|
|
734
|
+
throw error;
|
|
735
|
+
callback(this._syntaxError(null, `Token too long on line ${this._line}.`));
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
693
739
|
// ### `_syntaxError` creates a syntax error for the given issue
|
|
694
|
-
_syntaxError(issue) {
|
|
740
|
+
_syntaxError(issue, message = `Unexpected "${issue}" on line ${this._line}.`) {
|
|
695
741
|
this._input = null;
|
|
696
|
-
const err = new Error(
|
|
742
|
+
const err = new Error(message);
|
|
697
743
|
err.context = {
|
|
698
744
|
token: undefined,
|
|
699
745
|
line: this._line,
|
|
@@ -736,13 +782,13 @@ export default class N3Lexer {
|
|
|
736
782
|
if (typeof callback === 'function')
|
|
737
783
|
queueMicrotask(() => {
|
|
738
784
|
if (this._tokenization === tokenization)
|
|
739
|
-
this.
|
|
785
|
+
this._tryTokenizeToEnd(callback, true);
|
|
740
786
|
});
|
|
741
787
|
// If no callback was passed, tokenize synchronously and return
|
|
742
788
|
else {
|
|
743
789
|
const tokens = [];
|
|
744
790
|
let error;
|
|
745
|
-
this.
|
|
791
|
+
this._tryTokenizeToEnd((e, t) => e ? (error = e) : tokens.push(t), true);
|
|
746
792
|
if (error) throw error;
|
|
747
793
|
return tokens;
|
|
748
794
|
}
|
|
@@ -769,7 +815,7 @@ export default class N3Lexer {
|
|
|
769
815
|
// Tokenize as far as possible. When a previous attempt left a long unfinished token,
|
|
770
816
|
// wait until the buffered input has doubled, so the token is not rescanned for every chunk.
|
|
771
817
|
if (this._input.length >= retryLength) {
|
|
772
|
-
this.
|
|
818
|
+
this._tryTokenizeToEnd(callback, false);
|
|
773
819
|
retryLength = this._input !== null && this._input.length > MIN_RESCAN_LENGTH ?
|
|
774
820
|
2 * this._input.length : 0;
|
|
775
821
|
}
|
|
@@ -783,7 +829,7 @@ export default class N3Lexer {
|
|
|
783
829
|
if (rest)
|
|
784
830
|
this._input = typeof this._input === 'string' ? this._input + rest : rest;
|
|
785
831
|
if (typeof this._input === 'string')
|
|
786
|
-
this.
|
|
832
|
+
this._tryTokenizeToEnd(callback, true);
|
|
787
833
|
}
|
|
788
834
|
});
|
|
789
835
|
input.on('error', error => {
|
package/src/N3Parser.js
CHANGED
|
@@ -46,7 +46,16 @@ export default class N3Parser {
|
|
|
46
46
|
this._resolveRelativeIRI = iri => { return null; };
|
|
47
47
|
this._blankNodePrefix = typeof options.blankNodePrefix !== 'string' ? '' :
|
|
48
48
|
options.blankNodePrefix.replace(/^(?!_:)/, '_:');
|
|
49
|
-
|
|
49
|
+
// Map the token types of additional directives (@name and NAME) to their names
|
|
50
|
+
this._directives = null;
|
|
51
|
+
if (options.directives) {
|
|
52
|
+
this._directives = Object.create(null);
|
|
53
|
+
for (const name of options.directives)
|
|
54
|
+
this._directives[`@${name.toLowerCase()}`] = this._directives[name.toUpperCase()] = name;
|
|
55
|
+
}
|
|
56
|
+
this._lexer = options.lexer || new N3Lexer({
|
|
57
|
+
lineMode: isLineMode, n3: isN3, isImpliedBy: this._isImpliedBy, directives: options.directives,
|
|
58
|
+
});
|
|
50
59
|
// Disable explicit quantifiers by default
|
|
51
60
|
this._explicitQuantifiers = !!options.explicitQuantifiers;
|
|
52
61
|
// Disable formula-only blank node scoping by default
|
|
@@ -228,8 +237,13 @@ export default class N3Parser {
|
|
|
228
237
|
case 'GRAPH':
|
|
229
238
|
if (this._supportsNamedGraphs)
|
|
230
239
|
return this._readNamedGraphLabel;
|
|
231
|
-
// Otherwise, the next token must be a subject
|
|
240
|
+
// Otherwise, the next token must be a subject, unless it is an additional directive
|
|
232
241
|
default:
|
|
242
|
+
if (this._directives !== null && token.type in this._directives && this._graph === null) {
|
|
243
|
+
this._sparqlStyle = token.type[0] !== '@';
|
|
244
|
+
this._directiveCallback(this._directives[token.type]);
|
|
245
|
+
return this._readDeclarationPunctuation;
|
|
246
|
+
}
|
|
233
247
|
return this._readSubject(token);
|
|
234
248
|
}
|
|
235
249
|
}
|
|
@@ -1634,13 +1648,14 @@ export default class N3Parser {
|
|
|
1634
1648
|
parse(input, quadCallback, prefixCallback, versionCallback) {
|
|
1635
1649
|
// The second parameter also accepts an object of named callbacks.
|
|
1636
1650
|
// As a second and third parameter it still accepts a separate quadCallback and prefixCallback for backward compatibility as well
|
|
1637
|
-
let onQuad, onPrefix, onComment, onVersion, onToken, onTokenEnd;
|
|
1651
|
+
let onQuad, onPrefix, onComment, onVersion, onDirective, onToken, onTokenEnd;
|
|
1638
1652
|
if (quadCallback && (quadCallback.onQuad || quadCallback.onPrefix || quadCallback.onComment || quadCallback.onVersion ||
|
|
1639
|
-
quadCallback.onToken || quadCallback.onTokenEnd)) {
|
|
1653
|
+
quadCallback.onDirective || quadCallback.onToken || quadCallback.onTokenEnd)) {
|
|
1640
1654
|
onQuad = quadCallback.onQuad;
|
|
1641
1655
|
onPrefix = quadCallback.onPrefix;
|
|
1642
1656
|
onComment = quadCallback.onComment;
|
|
1643
1657
|
onVersion = quadCallback.onVersion;
|
|
1658
|
+
onDirective = quadCallback.onDirective;
|
|
1644
1659
|
onToken = quadCallback.onToken;
|
|
1645
1660
|
onTokenEnd = quadCallback.onTokenEnd;
|
|
1646
1661
|
}
|
|
@@ -1661,6 +1676,7 @@ export default class N3Parser {
|
|
|
1661
1676
|
this._prefixes[''] = this._resolveIRI('#');
|
|
1662
1677
|
this._prefixCallback = onPrefix || noop;
|
|
1663
1678
|
this._versionCallback = onVersion || noop;
|
|
1679
|
+
this._directiveCallback = onDirective || noop;
|
|
1664
1680
|
this._inversePredicate = false;
|
|
1665
1681
|
this._expectOf = false;
|
|
1666
1682
|
this._quantified = Object.create(null);
|
package/src/N3StreamParser.js
CHANGED
|
@@ -16,6 +16,8 @@ export default class N3StreamParser extends Transform {
|
|
|
16
16
|
onQuad: (error, quad) => { error && this.emit('error', error) || quad && this.push(quad); },
|
|
17
17
|
// Emit prefixes through the `prefix` event
|
|
18
18
|
onPrefix: (prefix, uri) => { this.emit('prefix', prefix, uri); },
|
|
19
|
+
// Emit additional directives through the `directive` event
|
|
20
|
+
onDirective: name => { this.emit('directive', name); },
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
if (options && options.comments)
|
package/src/N3Writer.js
CHANGED
|
@@ -8,6 +8,7 @@ import { escapeRegex } from './Util';
|
|
|
8
8
|
const DEFAULTGRAPH = N3DataFactory.defaultGraph();
|
|
9
9
|
|
|
10
10
|
const { rdf, xsd } = namespaces;
|
|
11
|
+
const { hasOwnProperty } = Object.prototype;
|
|
11
12
|
|
|
12
13
|
// Characters that require escaping in Turtle, TriG, and N3,
|
|
13
14
|
// where characters outside the Basic Multilingual Plane are escaped as well
|
|
@@ -23,6 +24,16 @@ const escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/,
|
|
|
23
24
|
'\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f',
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
// A local name (PN_LOCAL) as written in a prefixed name, without backslash escapes,
|
|
28
|
+
// so that it denotes the same characters as the IRI it is taken from.
|
|
29
|
+
// Characters outside the Basic Multilingual Plane are escaped before matching, so they never occur.
|
|
30
|
+
const localNameStart = 'A-Za-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF' +
|
|
31
|
+
'\\u200C\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD_:0-9',
|
|
32
|
+
localNameChar = `${localNameStart}\\-\\u00B7\\u0300-\\u036F\\u203F\\u2040`,
|
|
33
|
+
percent = '%[0-9A-Fa-f]{2}',
|
|
34
|
+
localName = new RegExp(`^(?:(?:[${localNameStart}]|${percent})` +
|
|
35
|
+
`(?:(?:[${localNameChar}.]|${percent})*(?:[${localNameChar}]|${percent}))?)?$`);
|
|
36
|
+
|
|
26
37
|
// ## Placeholder class to represent already pretty-printed terms
|
|
27
38
|
class SerializedTerm extends Term {
|
|
28
39
|
// Pretty-printed nodes are not equal to any other node
|
|
@@ -173,7 +184,7 @@ export default class N3Writer {
|
|
|
173
184
|
// A blank node or list is represented as-is
|
|
174
185
|
if (entity.termType !== 'NamedNode') {
|
|
175
186
|
// If it is a list head, pretty-print it
|
|
176
|
-
if (this._lists && (entity.value
|
|
187
|
+
if (this._lists && hasOwnProperty.call(this._lists, entity.value))
|
|
177
188
|
entity = this.list(this._lists[entity.value]);
|
|
178
189
|
// Terms from this library already hold their serialization as id
|
|
179
190
|
if (entity instanceof Term)
|
|
@@ -190,8 +201,13 @@ export default class N3Writer {
|
|
|
190
201
|
iri = iri.replace(this._escapeAll, this._characterReplacer);
|
|
191
202
|
// Try to represent the IRI as prefixed name, unless no prefixes were added
|
|
192
203
|
const prefixMatch = this._hasPrefixes ? (this._prefixRegex || this._createPrefixRegex()).exec(iri) : null;
|
|
193
|
-
|
|
194
|
-
|
|
204
|
+
if (!prefixMatch)
|
|
205
|
+
return `<${iri}>`;
|
|
206
|
+
// An IRI that starts with a registered prefix IRI
|
|
207
|
+
if (prefixMatch[1])
|
|
208
|
+
return this._prefixIRIs[prefixMatch[1]] + prefixMatch[2];
|
|
209
|
+
// An IRI that already looks like a prefixed name, if the rest is a valid local name
|
|
210
|
+
return localName.test(iri.slice(prefixMatch[0].length)) ? iri : `<${iri}>`;
|
|
195
211
|
}
|
|
196
212
|
|
|
197
213
|
// ### `_encodeLiteral` represents a literal
|
|
@@ -351,8 +367,8 @@ export default class N3Writer {
|
|
|
351
367
|
IRIlist += IRIlist ? `|${IRIpattern}` : IRIpattern;
|
|
352
368
|
prefixList += prefixList ? `|${prefixPattern}` : prefixPattern;
|
|
353
369
|
}
|
|
354
|
-
return this._prefixRegex = new RegExp(`^(
|
|
355
|
-
`^(
|
|
370
|
+
return this._prefixRegex = new RegExp(`^(${IRIlist})([_a-zA-Z0-9](?:\\.?[\\-_a-zA-Z0-9])*)$|` +
|
|
371
|
+
`^(?:${prefixList})`);
|
|
356
372
|
}
|
|
357
373
|
|
|
358
374
|
// ### `blank` creates a blank node with the given content
|