n3 1.17.0 → 1.17.2
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 +10 -6
- package/browser/n3.min.js +1 -1
- package/lib/IRIs.js +4 -5
- package/lib/N3DataFactory.js +121 -137
- package/lib/N3Lexer.js +106 -139
- package/lib/N3Parser.js +275 -403
- package/lib/N3Store.js +294 -317
- package/lib/N3StreamParser.js +11 -14
- package/lib/N3StreamWriter.js +7 -11
- package/lib/N3Util.js +19 -25
- package/lib/N3Writer.js +124 -154
- package/lib/index.js +4 -18
- package/package.json +41 -18
- package/src/N3DataFactory.js +31 -24
- package/src/N3Store.js +68 -49
- package/src/N3Writer.js +2 -2
package/lib/N3Lexer.js
CHANGED
|
@@ -4,18 +4,16 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
-
|
|
8
7
|
var _IRIs = _interopRequireDefault(require("./IRIs"));
|
|
9
|
-
|
|
10
8
|
var _queueMicrotask = _interopRequireDefault(require("queue-microtask"));
|
|
11
|
-
|
|
12
9
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
|
-
|
|
14
10
|
// **N3Lexer** tokenizes N3 documents.
|
|
11
|
+
|
|
15
12
|
const {
|
|
16
13
|
xsd
|
|
17
|
-
} = _IRIs.default;
|
|
14
|
+
} = _IRIs.default;
|
|
18
15
|
|
|
16
|
+
// Regular expression and replacement string to escape N3 strings
|
|
19
17
|
const escapeSequence = /\\u([a-fA-F0-9]{4})|\\U([a-fA-F0-9]{8})|\\([^])/g;
|
|
20
18
|
const escapeReplacements = {
|
|
21
19
|
'\\': '\\',
|
|
@@ -58,18 +56,16 @@ const lineModeRegExps = {
|
|
|
58
56
|
_whitespace: true,
|
|
59
57
|
_endOfFile: true
|
|
60
58
|
};
|
|
61
|
-
const invalidRegExp = /$0^/;
|
|
59
|
+
const invalidRegExp = /$0^/;
|
|
62
60
|
|
|
61
|
+
// ## Constructor
|
|
63
62
|
class N3Lexer {
|
|
64
63
|
constructor(options) {
|
|
65
64
|
// ## Regular expressions
|
|
66
65
|
// It's slightly faster to have these as properties than as in-scope variables
|
|
67
66
|
this._iri = /^<((?:[^ <>{}\\]|\\[uU])+)>[ \t]*/; // IRI with escape sequences; needs sanity check after unescaping
|
|
68
|
-
|
|
69
67
|
this._unescapedIri = /^<([^\x00-\x20<>\\"\{\}\|\^\`]*)>[ \t]*/; // IRI without escape sequences; no unescaping
|
|
70
|
-
|
|
71
68
|
this._simpleQuotedString = /^"([^"\\\r\n]*)"(?=[^"])/; // string without escape sequences
|
|
72
|
-
|
|
73
69
|
this._simpleApostropheString = /^'([^'\\\r\n]*)'(?=[^'])/;
|
|
74
70
|
this._langcode = /^@([a-z]+(?:-[a-z0-9]+)*)(?=[^a-z0-9\-])/i;
|
|
75
71
|
this._prefix = /^((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:(?=[#\s<])/;
|
|
@@ -85,48 +81,48 @@ class N3Lexer {
|
|
|
85
81
|
this._comment = /#([^\n\r]*)/;
|
|
86
82
|
this._whitespace = /^[ \t]+/;
|
|
87
83
|
this._endOfFile = /^(?:#[^\n\r]*)?$/;
|
|
88
|
-
options = options || {};
|
|
84
|
+
options = options || {};
|
|
89
85
|
|
|
86
|
+
// In line mode (N-Triples or N-Quads), only simple features may be parsed
|
|
90
87
|
if (this._lineMode = !!options.lineMode) {
|
|
91
|
-
this._n3Mode = false;
|
|
92
|
-
|
|
88
|
+
this._n3Mode = false;
|
|
89
|
+
// Don't tokenize special literals
|
|
93
90
|
for (const key in this) {
|
|
94
91
|
if (!(key in lineModeRegExps) && this[key] instanceof RegExp) this[key] = invalidRegExp;
|
|
95
92
|
}
|
|
96
|
-
}
|
|
93
|
+
}
|
|
94
|
+
// When not in line mode, enable N3 functionality by default
|
|
97
95
|
else {
|
|
98
96
|
this._n3Mode = options.n3 !== false;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
}
|
|
98
|
+
// Don't output comment tokens by default
|
|
99
|
+
this._comments = !!options.comments;
|
|
100
|
+
// Cache the last tested closing position of long literals
|
|
104
101
|
this._literalClosingPos = 0;
|
|
105
|
-
}
|
|
106
|
-
// ### `_tokenizeToEnd` tokenizes as for as possible, emitting tokens through the callback
|
|
102
|
+
}
|
|
107
103
|
|
|
104
|
+
// ## Private methods
|
|
108
105
|
|
|
106
|
+
// ### `_tokenizeToEnd` tokenizes as for as possible, emitting tokens through the callback
|
|
109
107
|
_tokenizeToEnd(callback, inputFinished) {
|
|
110
108
|
// Continue parsing as far as possible; the loop will return eventually
|
|
111
109
|
let input = this._input;
|
|
112
110
|
let currentLineLength = input.length;
|
|
113
|
-
|
|
114
111
|
while (true) {
|
|
115
112
|
// Count and skip whitespace lines
|
|
116
113
|
let whiteSpaceMatch, comment;
|
|
117
|
-
|
|
118
114
|
while (whiteSpaceMatch = this._newline.exec(input)) {
|
|
119
115
|
// Try to find a comment
|
|
120
|
-
if (this._comments && (comment = this._comment.exec(whiteSpaceMatch[0]))) emitToken('comment', comment[1], '', this._line, whiteSpaceMatch[0].length);
|
|
121
|
-
|
|
116
|
+
if (this._comments && (comment = this._comment.exec(whiteSpaceMatch[0]))) emitToken('comment', comment[1], '', this._line, whiteSpaceMatch[0].length);
|
|
117
|
+
// Advance the input
|
|
122
118
|
input = input.substr(whiteSpaceMatch[0].length, input.length);
|
|
123
119
|
currentLineLength = input.length;
|
|
124
120
|
this._line++;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (!whiteSpaceMatch && (whiteSpaceMatch = this._whitespace.exec(input))) input = input.substr(whiteSpaceMatch[0].length, input.length); // Stop for now if we're at the end
|
|
121
|
+
}
|
|
122
|
+
// Skip whitespace on current line
|
|
123
|
+
if (!whiteSpaceMatch && (whiteSpaceMatch = this._whitespace.exec(input))) input = input.substr(whiteSpaceMatch[0].length, input.length);
|
|
129
124
|
|
|
125
|
+
// Stop for now if we're at the end
|
|
130
126
|
if (this._endOfFile.test(input)) {
|
|
131
127
|
// If the input is finished, emit EOF
|
|
132
128
|
if (inputFinished) {
|
|
@@ -135,70 +131,68 @@ class N3Lexer {
|
|
|
135
131
|
input = null;
|
|
136
132
|
emitToken('eof', '', '', this._line, 0);
|
|
137
133
|
}
|
|
138
|
-
|
|
139
134
|
return this._input = input;
|
|
140
|
-
}
|
|
141
|
-
|
|
135
|
+
}
|
|
142
136
|
|
|
137
|
+
// Look for specific token types based on the first character
|
|
143
138
|
const line = this._line,
|
|
144
|
-
|
|
139
|
+
firstChar = input[0];
|
|
145
140
|
let type = '',
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
141
|
+
value = '',
|
|
142
|
+
prefix = '',
|
|
143
|
+
match = null,
|
|
144
|
+
matchLength = 0,
|
|
145
|
+
inconclusive = false;
|
|
152
146
|
switch (firstChar) {
|
|
153
147
|
case '^':
|
|
154
148
|
// We need at least 3 tokens lookahead to distinguish ^^<IRI> and ^^pre:fixed
|
|
155
|
-
if (input.length < 3) break;
|
|
149
|
+
if (input.length < 3) break;
|
|
150
|
+
// Try to match a type
|
|
156
151
|
else if (input[1] === '^') {
|
|
157
|
-
this._previousMarker = '^^';
|
|
158
|
-
|
|
152
|
+
this._previousMarker = '^^';
|
|
153
|
+
// Move to type IRI or prefixed name
|
|
159
154
|
input = input.substr(2);
|
|
160
|
-
|
|
161
155
|
if (input[0] !== '<') {
|
|
162
156
|
inconclusive = true;
|
|
163
157
|
break;
|
|
164
158
|
}
|
|
165
|
-
}
|
|
159
|
+
}
|
|
160
|
+
// If no type, it must be a path expression
|
|
166
161
|
else {
|
|
167
162
|
if (this._n3Mode) {
|
|
168
163
|
matchLength = 1;
|
|
169
164
|
type = '^';
|
|
170
165
|
}
|
|
171
|
-
|
|
172
166
|
break;
|
|
173
167
|
}
|
|
174
168
|
// Fall through in case the type is an IRI
|
|
175
|
-
|
|
176
169
|
case '<':
|
|
177
170
|
// Try to find a full IRI without escape sequences
|
|
178
|
-
if (match = this._unescapedIri.exec(input)) type = 'IRI', value = match[1];
|
|
171
|
+
if (match = this._unescapedIri.exec(input)) type = 'IRI', value = match[1];
|
|
172
|
+
// Try to find a full IRI with escape sequences
|
|
179
173
|
else if (match = this._iri.exec(input)) {
|
|
180
174
|
value = this._unescape(match[1]);
|
|
181
175
|
if (value === null || illegalIriChars.test(value)) return reportSyntaxError(this);
|
|
182
176
|
type = 'IRI';
|
|
183
|
-
}
|
|
184
|
-
|
|
177
|
+
}
|
|
178
|
+
// Try to find a nested triple
|
|
179
|
+
else if (input.length > 1 && input[1] === '<') type = '<<', matchLength = 2;
|
|
180
|
+
// Try to find a backwards implication arrow
|
|
185
181
|
else if (this._n3Mode && input.length > 1 && input[1] === '=') type = 'inverse', matchLength = 2, value = '>';
|
|
186
182
|
break;
|
|
187
|
-
|
|
188
183
|
case '>':
|
|
189
184
|
if (input.length > 1 && input[1] === '>') type = '>>', matchLength = 2;
|
|
190
185
|
break;
|
|
191
|
-
|
|
192
186
|
case '_':
|
|
193
187
|
// Try to find a blank node. Since it can contain (but not end with) a dot,
|
|
194
188
|
// we always need a non-dot character before deciding it is a blank node.
|
|
195
189
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
196
190
|
if ((match = this._blank.exec(input)) || inputFinished && (match = this._blank.exec(`${input} `))) type = 'blank', prefix = '_', value = match[1];
|
|
197
191
|
break;
|
|
198
|
-
|
|
199
192
|
case '"':
|
|
200
193
|
// Try to find a literal without escape sequences
|
|
201
|
-
if (match = this._simpleQuotedString.exec(input)) value = match[1];
|
|
194
|
+
if (match = this._simpleQuotedString.exec(input)) value = match[1];
|
|
195
|
+
// Try to find a literal wrapped in three pairs of quotes
|
|
202
196
|
else {
|
|
203
197
|
({
|
|
204
198
|
value,
|
|
@@ -206,18 +200,16 @@ class N3Lexer {
|
|
|
206
200
|
} = this._parseLiteral(input));
|
|
207
201
|
if (value === null) return reportSyntaxError(this);
|
|
208
202
|
}
|
|
209
|
-
|
|
210
203
|
if (match !== null || matchLength !== 0) {
|
|
211
204
|
type = 'literal';
|
|
212
205
|
this._literalClosingPos = 0;
|
|
213
206
|
}
|
|
214
|
-
|
|
215
207
|
break;
|
|
216
|
-
|
|
217
208
|
case "'":
|
|
218
209
|
if (!this._lineMode) {
|
|
219
210
|
// Try to find a literal without escape sequences
|
|
220
|
-
if (match = this._simpleApostropheString.exec(input)) value = match[1];
|
|
211
|
+
if (match = this._simpleApostropheString.exec(input)) value = match[1];
|
|
212
|
+
// Try to find a literal wrapped in three pairs of quotes
|
|
221
213
|
else {
|
|
222
214
|
({
|
|
223
215
|
value,
|
|
@@ -225,26 +217,22 @@ class N3Lexer {
|
|
|
225
217
|
} = this._parseLiteral(input));
|
|
226
218
|
if (value === null) return reportSyntaxError(this);
|
|
227
219
|
}
|
|
228
|
-
|
|
229
220
|
if (match !== null || matchLength !== 0) {
|
|
230
221
|
type = 'literal';
|
|
231
222
|
this._literalClosingPos = 0;
|
|
232
223
|
}
|
|
233
224
|
}
|
|
234
|
-
|
|
235
225
|
break;
|
|
236
|
-
|
|
237
226
|
case '?':
|
|
238
227
|
// Try to find a variable
|
|
239
228
|
if (this._n3Mode && (match = this._variable.exec(input))) type = 'var', value = match[0];
|
|
240
229
|
break;
|
|
241
|
-
|
|
242
230
|
case '@':
|
|
243
231
|
// Try to find a language code
|
|
244
|
-
if (this._previousMarker === 'literal' && (match = this._langcode.exec(input))) type = 'langcode', value = match[1];
|
|
232
|
+
if (this._previousMarker === 'literal' && (match = this._langcode.exec(input))) type = 'langcode', value = match[1];
|
|
233
|
+
// Try to find a keyword
|
|
245
234
|
else if (match = this._keyword.exec(input)) type = match[0];
|
|
246
235
|
break;
|
|
247
|
-
|
|
248
236
|
case '.':
|
|
249
237
|
// Try to find a dot as punctuation
|
|
250
238
|
if (input.length === 1 ? inputFinished : input[1] < '0' || input[1] > '9') {
|
|
@@ -252,7 +240,6 @@ class N3Lexer {
|
|
|
252
240
|
matchLength = 1;
|
|
253
241
|
break;
|
|
254
242
|
}
|
|
255
|
-
|
|
256
243
|
// Fall through to numerical case (could be a decimal dot)
|
|
257
244
|
|
|
258
245
|
case '0':
|
|
@@ -274,9 +261,7 @@ class N3Lexer {
|
|
|
274
261
|
type = 'literal', value = match[0];
|
|
275
262
|
prefix = typeof match[1] === 'string' ? xsd.double : typeof match[2] === 'string' ? xsd.decimal : xsd.integer;
|
|
276
263
|
}
|
|
277
|
-
|
|
278
264
|
break;
|
|
279
|
-
|
|
280
265
|
case 'B':
|
|
281
266
|
case 'b':
|
|
282
267
|
case 'p':
|
|
@@ -286,30 +271,24 @@ class N3Lexer {
|
|
|
286
271
|
// Try to find a SPARQL-style keyword
|
|
287
272
|
if (match = this._sparqlKeyword.exec(input)) type = match[0].toUpperCase();else inconclusive = true;
|
|
288
273
|
break;
|
|
289
|
-
|
|
290
274
|
case 'f':
|
|
291
275
|
case 't':
|
|
292
276
|
// Try to match a boolean
|
|
293
277
|
if (match = this._boolean.exec(input)) type = 'literal', value = match[0], prefix = xsd.boolean;else inconclusive = true;
|
|
294
278
|
break;
|
|
295
|
-
|
|
296
279
|
case 'a':
|
|
297
280
|
// Try to find an abbreviated predicate
|
|
298
281
|
if (match = this._shortPredicates.exec(input)) type = 'abbreviation', value = 'a';else inconclusive = true;
|
|
299
282
|
break;
|
|
300
|
-
|
|
301
283
|
case '=':
|
|
302
284
|
// Try to find an implication arrow or equals sign
|
|
303
285
|
if (this._n3Mode && input.length > 1) {
|
|
304
286
|
type = 'abbreviation';
|
|
305
287
|
if (input[1] !== '>') matchLength = 1, value = '=';else matchLength = 2, value = '>';
|
|
306
288
|
}
|
|
307
|
-
|
|
308
289
|
break;
|
|
309
|
-
|
|
310
290
|
case '!':
|
|
311
291
|
if (!this._n3Mode) break;
|
|
312
|
-
|
|
313
292
|
case ',':
|
|
314
293
|
case ';':
|
|
315
294
|
case '[':
|
|
@@ -321,71 +300,66 @@ class N3Lexer {
|
|
|
321
300
|
matchLength = 1;
|
|
322
301
|
type = firstChar;
|
|
323
302
|
}
|
|
324
|
-
|
|
325
303
|
break;
|
|
326
|
-
|
|
327
304
|
case '{':
|
|
328
305
|
// We need at least 2 tokens lookahead to distinguish "{|" and "{ "
|
|
329
306
|
if (!this._lineMode && input.length >= 2) {
|
|
330
307
|
// Try to find a quoted triple annotation start
|
|
331
308
|
if (input[1] === '|') type = '{|', matchLength = 2;else type = firstChar, matchLength = 1;
|
|
332
309
|
}
|
|
333
|
-
|
|
334
310
|
break;
|
|
335
|
-
|
|
336
311
|
case '|':
|
|
337
312
|
// We need 2 tokens lookahead to parse "|}"
|
|
338
313
|
// Try to find a quoted triple annotation end
|
|
339
314
|
if (input.length >= 2 && input[1] === '}') type = '|}', matchLength = 2;
|
|
340
315
|
break;
|
|
341
|
-
|
|
342
316
|
default:
|
|
343
317
|
inconclusive = true;
|
|
344
|
-
}
|
|
345
|
-
|
|
318
|
+
}
|
|
346
319
|
|
|
320
|
+
// Some first characters do not allow an immediate decision, so inspect more
|
|
347
321
|
if (inconclusive) {
|
|
348
322
|
// Try to find a prefix
|
|
349
|
-
if ((this._previousMarker === '@prefix' || this._previousMarker === 'PREFIX') && (match = this._prefix.exec(input))) type = 'prefix', value = match[1] || '';
|
|
323
|
+
if ((this._previousMarker === '@prefix' || this._previousMarker === 'PREFIX') && (match = this._prefix.exec(input))) type = 'prefix', value = match[1] || '';
|
|
324
|
+
// Try to find a prefixed name. Since it can contain (but not end with) a dot,
|
|
350
325
|
// we always need a non-dot character before deciding it is a prefixed name.
|
|
351
326
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
352
327
|
else if ((match = this._prefixed.exec(input)) || inputFinished && (match = this._prefixed.exec(`${input} `))) type = 'prefixed', prefix = match[1] || '', value = this._unescape(match[2]);
|
|
353
|
-
}
|
|
354
|
-
|
|
328
|
+
}
|
|
355
329
|
|
|
330
|
+
// A type token is special: it can only be emitted after an IRI or prefixed name is read
|
|
356
331
|
if (this._previousMarker === '^^') {
|
|
357
332
|
switch (type) {
|
|
358
333
|
case 'prefixed':
|
|
359
334
|
type = 'type';
|
|
360
335
|
break;
|
|
361
|
-
|
|
362
336
|
case 'IRI':
|
|
363
337
|
type = 'typeIRI';
|
|
364
338
|
break;
|
|
365
|
-
|
|
366
339
|
default:
|
|
367
340
|
type = '';
|
|
368
341
|
}
|
|
369
|
-
}
|
|
370
|
-
|
|
342
|
+
}
|
|
371
343
|
|
|
344
|
+
// What if nothing of the above was found?
|
|
372
345
|
if (!type) {
|
|
373
346
|
// We could be in streaming mode, and then we just wait for more input to arrive.
|
|
374
347
|
// Otherwise, a syntax error has occurred in the input.
|
|
375
348
|
// One exception: error on an unaccounted linebreak (= not inside a triple-quoted literal).
|
|
376
349
|
if (inputFinished || !/^'''|^"""/.test(input) && /\n|\r/.test(input)) return reportSyntaxError(this);else return this._input = input;
|
|
377
|
-
}
|
|
378
|
-
|
|
350
|
+
}
|
|
379
351
|
|
|
352
|
+
// Emit the parsed token
|
|
380
353
|
const length = matchLength || match[0].length;
|
|
381
354
|
const token = emitToken(type, value, prefix, line, length);
|
|
382
355
|
this.previousToken = token;
|
|
383
|
-
this._previousMarker = type;
|
|
356
|
+
this._previousMarker = type;
|
|
384
357
|
|
|
358
|
+
// Advance to next part to tokenize
|
|
385
359
|
input = input.substr(length, input.length);
|
|
386
|
-
}
|
|
387
|
-
|
|
360
|
+
}
|
|
388
361
|
|
|
362
|
+
// Emits the token through the callback
|
|
389
363
|
function emitToken(type, value, prefix, line, length) {
|
|
390
364
|
const start = input ? currentLineLength - input.length : currentLineLength;
|
|
391
365
|
const end = start + length;
|
|
@@ -399,59 +373,56 @@ class N3Lexer {
|
|
|
399
373
|
};
|
|
400
374
|
callback(null, token);
|
|
401
375
|
return token;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
376
|
+
}
|
|
377
|
+
// Signals the syntax error through the callback
|
|
405
378
|
function reportSyntaxError(self) {
|
|
406
379
|
callback(self._syntaxError(/^\S*/.exec(input)[0]));
|
|
407
380
|
}
|
|
408
|
-
}
|
|
409
|
-
|
|
381
|
+
}
|
|
410
382
|
|
|
383
|
+
// ### `_unescape` replaces N3 escape codes by their corresponding characters
|
|
411
384
|
_unescape(item) {
|
|
412
385
|
let invalid = false;
|
|
413
386
|
const replaced = item.replace(escapeSequence, (sequence, unicode4, unicode8, escapedChar) => {
|
|
414
387
|
// 4-digit unicode character
|
|
415
|
-
if (typeof unicode4 === 'string') return String.fromCharCode(Number.parseInt(unicode4, 16));
|
|
416
|
-
|
|
388
|
+
if (typeof unicode4 === 'string') return String.fromCharCode(Number.parseInt(unicode4, 16));
|
|
389
|
+
// 8-digit unicode character
|
|
417
390
|
if (typeof unicode8 === 'string') {
|
|
418
391
|
let charCode = Number.parseInt(unicode8, 16);
|
|
419
392
|
return charCode <= 0xFFFF ? String.fromCharCode(Number.parseInt(unicode8, 16)) : String.fromCharCode(0xD800 + ((charCode -= 0x10000) >> 10), 0xDC00 + (charCode & 0x3FF));
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
393
|
+
}
|
|
394
|
+
// fixed escape sequence
|
|
395
|
+
if (escapedChar in escapeReplacements) return escapeReplacements[escapedChar];
|
|
396
|
+
// invalid escape sequence
|
|
425
397
|
invalid = true;
|
|
426
398
|
return '';
|
|
427
399
|
});
|
|
428
400
|
return invalid ? null : replaced;
|
|
429
|
-
}
|
|
430
|
-
|
|
401
|
+
}
|
|
431
402
|
|
|
403
|
+
// ### `_parseLiteral` parses a literal into an unescaped value
|
|
432
404
|
_parseLiteral(input) {
|
|
433
405
|
// Ensure we have enough lookahead to identify triple-quoted strings
|
|
434
406
|
if (input.length >= 3) {
|
|
435
407
|
// Identify the opening quote(s)
|
|
436
408
|
const opening = input.match(/^(?:"""|"|'''|'|)/)[0];
|
|
437
|
-
const openingLength = opening.length;
|
|
409
|
+
const openingLength = opening.length;
|
|
438
410
|
|
|
411
|
+
// Find the next candidate closing quotes
|
|
439
412
|
let closingPos = Math.max(this._literalClosingPos, openingLength);
|
|
440
|
-
|
|
441
413
|
while ((closingPos = input.indexOf(opening, closingPos)) > 0) {
|
|
442
414
|
// Count backslashes right before the closing quotes
|
|
443
415
|
let backslashCount = 0;
|
|
416
|
+
while (input[closingPos - backslashCount - 1] === '\\') backslashCount++;
|
|
444
417
|
|
|
445
|
-
|
|
418
|
+
// An even number of backslashes (in particular 0)
|
|
446
419
|
// means these are actual, non-escaped closing quotes
|
|
447
|
-
|
|
448
|
-
|
|
449
420
|
if (backslashCount % 2 === 0) {
|
|
450
421
|
// Extract and unescape the value
|
|
451
422
|
const raw = input.substring(openingLength, closingPos);
|
|
452
423
|
const lines = raw.split(/\r\n|\r|\n/).length - 1;
|
|
453
|
-
const matchLength = closingPos + openingLength;
|
|
454
|
-
|
|
424
|
+
const matchLength = closingPos + openingLength;
|
|
425
|
+
// Only triple-quoted strings can be multi-line
|
|
455
426
|
if (openingLength === 1 && lines !== 0 || openingLength === 3 && this._lineMode) break;
|
|
456
427
|
this._line += lines;
|
|
457
428
|
return {
|
|
@@ -459,20 +430,17 @@ class N3Lexer {
|
|
|
459
430
|
matchLength
|
|
460
431
|
};
|
|
461
432
|
}
|
|
462
|
-
|
|
463
433
|
closingPos++;
|
|
464
434
|
}
|
|
465
|
-
|
|
466
435
|
this._literalClosingPos = input.length - openingLength + 1;
|
|
467
436
|
}
|
|
468
|
-
|
|
469
437
|
return {
|
|
470
438
|
value: '',
|
|
471
439
|
matchLength: 0
|
|
472
440
|
};
|
|
473
|
-
}
|
|
474
|
-
|
|
441
|
+
}
|
|
475
442
|
|
|
443
|
+
// ### `_syntaxError` creates a syntax error for the given issue
|
|
476
444
|
_syntaxError(issue) {
|
|
477
445
|
this._input = null;
|
|
478
446
|
const err = new Error(`Unexpected "${issue}" on line ${this._line}.`);
|
|
@@ -482,65 +450,64 @@ class N3Lexer {
|
|
|
482
450
|
previousToken: this.previousToken
|
|
483
451
|
};
|
|
484
452
|
return err;
|
|
485
|
-
}
|
|
486
|
-
|
|
453
|
+
}
|
|
487
454
|
|
|
455
|
+
// ### Strips off any starting UTF BOM mark.
|
|
488
456
|
_readStartingBom(input) {
|
|
489
457
|
return input.startsWith('\ufeff') ? input.substr(1) : input;
|
|
490
|
-
}
|
|
491
|
-
// ### `tokenize` starts the transformation of an N3 document into an array of tokens.
|
|
492
|
-
// The input can be a string or a stream.
|
|
458
|
+
}
|
|
493
459
|
|
|
460
|
+
// ## Public methods
|
|
494
461
|
|
|
462
|
+
// ### `tokenize` starts the transformation of an N3 document into an array of tokens.
|
|
463
|
+
// The input can be a string or a stream.
|
|
495
464
|
tokenize(input, callback) {
|
|
496
|
-
this._line = 1;
|
|
465
|
+
this._line = 1;
|
|
497
466
|
|
|
467
|
+
// If the input is a string, continuously emit tokens through the callback until the end
|
|
498
468
|
if (typeof input === 'string') {
|
|
499
|
-
this._input = this._readStartingBom(input);
|
|
500
|
-
|
|
501
|
-
if (typeof callback === 'function') (0, _queueMicrotask.default)(() => this._tokenizeToEnd(callback, true));
|
|
469
|
+
this._input = this._readStartingBom(input);
|
|
470
|
+
// If a callback was passed, asynchronously call it
|
|
471
|
+
if (typeof callback === 'function') (0, _queueMicrotask.default)(() => this._tokenizeToEnd(callback, true));
|
|
472
|
+
// If no callback was passed, tokenize synchronously and return
|
|
502
473
|
else {
|
|
503
474
|
const tokens = [];
|
|
504
475
|
let error;
|
|
505
|
-
|
|
506
476
|
this._tokenizeToEnd((e, t) => e ? error = e : tokens.push(t), true);
|
|
507
|
-
|
|
508
477
|
if (error) throw error;
|
|
509
478
|
return tokens;
|
|
510
479
|
}
|
|
511
|
-
}
|
|
480
|
+
}
|
|
481
|
+
// Otherwise, the input must be a stream
|
|
512
482
|
else {
|
|
513
483
|
this._pendingBuffer = null;
|
|
514
|
-
if (typeof input.setEncoding === 'function') input.setEncoding('utf8');
|
|
515
|
-
|
|
484
|
+
if (typeof input.setEncoding === 'function') input.setEncoding('utf8');
|
|
485
|
+
// Adds the data chunk to the buffer and parses as far as possible
|
|
516
486
|
input.on('data', data => {
|
|
517
487
|
if (this._input !== null && data.length !== 0) {
|
|
518
488
|
// Prepend any previous pending writes
|
|
519
489
|
if (this._pendingBuffer) {
|
|
520
490
|
data = Buffer.concat([this._pendingBuffer, data]);
|
|
521
491
|
this._pendingBuffer = null;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
|
|
492
|
+
}
|
|
493
|
+
// Hold if the buffer ends in an incomplete unicode sequence
|
|
525
494
|
if (data[data.length - 1] & 0x80) {
|
|
526
495
|
this._pendingBuffer = data;
|
|
527
|
-
}
|
|
496
|
+
}
|
|
497
|
+
// Otherwise, tokenize as far as possible
|
|
528
498
|
else {
|
|
529
499
|
// Only read a BOM at the start
|
|
530
500
|
if (typeof this._input === 'undefined') this._input = this._readStartingBom(typeof data === 'string' ? data : data.toString());else this._input += data;
|
|
531
|
-
|
|
532
501
|
this._tokenizeToEnd(callback, false);
|
|
533
502
|
}
|
|
534
503
|
}
|
|
535
|
-
});
|
|
536
|
-
|
|
504
|
+
});
|
|
505
|
+
// Parses until the end
|
|
537
506
|
input.on('end', () => {
|
|
538
507
|
if (typeof this._input === 'string') this._tokenizeToEnd(callback, true);
|
|
539
508
|
});
|
|
540
509
|
input.on('error', callback);
|
|
541
510
|
}
|
|
542
511
|
}
|
|
543
|
-
|
|
544
512
|
}
|
|
545
|
-
|
|
546
513
|
exports.default = N3Lexer;
|