parse5 8.0.1 → 8.1.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 +12 -0
- package/dist/common/foreign-content.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/parser/formatting-element-list.d.ts +38 -10
- package/dist/parser/formatting-element-list.js +153 -70
- package/dist/parser/index.d.ts +18 -9
- package/dist/parser/index.js +215 -152
- package/dist/parser/open-element-stack.d.ts +85 -8
- package/dist/parser/open-element-stack.js +500 -150
- package/dist/serializer/index.js +48 -34
- package/dist/tokenizer/index.d.ts +37 -7
- package/dist/tokenizer/index.js +321 -71
- package/dist/tokenizer/preprocessor.d.ts +8 -1
- package/dist/tokenizer/preprocessor.js +20 -7
- package/dist/tree-adapters/default.js +50 -6
- package/dist/tree-adapters/interface.d.ts +17 -0
- package/package.json +7 -3
package/dist/tokenizer/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Preprocessor } from './preprocessor.js';
|
|
2
2
|
import { CODE_POINTS as $, SEQUENCES as $$, REPLACEMENT_CHARACTER, isSurrogate, isUndefinedCodePoint, isControlCodePoint, } from '../common/unicode.js';
|
|
3
3
|
import { TokenType, getTokenAttr, } from '../common/token.js';
|
|
4
|
-
import {
|
|
4
|
+
import { HtmlEntityDecoder, DecodingMode } from 'entities/decode';
|
|
5
5
|
import { ERR } from '../common/error-codes.js';
|
|
6
6
|
import { TAG_ID, getTagID } from '../common/html.js';
|
|
7
|
+
// The few attributes a tag usually has are scanned for a duplicate name; from this many on, the names are
|
|
8
|
+
// collected in a Set instead, so that the check doesn't get quadratic in the number of attributes.
|
|
9
|
+
const ATTR_NAME_SET_THRESHOLD = 64;
|
|
7
10
|
//States
|
|
8
11
|
var State;
|
|
9
12
|
(function (State) {
|
|
@@ -113,7 +116,7 @@ function toAsciiLower(cp) {
|
|
|
113
116
|
return cp + 32;
|
|
114
117
|
}
|
|
115
118
|
function isWhitespace(cp) {
|
|
116
|
-
return cp === $.SPACE || cp === $.LINE_FEED || cp === $.TABULATION || cp === $.FORM_FEED;
|
|
119
|
+
return (cp === $.SPACE || cp === $.LINE_FEED || cp === $.TABULATION || cp === $.FORM_FEED || cp === $.CARRIAGE_RETURN);
|
|
117
120
|
}
|
|
118
121
|
function isScriptDataDoubleEscapeSequenceEnd(cp) {
|
|
119
122
|
return isWhitespace(cp) || cp === $.SOLIDUS || cp === $.GREATER_THAN_SIGN;
|
|
@@ -160,13 +163,31 @@ export class Tokenizer {
|
|
|
160
163
|
this.currentCharacterToken = null;
|
|
161
164
|
this.currentToken = null;
|
|
162
165
|
this.currentAttr = { name: '', value: '' };
|
|
166
|
+
this.currentAttrLocation = null;
|
|
167
|
+
this.currentAttrNames = null;
|
|
168
|
+
/**
|
|
169
|
+
* `[start, end)` of `preprocessor.html` still to be appended to the attribute value, comment data or character
|
|
170
|
+
* token being built (`_flushRun`): input that is used as is is appended as one slice, not a code point at a time.
|
|
171
|
+
*/
|
|
172
|
+
this.runStart = 0;
|
|
173
|
+
this.runEnd = 0;
|
|
163
174
|
this.preprocessor = new Preprocessor(handler);
|
|
164
175
|
this.currentLocation = this.getCurrentLocation(-1);
|
|
165
|
-
this.entityDecoder = new
|
|
176
|
+
this.entityDecoder = new HtmlEntityDecoder((cp, consumed) => {
|
|
166
177
|
// Note: Set `pos` _before_ flushing, as flushing might drop
|
|
167
178
|
// the current chunk and invalidate `entityStartPos`.
|
|
168
179
|
this.preprocessor.pos = this.entityStartPos + consumed - 1;
|
|
169
|
-
this.
|
|
180
|
+
if (!this._isCharacterReferenceInAttribute()) {
|
|
181
|
+
this._ensureCharacterToken(isWhitespace(cp)
|
|
182
|
+
? TokenType.WHITESPACE_CHARACTER
|
|
183
|
+
: cp === $.NULL
|
|
184
|
+
? TokenType.NULL_CHARACTER
|
|
185
|
+
: TokenType.CHARACTER);
|
|
186
|
+
}
|
|
187
|
+
this._extendRunWith(String.fromCodePoint(cp), 0);
|
|
188
|
+
// This callback runs twice for references that decode to two
|
|
189
|
+
// code points; keep `entityStartPos` valid if the chunk was dropped.
|
|
190
|
+
this.entityStartPos = this.preprocessor.pos - consumed + 1;
|
|
170
191
|
}, handler.onParseError
|
|
171
192
|
? {
|
|
172
193
|
missingSemicolonAfterCharacterReference: () => {
|
|
@@ -216,6 +237,99 @@ export class Tokenizer {
|
|
|
216
237
|
this.inLoop = false;
|
|
217
238
|
}
|
|
218
239
|
//API
|
|
240
|
+
/** Emit buffered character tokens without completing the current token or emitting EOF. */
|
|
241
|
+
flushCharacters() {
|
|
242
|
+
var _a;
|
|
243
|
+
if (this.inLoop) {
|
|
244
|
+
throw new Error('Cannot flush characters from a tokenizer callback');
|
|
245
|
+
}
|
|
246
|
+
const location = (_a = this.currentCharacterToken) === null || _a === void 0 ? void 0 : _a.location;
|
|
247
|
+
let nextLocation = null;
|
|
248
|
+
if (location) {
|
|
249
|
+
nextLocation = this.getCharacterTokenEndLocation(location);
|
|
250
|
+
this.currentLocation = nextLocation;
|
|
251
|
+
}
|
|
252
|
+
this.inLoop = true;
|
|
253
|
+
try {
|
|
254
|
+
this._emitCurrentCharacterToken(nextLocation);
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
this.inLoop = false;
|
|
258
|
+
}
|
|
259
|
+
this._runParsingLoop();
|
|
260
|
+
}
|
|
261
|
+
getCharacterTokenEndLocation(location) {
|
|
262
|
+
const endOffset = this.getCharacterTokenEndOffset();
|
|
263
|
+
const html = this.preprocessor.html.slice(location.startOffset - this.preprocessor.droppedBufferSize, endOffset - this.preprocessor.droppedBufferSize);
|
|
264
|
+
let line = location.startLine;
|
|
265
|
+
let lastLineBreak = -1;
|
|
266
|
+
for (let i = html.indexOf('\n'); i !== -1; i = html.indexOf('\n', i + 1)) {
|
|
267
|
+
line++;
|
|
268
|
+
lastLineBreak = i;
|
|
269
|
+
}
|
|
270
|
+
for (let i = html.indexOf('\r'); i !== -1; i = html.indexOf('\r', i + 1)) {
|
|
271
|
+
// Count CRLF as one line break by counting its LF only.
|
|
272
|
+
if (html.charCodeAt(i + 1) !== $.LINE_FEED) {
|
|
273
|
+
line++;
|
|
274
|
+
lastLineBreak = Math.max(lastLineBreak, i);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
startLine: line,
|
|
279
|
+
startCol: lastLineBreak === -1 ? location.startCol + html.length : html.length - lastLineBreak,
|
|
280
|
+
startOffset: endOffset,
|
|
281
|
+
endLine: -1,
|
|
282
|
+
endCol: -1,
|
|
283
|
+
endOffset: -1,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
// Return the input boundary before any unfinished token or buffered delimiter.
|
|
287
|
+
getCharacterTokenEndOffset() {
|
|
288
|
+
var _a;
|
|
289
|
+
const end = this.preprocessor.droppedBufferSize + this.preprocessor.pos + 1;
|
|
290
|
+
const tokenLocation = (_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.location;
|
|
291
|
+
switch (this.state) {
|
|
292
|
+
case State.TAG_OPEN:
|
|
293
|
+
case State.RCDATA_LESS_THAN_SIGN:
|
|
294
|
+
case State.RAWTEXT_LESS_THAN_SIGN:
|
|
295
|
+
case State.SCRIPT_DATA_LESS_THAN_SIGN:
|
|
296
|
+
case State.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN:
|
|
297
|
+
case State.CDATA_SECTION_BRACKET: {
|
|
298
|
+
return end - 1;
|
|
299
|
+
}
|
|
300
|
+
case State.END_TAG_OPEN:
|
|
301
|
+
case State.MARKUP_DECLARATION_OPEN:
|
|
302
|
+
case State.RCDATA_END_TAG_OPEN:
|
|
303
|
+
case State.RCDATA_END_TAG_NAME:
|
|
304
|
+
case State.RAWTEXT_END_TAG_OPEN:
|
|
305
|
+
case State.RAWTEXT_END_TAG_NAME:
|
|
306
|
+
case State.SCRIPT_DATA_END_TAG_OPEN:
|
|
307
|
+
case State.SCRIPT_DATA_END_TAG_NAME:
|
|
308
|
+
case State.SCRIPT_DATA_ESCAPED_END_TAG_OPEN:
|
|
309
|
+
case State.SCRIPT_DATA_ESCAPED_END_TAG_NAME:
|
|
310
|
+
case State.CDATA_SECTION_END: {
|
|
311
|
+
return end - 2;
|
|
312
|
+
}
|
|
313
|
+
case State.CHARACTER_REFERENCE: {
|
|
314
|
+
return this._isCharacterReferenceInAttribute() && tokenLocation
|
|
315
|
+
? tokenLocation.startOffset
|
|
316
|
+
: this.preprocessor.droppedBufferSize + this.entityStartPos;
|
|
317
|
+
}
|
|
318
|
+
case State.DOCTYPE:
|
|
319
|
+
case State.BEFORE_DOCTYPE_NAME: {
|
|
320
|
+
return this.currentLocation.startOffset;
|
|
321
|
+
}
|
|
322
|
+
default: {
|
|
323
|
+
if (tokenLocation &&
|
|
324
|
+
(this.state === State.TAG_NAME ||
|
|
325
|
+
(this.state >= State.BEFORE_ATTRIBUTE_NAME && this.state <= State.BOGUS_COMMENT) ||
|
|
326
|
+
(this.state >= State.COMMENT_START && this.state <= State.BOGUS_DOCTYPE))) {
|
|
327
|
+
return tokenLocation.startOffset;
|
|
328
|
+
}
|
|
329
|
+
return end;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
219
333
|
pause() {
|
|
220
334
|
this.paused = true;
|
|
221
335
|
}
|
|
@@ -245,6 +359,33 @@ export class Tokenizer {
|
|
|
245
359
|
this.preprocessor.insertHtmlAtCurrentPos(chunk);
|
|
246
360
|
this._runParsingLoop();
|
|
247
361
|
}
|
|
362
|
+
/** @internal Flush completed streaming text without splitting an unfinished token. */
|
|
363
|
+
flushCurrentCharacterToken() {
|
|
364
|
+
// Only completed text is flushed: an unfinished token stays intact, including a doctype that has no token yet.
|
|
365
|
+
if (this.paused ||
|
|
366
|
+
this.active ||
|
|
367
|
+
!this.currentCharacterToken ||
|
|
368
|
+
this.currentToken ||
|
|
369
|
+
this.state === State.DOCTYPE ||
|
|
370
|
+
this.state === State.BEFORE_DOCTYPE_NAME ||
|
|
371
|
+
!this.preprocessor.willDropParsedChunk()) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
// The last `pending` code units, a delimiter or character reference, are not text yet and stay buffered. They
|
|
375
|
+
// are ASCII, so they hold no preprocessing gap and no line break that the dropped gap history would have to keep.
|
|
376
|
+
const pending = this.preprocessor.droppedBufferSize + this.preprocessor.pos + 1 - this.getCharacterTokenEndOffset();
|
|
377
|
+
// Hibernation left us before the next code point (possibly an incomplete surrogate).
|
|
378
|
+
this.preprocessor.advance();
|
|
379
|
+
this.currentLocation = this.getCurrentLocation(pending);
|
|
380
|
+
this.preprocessor.retreat(1);
|
|
381
|
+
this._emitCurrentCharacterToken(this.currentLocation);
|
|
382
|
+
// Keep a source range for CDATA delimiters or a later tag discarded at EOF. An empty null-character token
|
|
383
|
+
// would become a replacement character in foreign content.
|
|
384
|
+
this._createCharacterToken(TokenType.CHARACTER);
|
|
385
|
+
this.preprocessor.dropParsedChunk(Math.max(pending - 1, 0));
|
|
386
|
+
// Keep `entityStartPos` valid for a pending character reference.
|
|
387
|
+
this.entityStartPos = this.preprocessor.pos + 1 - pending;
|
|
388
|
+
}
|
|
248
389
|
//Hibernation
|
|
249
390
|
_ensureHibernation() {
|
|
250
391
|
if (this.preprocessor.endOfChunkHit) {
|
|
@@ -276,6 +417,7 @@ export class Tokenizer {
|
|
|
276
417
|
}
|
|
277
418
|
//Token creation
|
|
278
419
|
_createStartTagToken() {
|
|
420
|
+
this._flushRun('');
|
|
279
421
|
this.currentToken = {
|
|
280
422
|
type: TokenType.START_TAG,
|
|
281
423
|
tagName: '',
|
|
@@ -287,6 +429,7 @@ export class Tokenizer {
|
|
|
287
429
|
};
|
|
288
430
|
}
|
|
289
431
|
_createEndTagToken() {
|
|
432
|
+
this._flushRun('');
|
|
290
433
|
this.currentToken = {
|
|
291
434
|
type: TokenType.END_TAG,
|
|
292
435
|
tagName: '',
|
|
@@ -298,6 +441,7 @@ export class Tokenizer {
|
|
|
298
441
|
};
|
|
299
442
|
}
|
|
300
443
|
_createCommentToken(offset) {
|
|
444
|
+
this._flushRun('');
|
|
301
445
|
this.currentToken = {
|
|
302
446
|
type: TokenType.COMMENT,
|
|
303
447
|
data: '',
|
|
@@ -305,6 +449,7 @@ export class Tokenizer {
|
|
|
305
449
|
};
|
|
306
450
|
}
|
|
307
451
|
_createDoctypeToken(initialName) {
|
|
452
|
+
this._flushRun('');
|
|
308
453
|
this.currentToken = {
|
|
309
454
|
type: TokenType.DOCTYPE,
|
|
310
455
|
name: initialName,
|
|
@@ -314,44 +459,128 @@ export class Tokenizer {
|
|
|
314
459
|
location: this.currentLocation,
|
|
315
460
|
};
|
|
316
461
|
}
|
|
317
|
-
_createCharacterToken(type
|
|
462
|
+
_createCharacterToken(type) {
|
|
463
|
+
if (this.currentLocation) {
|
|
464
|
+
const start = this.currentLocation.startOffset - this.preprocessor.droppedBufferSize;
|
|
465
|
+
// A flushed CR already advanced the line; exclude its ignored LF from the next token's range.
|
|
466
|
+
if (this.preprocessor.html[start] === '\n' && this.preprocessor.html[start - 1] === '\r') {
|
|
467
|
+
this.currentLocation.startOffset++;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
318
470
|
this.currentCharacterToken = {
|
|
319
471
|
type,
|
|
320
|
-
chars,
|
|
472
|
+
chars: '',
|
|
321
473
|
location: this.currentLocation,
|
|
322
474
|
};
|
|
323
475
|
}
|
|
324
476
|
//Tag attributes
|
|
325
477
|
_createAttr(attrNameFirstCh) {
|
|
478
|
+
this._flushRun('');
|
|
326
479
|
this.currentAttr = {
|
|
327
480
|
name: attrNameFirstCh,
|
|
328
481
|
value: '',
|
|
329
482
|
};
|
|
330
|
-
this.
|
|
483
|
+
this.currentAttrLocation = this.getCurrentLocation(0);
|
|
331
484
|
}
|
|
332
485
|
_leaveAttrName() {
|
|
333
486
|
var _a;
|
|
334
487
|
var _b;
|
|
335
488
|
const token = this.currentToken;
|
|
336
|
-
|
|
489
|
+
const { name } = this.currentAttr;
|
|
490
|
+
const names = this.currentAttrNames;
|
|
491
|
+
let duplicate;
|
|
492
|
+
if (names === null) {
|
|
493
|
+
duplicate = getTokenAttr(token, name) !== null;
|
|
494
|
+
// Build the name set only once a new (65th) name is accepted, so a tag with 64 names and many
|
|
495
|
+
// duplicates does not build it.
|
|
496
|
+
if (!duplicate && token.attrs.length === ATTR_NAME_SET_THRESHOLD) {
|
|
497
|
+
this.currentAttrNames = new Set(token.attrs.map((attr) => attr.name)).add(name);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
duplicate = names.has(name);
|
|
502
|
+
if (!duplicate)
|
|
503
|
+
names.add(name);
|
|
504
|
+
}
|
|
505
|
+
if (duplicate) {
|
|
506
|
+
this._err(ERR.duplicateAttribute);
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
337
509
|
token.attrs.push(this.currentAttr);
|
|
338
|
-
if (token.location && this.
|
|
510
|
+
if (token.location && this.currentAttrLocation) {
|
|
339
511
|
const attrLocations = ((_a = (_b = token.location).attrs) !== null && _a !== void 0 ? _a : (_b.attrs = Object.create(null)));
|
|
340
|
-
attrLocations[this.currentAttr.name] = this.
|
|
512
|
+
attrLocations[this.currentAttr.name] = this.currentAttrLocation;
|
|
341
513
|
// Set end location
|
|
342
514
|
this._leaveAttrValue();
|
|
343
515
|
}
|
|
344
516
|
}
|
|
517
|
+
}
|
|
518
|
+
_leaveAttrValue() {
|
|
519
|
+
if (this.currentAttrLocation) {
|
|
520
|
+
this.currentAttrLocation.endLine = this.preprocessor.line;
|
|
521
|
+
this.currentAttrLocation.endCol = this.preprocessor.col;
|
|
522
|
+
this.currentAttrLocation.endOffset = this.preprocessor.offset;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Append the pending run and `text` to the attribute value, comment data or character token being built.
|
|
527
|
+
* The run is a string of its own: V8 copies a slice of fewer than 13 characters but lets a longer one share
|
|
528
|
+
* the input's buffer, which would keep that buffer alive for as long as any attribute value or text cut from
|
|
529
|
+
* it is; a concatenation is flattened into a buffer of its own when it is sliced.
|
|
530
|
+
*/
|
|
531
|
+
_flushRun(text) {
|
|
532
|
+
if (this.runEnd === this.runStart && text.length === 0) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const slice = this.preprocessor.html.slice(this.runStart, this.runEnd);
|
|
536
|
+
const run = (slice.length < 13 ? slice : ` ${slice}`.slice(1)) + text;
|
|
537
|
+
const token = this.currentToken;
|
|
538
|
+
this.runStart = this.runEnd;
|
|
539
|
+
if (token === null) {
|
|
540
|
+
this.currentCharacterToken.chars += run;
|
|
541
|
+
}
|
|
542
|
+
else if (token.type === TokenType.COMMENT) {
|
|
543
|
+
token.data += run;
|
|
544
|
+
}
|
|
345
545
|
else {
|
|
346
|
-
this.
|
|
546
|
+
this.currentAttr.value += run;
|
|
347
547
|
}
|
|
348
548
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
549
|
+
/** Extend the pending run by the current input code point, read as `cp`, or flush it and start a new one. */
|
|
550
|
+
_extendRun(cp) {
|
|
551
|
+
const { preprocessor } = this;
|
|
552
|
+
//NOTE: a CR is read as LF, which is then not in the input.
|
|
553
|
+
if (cp === $.LINE_FEED && preprocessor.html.charCodeAt(preprocessor.pos) !== $.LINE_FEED) {
|
|
554
|
+
this._flushRun('\n');
|
|
555
|
+
this.runStart = preprocessor.pos + 1;
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
const start = preprocessor.pos - Number(cp > 65535);
|
|
559
|
+
if (start !== this.runEnd) {
|
|
560
|
+
this._flushRun('');
|
|
561
|
+
this.runStart = start;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
this.runEnd = preprocessor.pos + 1;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Extend the pending run by `text`, the input that ends `cpOffset` code points after the current one, or flush
|
|
568
|
+
* the run and append `text` where the input differs (a NUL replacement, a decoded reference, a dropped chunk).
|
|
569
|
+
*/
|
|
570
|
+
_extendRunWith(text, cpOffset) {
|
|
571
|
+
const { preprocessor } = this;
|
|
572
|
+
const start = preprocessor.codePointStart + 1 + cpOffset - text.length;
|
|
573
|
+
if (start >= 0 && preprocessor.html.startsWith(text, start)) {
|
|
574
|
+
if (start !== this.runEnd) {
|
|
575
|
+
this._flushRun('');
|
|
576
|
+
this.runStart = start;
|
|
577
|
+
}
|
|
354
578
|
}
|
|
579
|
+
else {
|
|
580
|
+
this._flushRun(text);
|
|
581
|
+
this.runStart = start + text.length;
|
|
582
|
+
}
|
|
583
|
+
this.runEnd = start + text.length;
|
|
355
584
|
}
|
|
356
585
|
//Token emission
|
|
357
586
|
prepareToken(ct) {
|
|
@@ -366,6 +595,7 @@ export class Tokenizer {
|
|
|
366
595
|
}
|
|
367
596
|
emitCurrentTagToken() {
|
|
368
597
|
const ct = this.currentToken;
|
|
598
|
+
this.currentAttrNames = null;
|
|
369
599
|
this.prepareToken(ct);
|
|
370
600
|
ct.tagID = getTagID(ct.tagName);
|
|
371
601
|
if (ct.type === TokenType.START_TAG) {
|
|
@@ -394,6 +624,7 @@ export class Tokenizer {
|
|
|
394
624
|
this.preprocessor.dropParsedChunk();
|
|
395
625
|
}
|
|
396
626
|
_emitCurrentCharacterToken(nextLocation) {
|
|
627
|
+
this._flushRun('');
|
|
397
628
|
if (this.currentCharacterToken) {
|
|
398
629
|
//NOTE: if we have a pending character token, make it's end location equal to the
|
|
399
630
|
//current token's start location.
|
|
@@ -420,13 +651,16 @@ export class Tokenizer {
|
|
|
420
651
|
}
|
|
421
652
|
}
|
|
422
653
|
_emitEOFToken() {
|
|
654
|
+
var _a, _b;
|
|
423
655
|
const location = this.getCurrentLocation(0);
|
|
424
656
|
if (location) {
|
|
425
657
|
location.endLine = location.startLine;
|
|
426
658
|
location.endCol = location.startCol;
|
|
427
659
|
location.endOffset = location.startOffset;
|
|
428
660
|
}
|
|
429
|
-
|
|
661
|
+
// A token that is still open at EOF (eof-in-tag) is dropped. Pending
|
|
662
|
+
// characters end where that token started, rather than at EOF.
|
|
663
|
+
this._emitCurrentCharacterToken((_b = (_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.location) !== null && _b !== void 0 ? _b : location);
|
|
430
664
|
this.handler.onEof({ type: TokenType.EOF, location });
|
|
431
665
|
this.active = false;
|
|
432
666
|
}
|
|
@@ -439,34 +673,48 @@ export class Tokenizer {
|
|
|
439
673
|
//1)TokenType.NULL_CHARACTER - \u0000-character sequences (e.g. '\u0000\u0000\u0000')
|
|
440
674
|
//2)TokenType.WHITESPACE_CHARACTER - any whitespace/new-line character sequences (e.g. '\n \r\t \f')
|
|
441
675
|
//3)TokenType.CHARACTER - any character sequence which don't belong to groups 1 and 2 (e.g. 'abcdef1234@@#$%^')
|
|
442
|
-
|
|
676
|
+
/** Make the current character token one of `type`, emitting the current one if it has another type. */
|
|
677
|
+
_ensureCharacterToken(type) {
|
|
443
678
|
if (this.currentCharacterToken) {
|
|
444
679
|
if (this.currentCharacterToken.type === type) {
|
|
445
|
-
this.currentCharacterToken.chars += ch;
|
|
446
680
|
return;
|
|
447
681
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
this.preprocessor.dropParsedChunk();
|
|
452
|
-
}
|
|
682
|
+
this.currentLocation = this.getCurrentLocation(0);
|
|
683
|
+
this._emitCurrentCharacterToken(this.currentLocation);
|
|
684
|
+
this.preprocessor.dropParsedChunk();
|
|
453
685
|
}
|
|
454
|
-
this._createCharacterToken(type
|
|
686
|
+
this._createCharacterToken(type);
|
|
455
687
|
}
|
|
688
|
+
/** Emit the current input code point, read as `cp`. */
|
|
456
689
|
_emitCodePoint(cp) {
|
|
457
690
|
const type = isWhitespace(cp)
|
|
458
691
|
? TokenType.WHITESPACE_CHARACTER
|
|
459
692
|
: cp === $.NULL
|
|
460
693
|
? TokenType.NULL_CHARACTER
|
|
461
694
|
: TokenType.CHARACTER;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
695
|
+
const token = this.currentCharacterToken;
|
|
696
|
+
if (token === null || token.type !== type) {
|
|
697
|
+
this._ensureCharacterToken(type);
|
|
698
|
+
}
|
|
699
|
+
this._extendRun(cp);
|
|
465
700
|
}
|
|
466
701
|
//NOTE: used when we emit characters explicitly.
|
|
467
702
|
//This is always for non-whitespace and non-null characters, which allows us to avoid additional checks.
|
|
468
|
-
|
|
469
|
-
|
|
703
|
+
// Negative `cpOffset` values locate text emitted before the current input character.
|
|
704
|
+
_emitChars(ch, cpOffset = 0) {
|
|
705
|
+
if (this.options.sourceCodeLocationInfo &&
|
|
706
|
+
cpOffset !== 0 &&
|
|
707
|
+
this.currentCharacterToken &&
|
|
708
|
+
this.currentCharacterToken.type !== TokenType.CHARACTER) {
|
|
709
|
+
this.currentLocation = this.getCurrentLocation(ch.length - 1 - cpOffset);
|
|
710
|
+
this._emitCurrentCharacterToken(this.currentLocation);
|
|
711
|
+
// Keep source characters that precede the lookahead available for a subsequent flush.
|
|
712
|
+
this._createCharacterToken(TokenType.CHARACTER);
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
this._ensureCharacterToken(TokenType.CHARACTER);
|
|
716
|
+
}
|
|
717
|
+
this._extendRunWith(ch, cpOffset);
|
|
470
718
|
}
|
|
471
719
|
// Character reference helpers
|
|
472
720
|
_startCharacterReference() {
|
|
@@ -482,7 +730,7 @@ export class Tokenizer {
|
|
|
482
730
|
}
|
|
483
731
|
_flushCodePointConsumedAsCharacterReference(cp) {
|
|
484
732
|
if (this._isCharacterReferenceInAttribute()) {
|
|
485
|
-
this.
|
|
733
|
+
this._extendRun(cp);
|
|
486
734
|
}
|
|
487
735
|
else {
|
|
488
736
|
this._emitCodePoint(cp);
|
|
@@ -930,13 +1178,13 @@ export class Tokenizer {
|
|
|
930
1178
|
}
|
|
931
1179
|
case $.EOF: {
|
|
932
1180
|
this._err(ERR.eofBeforeTagName);
|
|
933
|
-
this._emitChars('<');
|
|
1181
|
+
this._emitChars('<', -1);
|
|
934
1182
|
this._emitEOFToken();
|
|
935
1183
|
break;
|
|
936
1184
|
}
|
|
937
1185
|
default: {
|
|
938
1186
|
this._err(ERR.invalidFirstCharacterOfTagName);
|
|
939
|
-
this._emitChars('<');
|
|
1187
|
+
this._emitChars('<', -1);
|
|
940
1188
|
this.state = State.DATA;
|
|
941
1189
|
this._stateData(cp);
|
|
942
1190
|
}
|
|
@@ -959,7 +1207,7 @@ export class Tokenizer {
|
|
|
959
1207
|
}
|
|
960
1208
|
case $.EOF: {
|
|
961
1209
|
this._err(ERR.eofBeforeTagName);
|
|
962
|
-
this._emitChars('</');
|
|
1210
|
+
this._emitChars('</', -1);
|
|
963
1211
|
this._emitEOFToken();
|
|
964
1212
|
break;
|
|
965
1213
|
}
|
|
@@ -1014,7 +1262,7 @@ export class Tokenizer {
|
|
|
1014
1262
|
this.state = State.RCDATA_END_TAG_OPEN;
|
|
1015
1263
|
}
|
|
1016
1264
|
else {
|
|
1017
|
-
this._emitChars('<');
|
|
1265
|
+
this._emitChars('<', -1);
|
|
1018
1266
|
this.state = State.RCDATA;
|
|
1019
1267
|
this._stateRcdata(cp);
|
|
1020
1268
|
}
|
|
@@ -1027,7 +1275,7 @@ export class Tokenizer {
|
|
|
1027
1275
|
this._stateRcdataEndTagName(cp);
|
|
1028
1276
|
}
|
|
1029
1277
|
else {
|
|
1030
|
-
this._emitChars('</');
|
|
1278
|
+
this._emitChars('</', -1);
|
|
1031
1279
|
this.state = State.RCDATA;
|
|
1032
1280
|
this._stateRcdata(cp);
|
|
1033
1281
|
}
|
|
@@ -1061,6 +1309,8 @@ export class Tokenizer {
|
|
|
1061
1309
|
return false;
|
|
1062
1310
|
}
|
|
1063
1311
|
default: {
|
|
1312
|
+
//NOTE: the end tag is text after all, so no token is being built.
|
|
1313
|
+
this.currentToken = null;
|
|
1064
1314
|
return !this._ensureHibernation();
|
|
1065
1315
|
}
|
|
1066
1316
|
}
|
|
@@ -1069,7 +1319,7 @@ export class Tokenizer {
|
|
|
1069
1319
|
//------------------------------------------------------------------
|
|
1070
1320
|
_stateRcdataEndTagName(cp) {
|
|
1071
1321
|
if (this.handleSpecialEndTag(cp)) {
|
|
1072
|
-
this._emitChars('</');
|
|
1322
|
+
this._emitChars('</', -1);
|
|
1073
1323
|
this.state = State.RCDATA;
|
|
1074
1324
|
this._stateRcdata(cp);
|
|
1075
1325
|
}
|
|
@@ -1081,7 +1331,7 @@ export class Tokenizer {
|
|
|
1081
1331
|
this.state = State.RAWTEXT_END_TAG_OPEN;
|
|
1082
1332
|
}
|
|
1083
1333
|
else {
|
|
1084
|
-
this._emitChars('<');
|
|
1334
|
+
this._emitChars('<', -1);
|
|
1085
1335
|
this.state = State.RAWTEXT;
|
|
1086
1336
|
this._stateRawtext(cp);
|
|
1087
1337
|
}
|
|
@@ -1094,7 +1344,7 @@ export class Tokenizer {
|
|
|
1094
1344
|
this._stateRawtextEndTagName(cp);
|
|
1095
1345
|
}
|
|
1096
1346
|
else {
|
|
1097
|
-
this._emitChars('</');
|
|
1347
|
+
this._emitChars('</', -1);
|
|
1098
1348
|
this.state = State.RAWTEXT;
|
|
1099
1349
|
this._stateRawtext(cp);
|
|
1100
1350
|
}
|
|
@@ -1103,7 +1353,7 @@ export class Tokenizer {
|
|
|
1103
1353
|
//------------------------------------------------------------------
|
|
1104
1354
|
_stateRawtextEndTagName(cp) {
|
|
1105
1355
|
if (this.handleSpecialEndTag(cp)) {
|
|
1106
|
-
this._emitChars('</');
|
|
1356
|
+
this._emitChars('</', -1);
|
|
1107
1357
|
this.state = State.RAWTEXT;
|
|
1108
1358
|
this._stateRawtext(cp);
|
|
1109
1359
|
}
|
|
@@ -1122,7 +1372,7 @@ export class Tokenizer {
|
|
|
1122
1372
|
break;
|
|
1123
1373
|
}
|
|
1124
1374
|
default: {
|
|
1125
|
-
this._emitChars('<');
|
|
1375
|
+
this._emitChars('<', -1);
|
|
1126
1376
|
this.state = State.SCRIPT_DATA;
|
|
1127
1377
|
this._stateScriptData(cp);
|
|
1128
1378
|
}
|
|
@@ -1136,7 +1386,7 @@ export class Tokenizer {
|
|
|
1136
1386
|
this._stateScriptDataEndTagName(cp);
|
|
1137
1387
|
}
|
|
1138
1388
|
else {
|
|
1139
|
-
this._emitChars('</');
|
|
1389
|
+
this._emitChars('</', -1);
|
|
1140
1390
|
this.state = State.SCRIPT_DATA;
|
|
1141
1391
|
this._stateScriptData(cp);
|
|
1142
1392
|
}
|
|
@@ -1145,7 +1395,7 @@ export class Tokenizer {
|
|
|
1145
1395
|
//------------------------------------------------------------------
|
|
1146
1396
|
_stateScriptDataEndTagName(cp) {
|
|
1147
1397
|
if (this.handleSpecialEndTag(cp)) {
|
|
1148
|
-
this._emitChars('</');
|
|
1398
|
+
this._emitChars('</', -1);
|
|
1149
1399
|
this.state = State.SCRIPT_DATA;
|
|
1150
1400
|
this._stateScriptData(cp);
|
|
1151
1401
|
}
|
|
@@ -1273,12 +1523,12 @@ export class Tokenizer {
|
|
|
1273
1523
|
this.state = State.SCRIPT_DATA_ESCAPED_END_TAG_OPEN;
|
|
1274
1524
|
}
|
|
1275
1525
|
else if (isAsciiLetter(cp)) {
|
|
1276
|
-
this._emitChars('<');
|
|
1526
|
+
this._emitChars('<', -1);
|
|
1277
1527
|
this.state = State.SCRIPT_DATA_DOUBLE_ESCAPE_START;
|
|
1278
1528
|
this._stateScriptDataDoubleEscapeStart(cp);
|
|
1279
1529
|
}
|
|
1280
1530
|
else {
|
|
1281
|
-
this._emitChars('<');
|
|
1531
|
+
this._emitChars('<', -1);
|
|
1282
1532
|
this.state = State.SCRIPT_DATA_ESCAPED;
|
|
1283
1533
|
this._stateScriptDataEscaped(cp);
|
|
1284
1534
|
}
|
|
@@ -1291,7 +1541,7 @@ export class Tokenizer {
|
|
|
1291
1541
|
this._stateScriptDataEscapedEndTagName(cp);
|
|
1292
1542
|
}
|
|
1293
1543
|
else {
|
|
1294
|
-
this._emitChars('</');
|
|
1544
|
+
this._emitChars('</', -1);
|
|
1295
1545
|
this.state = State.SCRIPT_DATA_ESCAPED;
|
|
1296
1546
|
this._stateScriptDataEscaped(cp);
|
|
1297
1547
|
}
|
|
@@ -1300,7 +1550,7 @@ export class Tokenizer {
|
|
|
1300
1550
|
//------------------------------------------------------------------
|
|
1301
1551
|
_stateScriptDataEscapedEndTagName(cp) {
|
|
1302
1552
|
if (this.handleSpecialEndTag(cp)) {
|
|
1303
|
-
this._emitChars('</');
|
|
1553
|
+
this._emitChars('</', -1);
|
|
1304
1554
|
this.state = State.SCRIPT_DATA_ESCAPED;
|
|
1305
1555
|
this._stateScriptDataEscaped(cp);
|
|
1306
1556
|
}
|
|
@@ -1594,7 +1844,7 @@ export class Tokenizer {
|
|
|
1594
1844
|
}
|
|
1595
1845
|
case $.NULL: {
|
|
1596
1846
|
this._err(ERR.unexpectedNullCharacter);
|
|
1597
|
-
this.
|
|
1847
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1598
1848
|
break;
|
|
1599
1849
|
}
|
|
1600
1850
|
case $.EOF: {
|
|
@@ -1603,7 +1853,7 @@ export class Tokenizer {
|
|
|
1603
1853
|
break;
|
|
1604
1854
|
}
|
|
1605
1855
|
default: {
|
|
1606
|
-
this.
|
|
1856
|
+
this._extendRun(cp);
|
|
1607
1857
|
}
|
|
1608
1858
|
}
|
|
1609
1859
|
}
|
|
@@ -1621,7 +1871,7 @@ export class Tokenizer {
|
|
|
1621
1871
|
}
|
|
1622
1872
|
case $.NULL: {
|
|
1623
1873
|
this._err(ERR.unexpectedNullCharacter);
|
|
1624
|
-
this.
|
|
1874
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1625
1875
|
break;
|
|
1626
1876
|
}
|
|
1627
1877
|
case $.EOF: {
|
|
@@ -1630,7 +1880,7 @@ export class Tokenizer {
|
|
|
1630
1880
|
break;
|
|
1631
1881
|
}
|
|
1632
1882
|
default: {
|
|
1633
|
-
this.
|
|
1883
|
+
this._extendRun(cp);
|
|
1634
1884
|
}
|
|
1635
1885
|
}
|
|
1636
1886
|
}
|
|
@@ -1658,7 +1908,7 @@ export class Tokenizer {
|
|
|
1658
1908
|
}
|
|
1659
1909
|
case $.NULL: {
|
|
1660
1910
|
this._err(ERR.unexpectedNullCharacter);
|
|
1661
|
-
this.
|
|
1911
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1662
1912
|
break;
|
|
1663
1913
|
}
|
|
1664
1914
|
case $.QUOTATION_MARK:
|
|
@@ -1667,7 +1917,7 @@ export class Tokenizer {
|
|
|
1667
1917
|
case $.EQUALS_SIGN:
|
|
1668
1918
|
case $.GRAVE_ACCENT: {
|
|
1669
1919
|
this._err(ERR.unexpectedCharacterInUnquotedAttributeValue);
|
|
1670
|
-
this.
|
|
1920
|
+
this._extendRun(cp);
|
|
1671
1921
|
break;
|
|
1672
1922
|
}
|
|
1673
1923
|
case $.EOF: {
|
|
@@ -1676,7 +1926,7 @@ export class Tokenizer {
|
|
|
1676
1926
|
break;
|
|
1677
1927
|
}
|
|
1678
1928
|
default: {
|
|
1679
|
-
this.
|
|
1929
|
+
this._extendRun(cp);
|
|
1680
1930
|
}
|
|
1681
1931
|
}
|
|
1682
1932
|
}
|
|
@@ -1710,6 +1960,7 @@ export class Tokenizer {
|
|
|
1710
1960
|
}
|
|
1711
1961
|
default: {
|
|
1712
1962
|
this._err(ERR.missingWhitespaceBetweenAttributes);
|
|
1963
|
+
this._leaveAttrValue();
|
|
1713
1964
|
this.state = State.BEFORE_ATTRIBUTE_NAME;
|
|
1714
1965
|
this._stateBeforeAttributeName(cp);
|
|
1715
1966
|
}
|
|
@@ -1755,11 +2006,11 @@ export class Tokenizer {
|
|
|
1755
2006
|
}
|
|
1756
2007
|
case $.NULL: {
|
|
1757
2008
|
this._err(ERR.unexpectedNullCharacter);
|
|
1758
|
-
|
|
2009
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1759
2010
|
break;
|
|
1760
2011
|
}
|
|
1761
2012
|
default: {
|
|
1762
|
-
|
|
2013
|
+
this._extendRun(cp);
|
|
1763
2014
|
}
|
|
1764
2015
|
}
|
|
1765
2016
|
}
|
|
@@ -1838,7 +2089,7 @@ export class Tokenizer {
|
|
|
1838
2089
|
break;
|
|
1839
2090
|
}
|
|
1840
2091
|
default: {
|
|
1841
|
-
|
|
2092
|
+
this._extendRunWith('-', -1);
|
|
1842
2093
|
this.state = State.COMMENT;
|
|
1843
2094
|
this._stateComment(cp);
|
|
1844
2095
|
}
|
|
@@ -1854,13 +2105,13 @@ export class Tokenizer {
|
|
|
1854
2105
|
break;
|
|
1855
2106
|
}
|
|
1856
2107
|
case $.LESS_THAN_SIGN: {
|
|
1857
|
-
|
|
2108
|
+
this._extendRunWith('<', 0);
|
|
1858
2109
|
this.state = State.COMMENT_LESS_THAN_SIGN;
|
|
1859
2110
|
break;
|
|
1860
2111
|
}
|
|
1861
2112
|
case $.NULL: {
|
|
1862
2113
|
this._err(ERR.unexpectedNullCharacter);
|
|
1863
|
-
|
|
2114
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1864
2115
|
break;
|
|
1865
2116
|
}
|
|
1866
2117
|
case $.EOF: {
|
|
@@ -1870,22 +2121,21 @@ export class Tokenizer {
|
|
|
1870
2121
|
break;
|
|
1871
2122
|
}
|
|
1872
2123
|
default: {
|
|
1873
|
-
|
|
2124
|
+
this._extendRun(cp);
|
|
1874
2125
|
}
|
|
1875
2126
|
}
|
|
1876
2127
|
}
|
|
1877
2128
|
// Comment less-than sign state
|
|
1878
2129
|
//------------------------------------------------------------------
|
|
1879
2130
|
_stateCommentLessThanSign(cp) {
|
|
1880
|
-
const token = this.currentToken;
|
|
1881
2131
|
switch (cp) {
|
|
1882
2132
|
case $.EXCLAMATION_MARK: {
|
|
1883
|
-
|
|
2133
|
+
this._extendRunWith('!', 0);
|
|
1884
2134
|
this.state = State.COMMENT_LESS_THAN_SIGN_BANG;
|
|
1885
2135
|
break;
|
|
1886
2136
|
}
|
|
1887
2137
|
case $.LESS_THAN_SIGN: {
|
|
1888
|
-
|
|
2138
|
+
this._extendRunWith('<', 0);
|
|
1889
2139
|
break;
|
|
1890
2140
|
}
|
|
1891
2141
|
default: {
|
|
@@ -1941,7 +2191,7 @@ export class Tokenizer {
|
|
|
1941
2191
|
break;
|
|
1942
2192
|
}
|
|
1943
2193
|
default: {
|
|
1944
|
-
|
|
2194
|
+
this._extendRunWith('-', -1);
|
|
1945
2195
|
this.state = State.COMMENT;
|
|
1946
2196
|
this._stateComment(cp);
|
|
1947
2197
|
}
|
|
@@ -1962,7 +2212,7 @@ export class Tokenizer {
|
|
|
1962
2212
|
break;
|
|
1963
2213
|
}
|
|
1964
2214
|
case $.HYPHEN_MINUS: {
|
|
1965
|
-
|
|
2215
|
+
this._extendRunWith('-', -2);
|
|
1966
2216
|
break;
|
|
1967
2217
|
}
|
|
1968
2218
|
case $.EOF: {
|
|
@@ -1972,7 +2222,7 @@ export class Tokenizer {
|
|
|
1972
2222
|
break;
|
|
1973
2223
|
}
|
|
1974
2224
|
default: {
|
|
1975
|
-
|
|
2225
|
+
this._extendRunWith('--', -1);
|
|
1976
2226
|
this.state = State.COMMENT;
|
|
1977
2227
|
this._stateComment(cp);
|
|
1978
2228
|
}
|
|
@@ -1984,7 +2234,7 @@ export class Tokenizer {
|
|
|
1984
2234
|
const token = this.currentToken;
|
|
1985
2235
|
switch (cp) {
|
|
1986
2236
|
case $.HYPHEN_MINUS: {
|
|
1987
|
-
|
|
2237
|
+
this._extendRunWith('--!', -1);
|
|
1988
2238
|
this.state = State.COMMENT_END_DASH;
|
|
1989
2239
|
break;
|
|
1990
2240
|
}
|
|
@@ -2001,7 +2251,7 @@ export class Tokenizer {
|
|
|
2001
2251
|
break;
|
|
2002
2252
|
}
|
|
2003
2253
|
default: {
|
|
2004
|
-
|
|
2254
|
+
this._extendRunWith('--!', -1);
|
|
2005
2255
|
this.state = State.COMMENT;
|
|
2006
2256
|
this._stateComment(cp);
|
|
2007
2257
|
}
|
|
@@ -2638,7 +2888,7 @@ export class Tokenizer {
|
|
|
2638
2888
|
this.state = State.CDATA_SECTION_END;
|
|
2639
2889
|
}
|
|
2640
2890
|
else {
|
|
2641
|
-
this._emitChars(']');
|
|
2891
|
+
this._emitChars(']', -1);
|
|
2642
2892
|
this.state = State.CDATA_SECTION;
|
|
2643
2893
|
this._stateCdataSection(cp);
|
|
2644
2894
|
}
|
|
@@ -2652,11 +2902,11 @@ export class Tokenizer {
|
|
|
2652
2902
|
break;
|
|
2653
2903
|
}
|
|
2654
2904
|
case $.RIGHT_SQUARE_BRACKET: {
|
|
2655
|
-
this._emitChars(']');
|
|
2905
|
+
this._emitChars(']', -2);
|
|
2656
2906
|
break;
|
|
2657
2907
|
}
|
|
2658
2908
|
default: {
|
|
2659
|
-
this._emitChars(']]');
|
|
2909
|
+
this._emitChars(']]', -1);
|
|
2660
2910
|
this.state = State.CDATA_SECTION;
|
|
2661
2911
|
this._stateCdataSection(cp);
|
|
2662
2912
|
}
|