parse5 8.0.0 → 8.1.0
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/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/parser/index.d.ts +12 -7
- package/dist/parser/index.js +50 -21
- package/dist/tokenizer/index.d.ts +34 -7
- package/dist/tokenizer/index.js +263 -65
- package/dist/tokenizer/preprocessor.d.ts +3 -0
- package/dist/tokenizer/preprocessor.js +14 -5
- package/dist/tree-adapters/interface.d.ts +17 -0
- package/package.json +13 -4
package/dist/index.d.ts
CHANGED
|
@@ -16,8 +16,8 @@ export declare namespace DefaultTreeAdapterTypes {
|
|
|
16
16
|
type Node = DefaultTreeAdapter.Node;
|
|
17
17
|
type DefaultTreeAdapterMap = DefaultTreeAdapter.DefaultTreeAdapterMap;
|
|
18
18
|
}
|
|
19
|
-
export type { TreeAdapter, TreeAdapterTypeMap } from './tree-adapters/interface.js';
|
|
20
|
-
export { type ParserOptions, /** @internal */ Parser } from './parser/index.js';
|
|
19
|
+
export type { ElementCreationContext, TreeAdapter, TreeAdapterTypeMap } from './tree-adapters/interface.js';
|
|
20
|
+
export { type ParserOptions, /** @internal */ Parser, InsertionMode } from './parser/index.js';
|
|
21
21
|
export { serialize, serializeOuter, type SerializerOptions } from './serializer/index.js';
|
|
22
22
|
export { ERR as ErrorCodes, type ParserError, type ParserErrorHandler } from './common/error-codes.js';
|
|
23
23
|
/** @internal */
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Parser } from './parser/index.js';
|
|
2
2
|
export { defaultTreeAdapter } from './tree-adapters/default.js';
|
|
3
|
-
export { /** @internal */ Parser } from './parser/index.js';
|
|
3
|
+
export { /** @internal */ Parser, InsertionMode } from './parser/index.js';
|
|
4
4
|
export { serialize, serializeOuter } from './serializer/index.js';
|
|
5
5
|
export { ERR as ErrorCodes } from './common/error-codes.js';
|
|
6
6
|
/** @internal */
|
package/dist/parser/index.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { FormattingElementList } from './formatting-element-list.js';
|
|
|
4
4
|
import { ERR, type ParserErrorHandler } from '../common/error-codes.js';
|
|
5
5
|
import { TAG_ID as $, NS } from '../common/html.js';
|
|
6
6
|
import type { TreeAdapter, TreeAdapterTypeMap } from '../tree-adapters/interface.js';
|
|
7
|
-
import { type Token, type CommentToken, type CharacterToken, type TagToken, type DoctypeToken, type EOFToken, type LocationWithAttributes } from '../common/token.js';
|
|
8
|
-
declare enum InsertionMode {
|
|
7
|
+
import { type Token, type CommentToken, type CharacterToken, type TagToken, type DoctypeToken, type EOFToken, type LocationWithAttributes, type Attribute } from '../common/token.js';
|
|
8
|
+
export declare enum InsertionMode {
|
|
9
9
|
INITIAL = 0,
|
|
10
10
|
BEFORE_HTML = 1,
|
|
11
11
|
BEFORE_HEAD = 2,
|
|
@@ -30,6 +30,10 @@ declare enum InsertionMode {
|
|
|
30
30
|
AFTER_AFTER_BODY = 21,
|
|
31
31
|
AFTER_AFTER_FRAMESET = 22
|
|
32
32
|
}
|
|
33
|
+
interface InsertionLocation<T extends TreeAdapterTypeMap> {
|
|
34
|
+
parent: T['parentNode'];
|
|
35
|
+
beforeElement: T['element'] | null;
|
|
36
|
+
}
|
|
33
37
|
export interface ParserOptions<T extends TreeAdapterTypeMap> {
|
|
34
38
|
/**
|
|
35
39
|
* The [scripting flag](https://html.spec.whatwg.org/multipage/parsing.html#scripting-flag). If set
|
|
@@ -134,7 +138,11 @@ export declare class Parser<T extends TreeAdapterTypeMap> implements TokenHandle
|
|
|
134
138
|
/** @protected */
|
|
135
139
|
_setDocumentType(token: DoctypeToken): void;
|
|
136
140
|
/** @protected */
|
|
137
|
-
_attachElementToTree(element: T['element'], location: LocationWithAttributes | null): void;
|
|
141
|
+
_attachElementToTree(element: T['element'], location: LocationWithAttributes | null, insertionLocation: InsertionLocation<T>): void;
|
|
142
|
+
/** @protected */
|
|
143
|
+
_getInsertionLocation(): InsertionLocation<T>;
|
|
144
|
+
/** @protected */
|
|
145
|
+
_createElement(tagName: string, namespaceURI: NS, attrs: Attribute[], intendedParent: T['parentNode']): T['element'];
|
|
138
146
|
/**
|
|
139
147
|
* For self-closing tags. Add an element to the tree, but skip adding it
|
|
140
148
|
* to the stack.
|
|
@@ -177,10 +185,7 @@ export declare class Parser<T extends TreeAdapterTypeMap> implements TokenHandle
|
|
|
177
185
|
/** @protected */
|
|
178
186
|
_shouldFosterParentOnInsertion(): boolean;
|
|
179
187
|
/** @protected */
|
|
180
|
-
_findFosterParentingLocation():
|
|
181
|
-
parent: T['parentNode'];
|
|
182
|
-
beforeElement: T['element'] | null;
|
|
183
|
-
};
|
|
188
|
+
_findFosterParentingLocation(): InsertionLocation<T>;
|
|
184
189
|
/** @protected */
|
|
185
190
|
_fosterParentElement(element: T['element']): void;
|
|
186
191
|
/** @protected */
|
package/dist/parser/index.js
CHANGED
|
@@ -14,7 +14,7 @@ const HIDDEN_INPUT_TYPE = 'hidden';
|
|
|
14
14
|
const AA_OUTER_LOOP_ITER = 8;
|
|
15
15
|
const AA_INNER_LOOP_ITER = 3;
|
|
16
16
|
//Insertion modes
|
|
17
|
-
var InsertionMode;
|
|
17
|
+
export var InsertionMode;
|
|
18
18
|
(function (InsertionMode) {
|
|
19
19
|
InsertionMode[InsertionMode["INITIAL"] = 0] = "INITIAL";
|
|
20
20
|
InsertionMode[InsertionMode["BEFORE_HTML"] = 1] = "BEFORE_HTML";
|
|
@@ -273,7 +273,7 @@ export class Parser {
|
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
/** @protected */
|
|
276
|
-
_attachElementToTree(element, location) {
|
|
276
|
+
_attachElementToTree(element, location, insertionLocation) {
|
|
277
277
|
if (this.options.sourceCodeLocationInfo) {
|
|
278
278
|
const loc = location && {
|
|
279
279
|
...location,
|
|
@@ -281,13 +281,34 @@ export class Parser {
|
|
|
281
281
|
};
|
|
282
282
|
this.treeAdapter.setNodeSourceCodeLocation(element, loc);
|
|
283
283
|
}
|
|
284
|
-
if (
|
|
285
|
-
this.
|
|
284
|
+
if (insertionLocation.beforeElement) {
|
|
285
|
+
this.treeAdapter.insertBefore(insertionLocation.parent, element, insertionLocation.beforeElement);
|
|
286
286
|
}
|
|
287
287
|
else {
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
this.treeAdapter.appendChild(insertionLocation.parent, element);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/** @protected */
|
|
292
|
+
_getInsertionLocation() {
|
|
293
|
+
var _a;
|
|
294
|
+
if (this._shouldFosterParentOnInsertion()) {
|
|
295
|
+
return this._findFosterParentingLocation();
|
|
290
296
|
}
|
|
297
|
+
return {
|
|
298
|
+
parent: (_a = this.openElements.currentTmplContentOrNode) !== null && _a !== void 0 ? _a : this.document,
|
|
299
|
+
beforeElement: null,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/** @protected */
|
|
303
|
+
_createElement(tagName, namespaceURI, attrs, intendedParent) {
|
|
304
|
+
var _a, _b;
|
|
305
|
+
const element = this.treeAdapter.createElement(tagName, namespaceURI, attrs);
|
|
306
|
+
(_b = (_a = this.treeAdapter).onElementCreated) === null || _b === void 0 ? void 0 : _b.call(_a, element, {
|
|
307
|
+
intendedParent,
|
|
308
|
+
formElement: this.formElement,
|
|
309
|
+
isInTemplate: this.openElements.tmplCount > 0,
|
|
310
|
+
});
|
|
311
|
+
return element;
|
|
291
312
|
}
|
|
292
313
|
/**
|
|
293
314
|
* For self-closing tags. Add an element to the tree, but skip adding it
|
|
@@ -295,37 +316,41 @@ export class Parser {
|
|
|
295
316
|
*/
|
|
296
317
|
/** @protected */
|
|
297
318
|
_appendElement(token, namespaceURI) {
|
|
298
|
-
const
|
|
299
|
-
this.
|
|
319
|
+
const insertionLocation = this._getInsertionLocation();
|
|
320
|
+
const element = this._createElement(token.tagName, namespaceURI, token.attrs, insertionLocation.parent);
|
|
321
|
+
this._attachElementToTree(element, token.location, insertionLocation);
|
|
300
322
|
}
|
|
301
323
|
/** @protected */
|
|
302
324
|
_insertElement(token, namespaceURI) {
|
|
303
|
-
const
|
|
304
|
-
this.
|
|
325
|
+
const insertionLocation = this._getInsertionLocation();
|
|
326
|
+
const element = this._createElement(token.tagName, namespaceURI, token.attrs, insertionLocation.parent);
|
|
327
|
+
this._attachElementToTree(element, token.location, insertionLocation);
|
|
305
328
|
this.openElements.push(element, token.tagID);
|
|
306
329
|
}
|
|
307
330
|
/** @protected */
|
|
308
331
|
_insertFakeElement(tagName, tagID) {
|
|
309
|
-
const
|
|
310
|
-
this.
|
|
332
|
+
const insertionLocation = this._getInsertionLocation();
|
|
333
|
+
const element = this._createElement(tagName, NS.HTML, [], insertionLocation.parent);
|
|
334
|
+
this._attachElementToTree(element, null, insertionLocation);
|
|
311
335
|
this.openElements.push(element, tagID);
|
|
312
336
|
}
|
|
313
337
|
/** @protected */
|
|
314
338
|
_insertTemplate(token) {
|
|
315
|
-
const
|
|
339
|
+
const insertionLocation = this._getInsertionLocation();
|
|
340
|
+
const tmpl = this._createElement(token.tagName, NS.HTML, token.attrs, insertionLocation.parent);
|
|
316
341
|
const content = this.treeAdapter.createDocumentFragment();
|
|
317
342
|
this.treeAdapter.setTemplateContent(tmpl, content);
|
|
318
|
-
this._attachElementToTree(tmpl, token.location);
|
|
343
|
+
this._attachElementToTree(tmpl, token.location, insertionLocation);
|
|
319
344
|
this.openElements.push(tmpl, token.tagID);
|
|
320
345
|
if (this.options.sourceCodeLocationInfo)
|
|
321
346
|
this.treeAdapter.setNodeSourceCodeLocation(content, null);
|
|
322
347
|
}
|
|
323
348
|
/** @protected */
|
|
324
349
|
_insertFakeRootElement() {
|
|
325
|
-
const element = this.
|
|
350
|
+
const element = this._createElement(TN.HTML, NS.HTML, [], this.document);
|
|
326
351
|
if (this.options.sourceCodeLocationInfo)
|
|
327
352
|
this.treeAdapter.setNodeSourceCodeLocation(element, null);
|
|
328
|
-
this.treeAdapter.appendChild(this.
|
|
353
|
+
this.treeAdapter.appendChild(this.document, element);
|
|
329
354
|
this.openElements.push(element, $.HTML);
|
|
330
355
|
}
|
|
331
356
|
/** @protected */
|
|
@@ -1217,6 +1242,7 @@ function aaObtainFurthestBlock(p, formattingElementEntry) {
|
|
|
1217
1242
|
function aaInnerLoop(p, furthestBlock, formattingElement) {
|
|
1218
1243
|
let lastElement = furthestBlock;
|
|
1219
1244
|
let nextElement = p.openElements.getCommonAncestor(furthestBlock);
|
|
1245
|
+
const intendedParent = p.openElements.getCommonAncestor(formattingElement);
|
|
1220
1246
|
for (let i = 0, element = nextElement; element !== formattingElement; i++, element = nextElement) {
|
|
1221
1247
|
//NOTE: store the next element for the next loop iteration (it may be deleted from the stack by step 9.5)
|
|
1222
1248
|
nextElement = p.openElements.getCommonAncestor(element);
|
|
@@ -1230,7 +1256,7 @@ function aaInnerLoop(p, furthestBlock, formattingElement) {
|
|
|
1230
1256
|
p.openElements.remove(element);
|
|
1231
1257
|
}
|
|
1232
1258
|
else {
|
|
1233
|
-
element = aaRecreateElementFromEntry(p, elementEntry);
|
|
1259
|
+
element = aaRecreateElementFromEntry(p, elementEntry, intendedParent);
|
|
1234
1260
|
if (lastElement === furthestBlock) {
|
|
1235
1261
|
p.activeFormattingElements.bookmark = elementEntry;
|
|
1236
1262
|
}
|
|
@@ -1242,9 +1268,9 @@ function aaInnerLoop(p, furthestBlock, formattingElement) {
|
|
|
1242
1268
|
return lastElement;
|
|
1243
1269
|
}
|
|
1244
1270
|
//Step 13.7 of the algorithm
|
|
1245
|
-
function aaRecreateElementFromEntry(p, elementEntry) {
|
|
1271
|
+
function aaRecreateElementFromEntry(p, elementEntry, intendedParent) {
|
|
1246
1272
|
const ns = p.treeAdapter.getNamespaceURI(elementEntry.element);
|
|
1247
|
-
const newElement = p.
|
|
1273
|
+
const newElement = p._createElement(elementEntry.token.tagName, ns, elementEntry.token.attrs, intendedParent);
|
|
1248
1274
|
p.openElements.replace(elementEntry.element, newElement);
|
|
1249
1275
|
elementEntry.element = newElement;
|
|
1250
1276
|
return newElement;
|
|
@@ -1268,7 +1294,7 @@ function aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement) {
|
|
|
1268
1294
|
function aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry) {
|
|
1269
1295
|
const ns = p.treeAdapter.getNamespaceURI(formattingElementEntry.element);
|
|
1270
1296
|
const { token } = formattingElementEntry;
|
|
1271
|
-
const newElement = p.
|
|
1297
|
+
const newElement = p._createElement(token.tagName, ns, token.attrs, furthestBlock);
|
|
1272
1298
|
p._adoptNodes(furthestBlock, newElement);
|
|
1273
1299
|
p.treeAdapter.appendChild(furthestBlock, newElement);
|
|
1274
1300
|
p.activeFormattingElements.insertElementAfterBookmark(newElement, token);
|
|
@@ -2227,7 +2253,10 @@ function genericEndTagInBody(p, token) {
|
|
|
2227
2253
|
const element = p.openElements.items[i];
|
|
2228
2254
|
const elementId = p.openElements.tagIDs[i];
|
|
2229
2255
|
// Compare the tag name here, as the tag might not be a known tag with an ID.
|
|
2230
|
-
|
|
2256
|
+
// Only HTML elements match; a foreign element falls through to the special-element check.
|
|
2257
|
+
if (tid === elementId &&
|
|
2258
|
+
p.treeAdapter.getNamespaceURI(element) === NS.HTML &&
|
|
2259
|
+
(tid !== $.UNKNOWN || p.treeAdapter.getTagName(element) === tn)) {
|
|
2231
2260
|
p.openElements.generateImpliedEndTagsWithExclusion(tid);
|
|
2232
2261
|
if (p.openElements.stackTop >= i)
|
|
2233
2262
|
p.openElements.shortenToLength(i);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Preprocessor } from './preprocessor.js';
|
|
2
2
|
import { type Token, type CharacterToken, type DoctypeToken, type TagToken, type EOFToken, type CommentToken, type Attribute, type Location } from '../common/token.js';
|
|
3
|
-
import {
|
|
3
|
+
import { HtmlEntityDecoder } from 'entities/decode';
|
|
4
4
|
import { ERR, type ParserErrorHandler } from '../common/error-codes.js';
|
|
5
5
|
declare const enum State {
|
|
6
6
|
DATA = 0,
|
|
@@ -118,9 +118,9 @@ export declare class Tokenizer {
|
|
|
118
118
|
state: State;
|
|
119
119
|
protected returnState: State;
|
|
120
120
|
/**
|
|
121
|
-
* We use `entities`' `
|
|
121
|
+
* We use `entities`' `HtmlEntityDecoder` to parse character references.
|
|
122
122
|
*
|
|
123
|
-
* All of the following states are handled by the `
|
|
123
|
+
* All of the following states are handled by the `HtmlEntityDecoder`:
|
|
124
124
|
*
|
|
125
125
|
* - Named character reference state
|
|
126
126
|
* - Numeric character reference state
|
|
@@ -129,17 +129,28 @@ export declare class Tokenizer {
|
|
|
129
129
|
* - Decimal character reference state
|
|
130
130
|
* - Numeric character reference end state
|
|
131
131
|
*/
|
|
132
|
-
protected entityDecoder:
|
|
132
|
+
protected entityDecoder: HtmlEntityDecoder;
|
|
133
133
|
protected entityStartPos: number;
|
|
134
134
|
protected consumedAfterSnapshot: number;
|
|
135
135
|
protected currentLocation: Location | null;
|
|
136
136
|
protected currentCharacterToken: CharacterToken | null;
|
|
137
137
|
protected currentToken: Token | null;
|
|
138
138
|
protected currentAttr: Attribute;
|
|
139
|
+
private currentAttrLocation;
|
|
140
|
+
/**
|
|
141
|
+
* `[start, end)` of `preprocessor.html` still to be appended to the attribute value, comment data or character
|
|
142
|
+
* token being built (`_flushRun`): input that is used as is is appended as one slice, not a code point at a time.
|
|
143
|
+
*/
|
|
144
|
+
protected runStart: number;
|
|
145
|
+
protected runEnd: number;
|
|
139
146
|
constructor(options: TokenizerOptions, handler: TokenHandler);
|
|
140
147
|
protected _err(code: ERR, cpOffset?: number): void;
|
|
141
148
|
protected getCurrentLocation(offset: number): Location | null;
|
|
142
149
|
protected _runParsingLoop(): void;
|
|
150
|
+
/** Emit buffered character tokens without completing the current token or emitting EOF. */
|
|
151
|
+
flushCharacters(): void;
|
|
152
|
+
private getCharacterTokenEndLocation;
|
|
153
|
+
private getCharacterTokenEndOffset;
|
|
143
154
|
pause(): void;
|
|
144
155
|
resume(writeCallback?: () => void): void;
|
|
145
156
|
write(chunk: string, isLastChunk: boolean, writeCallback?: () => void): void;
|
|
@@ -152,19 +163,35 @@ export declare class Tokenizer {
|
|
|
152
163
|
protected _createEndTagToken(): void;
|
|
153
164
|
protected _createCommentToken(offset: number): void;
|
|
154
165
|
protected _createDoctypeToken(initialName: string | null): void;
|
|
155
|
-
protected _createCharacterToken(type: CharacterToken['type']
|
|
166
|
+
protected _createCharacterToken(type: CharacterToken['type']): void;
|
|
156
167
|
protected _createAttr(attrNameFirstCh: string): void;
|
|
157
168
|
protected _leaveAttrName(): void;
|
|
158
169
|
protected _leaveAttrValue(): void;
|
|
170
|
+
/**
|
|
171
|
+
* Append the pending run and `text` to the attribute value, comment data or character token being built.
|
|
172
|
+
* The run is a string of its own: V8 copies a slice of fewer than 13 characters but lets a longer one share
|
|
173
|
+
* the input's buffer, which would keep that buffer alive for as long as any attribute value or text cut from
|
|
174
|
+
* it is; a concatenation is flattened into a buffer of its own when it is sliced.
|
|
175
|
+
*/
|
|
176
|
+
protected _flushRun(text: string): void;
|
|
177
|
+
/** Extend the pending run by the current input code point, read as `cp`, or flush it and start a new one. */
|
|
178
|
+
protected _extendRun(cp: number): void;
|
|
179
|
+
/**
|
|
180
|
+
* Extend the pending run by `text`, the input that ends `cpOffset` code points after the current one, or flush
|
|
181
|
+
* the run and append `text` where the input differs (a NUL replacement, a decoded reference, a dropped chunk).
|
|
182
|
+
*/
|
|
183
|
+
protected _extendRunWith(text: string, cpOffset: number): void;
|
|
159
184
|
protected prepareToken(ct: Token): void;
|
|
160
185
|
protected emitCurrentTagToken(): void;
|
|
161
186
|
protected emitCurrentComment(ct: CommentToken): void;
|
|
162
187
|
protected emitCurrentDoctype(ct: DoctypeToken): void;
|
|
163
188
|
protected _emitCurrentCharacterToken(nextLocation: Location | null): void;
|
|
164
189
|
protected _emitEOFToken(): void;
|
|
165
|
-
|
|
190
|
+
/** Make the current character token one of `type`, emitting the current one if it has another type. */
|
|
191
|
+
protected _ensureCharacterToken(type: CharacterToken['type']): void;
|
|
192
|
+
/** Emit the current input code point, read as `cp`. */
|
|
166
193
|
protected _emitCodePoint(cp: number): void;
|
|
167
|
-
protected _emitChars(ch: string): void;
|
|
194
|
+
protected _emitChars(ch: string, cpOffset?: number): void;
|
|
168
195
|
protected _startCharacterReference(): void;
|
|
169
196
|
protected _isCharacterReferenceInAttribute(): boolean;
|
|
170
197
|
protected _flushCodePointConsumedAsCharacterReference(cp: number): void;
|
package/dist/tokenizer/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
7
|
//States
|
|
@@ -113,7 +113,7 @@ function toAsciiLower(cp) {
|
|
|
113
113
|
return cp + 32;
|
|
114
114
|
}
|
|
115
115
|
function isWhitespace(cp) {
|
|
116
|
-
return cp === $.SPACE || cp === $.LINE_FEED || cp === $.TABULATION || cp === $.FORM_FEED;
|
|
116
|
+
return (cp === $.SPACE || cp === $.LINE_FEED || cp === $.TABULATION || cp === $.FORM_FEED || cp === $.CARRIAGE_RETURN);
|
|
117
117
|
}
|
|
118
118
|
function isScriptDataDoubleEscapeSequenceEnd(cp) {
|
|
119
119
|
return isWhitespace(cp) || cp === $.SOLIDUS || cp === $.GREATER_THAN_SIGN;
|
|
@@ -160,13 +160,27 @@ export class Tokenizer {
|
|
|
160
160
|
this.currentCharacterToken = null;
|
|
161
161
|
this.currentToken = null;
|
|
162
162
|
this.currentAttr = { name: '', value: '' };
|
|
163
|
+
this.currentAttrLocation = null;
|
|
164
|
+
/**
|
|
165
|
+
* `[start, end)` of `preprocessor.html` still to be appended to the attribute value, comment data or character
|
|
166
|
+
* token being built (`_flushRun`): input that is used as is is appended as one slice, not a code point at a time.
|
|
167
|
+
*/
|
|
168
|
+
this.runStart = 0;
|
|
169
|
+
this.runEnd = 0;
|
|
163
170
|
this.preprocessor = new Preprocessor(handler);
|
|
164
171
|
this.currentLocation = this.getCurrentLocation(-1);
|
|
165
|
-
this.entityDecoder = new
|
|
172
|
+
this.entityDecoder = new HtmlEntityDecoder((cp, consumed) => {
|
|
166
173
|
// Note: Set `pos` _before_ flushing, as flushing might drop
|
|
167
174
|
// the current chunk and invalidate `entityStartPos`.
|
|
168
175
|
this.preprocessor.pos = this.entityStartPos + consumed - 1;
|
|
169
|
-
this.
|
|
176
|
+
if (!this._isCharacterReferenceInAttribute()) {
|
|
177
|
+
this._ensureCharacterToken(isWhitespace(cp)
|
|
178
|
+
? TokenType.WHITESPACE_CHARACTER
|
|
179
|
+
: cp === $.NULL
|
|
180
|
+
? TokenType.NULL_CHARACTER
|
|
181
|
+
: TokenType.CHARACTER);
|
|
182
|
+
}
|
|
183
|
+
this._extendRunWith(String.fromCodePoint(cp), 0);
|
|
170
184
|
}, handler.onParseError
|
|
171
185
|
? {
|
|
172
186
|
missingSemicolonAfterCharacterReference: () => {
|
|
@@ -216,6 +230,99 @@ export class Tokenizer {
|
|
|
216
230
|
this.inLoop = false;
|
|
217
231
|
}
|
|
218
232
|
//API
|
|
233
|
+
/** Emit buffered character tokens without completing the current token or emitting EOF. */
|
|
234
|
+
flushCharacters() {
|
|
235
|
+
var _a;
|
|
236
|
+
if (this.inLoop) {
|
|
237
|
+
throw new Error('Cannot flush characters from a tokenizer callback');
|
|
238
|
+
}
|
|
239
|
+
const location = (_a = this.currentCharacterToken) === null || _a === void 0 ? void 0 : _a.location;
|
|
240
|
+
let nextLocation = null;
|
|
241
|
+
if (location) {
|
|
242
|
+
nextLocation = this.getCharacterTokenEndLocation(location);
|
|
243
|
+
this.currentLocation = nextLocation;
|
|
244
|
+
}
|
|
245
|
+
this.inLoop = true;
|
|
246
|
+
try {
|
|
247
|
+
this._emitCurrentCharacterToken(nextLocation);
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
this.inLoop = false;
|
|
251
|
+
}
|
|
252
|
+
this._runParsingLoop();
|
|
253
|
+
}
|
|
254
|
+
getCharacterTokenEndLocation(location) {
|
|
255
|
+
const endOffset = this.getCharacterTokenEndOffset();
|
|
256
|
+
const html = this.preprocessor.html.slice(location.startOffset - this.preprocessor.droppedBufferSize, endOffset - this.preprocessor.droppedBufferSize);
|
|
257
|
+
let line = location.startLine;
|
|
258
|
+
let lastLineBreak = -1;
|
|
259
|
+
for (let i = html.indexOf('\n'); i !== -1; i = html.indexOf('\n', i + 1)) {
|
|
260
|
+
line++;
|
|
261
|
+
lastLineBreak = i;
|
|
262
|
+
}
|
|
263
|
+
for (let i = html.indexOf('\r'); i !== -1; i = html.indexOf('\r', i + 1)) {
|
|
264
|
+
// Count CRLF as one line break by counting its LF only.
|
|
265
|
+
if (html.charCodeAt(i + 1) !== $.LINE_FEED) {
|
|
266
|
+
line++;
|
|
267
|
+
lastLineBreak = Math.max(lastLineBreak, i);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
startLine: line,
|
|
272
|
+
startCol: lastLineBreak === -1 ? location.startCol + html.length : html.length - lastLineBreak,
|
|
273
|
+
startOffset: endOffset,
|
|
274
|
+
endLine: -1,
|
|
275
|
+
endCol: -1,
|
|
276
|
+
endOffset: -1,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
// Return the input boundary before any unfinished token or buffered delimiter.
|
|
280
|
+
getCharacterTokenEndOffset() {
|
|
281
|
+
var _a;
|
|
282
|
+
const end = this.preprocessor.droppedBufferSize + this.preprocessor.pos + 1;
|
|
283
|
+
const tokenLocation = (_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.location;
|
|
284
|
+
switch (this.state) {
|
|
285
|
+
case State.TAG_OPEN:
|
|
286
|
+
case State.RCDATA_LESS_THAN_SIGN:
|
|
287
|
+
case State.RAWTEXT_LESS_THAN_SIGN:
|
|
288
|
+
case State.SCRIPT_DATA_LESS_THAN_SIGN:
|
|
289
|
+
case State.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN:
|
|
290
|
+
case State.CDATA_SECTION_BRACKET: {
|
|
291
|
+
return end - 1;
|
|
292
|
+
}
|
|
293
|
+
case State.END_TAG_OPEN:
|
|
294
|
+
case State.MARKUP_DECLARATION_OPEN:
|
|
295
|
+
case State.RCDATA_END_TAG_OPEN:
|
|
296
|
+
case State.RCDATA_END_TAG_NAME:
|
|
297
|
+
case State.RAWTEXT_END_TAG_OPEN:
|
|
298
|
+
case State.RAWTEXT_END_TAG_NAME:
|
|
299
|
+
case State.SCRIPT_DATA_END_TAG_OPEN:
|
|
300
|
+
case State.SCRIPT_DATA_END_TAG_NAME:
|
|
301
|
+
case State.SCRIPT_DATA_ESCAPED_END_TAG_OPEN:
|
|
302
|
+
case State.SCRIPT_DATA_ESCAPED_END_TAG_NAME:
|
|
303
|
+
case State.CDATA_SECTION_END: {
|
|
304
|
+
return end - 2;
|
|
305
|
+
}
|
|
306
|
+
case State.CHARACTER_REFERENCE: {
|
|
307
|
+
return this._isCharacterReferenceInAttribute() && tokenLocation
|
|
308
|
+
? tokenLocation.startOffset
|
|
309
|
+
: this.preprocessor.droppedBufferSize + this.entityStartPos;
|
|
310
|
+
}
|
|
311
|
+
case State.DOCTYPE:
|
|
312
|
+
case State.BEFORE_DOCTYPE_NAME: {
|
|
313
|
+
return this.currentLocation.startOffset;
|
|
314
|
+
}
|
|
315
|
+
default: {
|
|
316
|
+
if (tokenLocation &&
|
|
317
|
+
(this.state === State.TAG_NAME ||
|
|
318
|
+
(this.state >= State.BEFORE_ATTRIBUTE_NAME && this.state <= State.BOGUS_COMMENT) ||
|
|
319
|
+
(this.state >= State.COMMENT_START && this.state <= State.BOGUS_DOCTYPE))) {
|
|
320
|
+
return tokenLocation.startOffset;
|
|
321
|
+
}
|
|
322
|
+
return end;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
219
326
|
pause() {
|
|
220
327
|
this.paused = true;
|
|
221
328
|
}
|
|
@@ -276,6 +383,7 @@ export class Tokenizer {
|
|
|
276
383
|
}
|
|
277
384
|
//Token creation
|
|
278
385
|
_createStartTagToken() {
|
|
386
|
+
this._flushRun('');
|
|
279
387
|
this.currentToken = {
|
|
280
388
|
type: TokenType.START_TAG,
|
|
281
389
|
tagName: '',
|
|
@@ -287,6 +395,7 @@ export class Tokenizer {
|
|
|
287
395
|
};
|
|
288
396
|
}
|
|
289
397
|
_createEndTagToken() {
|
|
398
|
+
this._flushRun('');
|
|
290
399
|
this.currentToken = {
|
|
291
400
|
type: TokenType.END_TAG,
|
|
292
401
|
tagName: '',
|
|
@@ -298,6 +407,7 @@ export class Tokenizer {
|
|
|
298
407
|
};
|
|
299
408
|
}
|
|
300
409
|
_createCommentToken(offset) {
|
|
410
|
+
this._flushRun('');
|
|
301
411
|
this.currentToken = {
|
|
302
412
|
type: TokenType.COMMENT,
|
|
303
413
|
data: '',
|
|
@@ -305,6 +415,7 @@ export class Tokenizer {
|
|
|
305
415
|
};
|
|
306
416
|
}
|
|
307
417
|
_createDoctypeToken(initialName) {
|
|
418
|
+
this._flushRun('');
|
|
308
419
|
this.currentToken = {
|
|
309
420
|
type: TokenType.DOCTYPE,
|
|
310
421
|
name: initialName,
|
|
@@ -314,20 +425,28 @@ export class Tokenizer {
|
|
|
314
425
|
location: this.currentLocation,
|
|
315
426
|
};
|
|
316
427
|
}
|
|
317
|
-
_createCharacterToken(type
|
|
428
|
+
_createCharacterToken(type) {
|
|
429
|
+
if (this.currentLocation) {
|
|
430
|
+
const start = this.currentLocation.startOffset - this.preprocessor.droppedBufferSize;
|
|
431
|
+
// A flushed CR already advanced the line; exclude its ignored LF from the next token's range.
|
|
432
|
+
if (this.preprocessor.html[start] === '\n' && this.preprocessor.html[start - 1] === '\r') {
|
|
433
|
+
this.currentLocation.startOffset++;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
318
436
|
this.currentCharacterToken = {
|
|
319
437
|
type,
|
|
320
|
-
chars,
|
|
438
|
+
chars: '',
|
|
321
439
|
location: this.currentLocation,
|
|
322
440
|
};
|
|
323
441
|
}
|
|
324
442
|
//Tag attributes
|
|
325
443
|
_createAttr(attrNameFirstCh) {
|
|
444
|
+
this._flushRun('');
|
|
326
445
|
this.currentAttr = {
|
|
327
446
|
name: attrNameFirstCh,
|
|
328
447
|
value: '',
|
|
329
448
|
};
|
|
330
|
-
this.
|
|
449
|
+
this.currentAttrLocation = this.getCurrentLocation(0);
|
|
331
450
|
}
|
|
332
451
|
_leaveAttrName() {
|
|
333
452
|
var _a;
|
|
@@ -335,9 +454,9 @@ export class Tokenizer {
|
|
|
335
454
|
const token = this.currentToken;
|
|
336
455
|
if (getTokenAttr(token, this.currentAttr.name) === null) {
|
|
337
456
|
token.attrs.push(this.currentAttr);
|
|
338
|
-
if (token.location && this.
|
|
457
|
+
if (token.location && this.currentAttrLocation) {
|
|
339
458
|
const attrLocations = ((_a = (_b = token.location).attrs) !== null && _a !== void 0 ? _a : (_b.attrs = Object.create(null)));
|
|
340
|
-
attrLocations[this.currentAttr.name] = this.
|
|
459
|
+
attrLocations[this.currentAttr.name] = this.currentAttrLocation;
|
|
341
460
|
// Set end location
|
|
342
461
|
this._leaveAttrValue();
|
|
343
462
|
}
|
|
@@ -347,11 +466,71 @@ export class Tokenizer {
|
|
|
347
466
|
}
|
|
348
467
|
}
|
|
349
468
|
_leaveAttrValue() {
|
|
350
|
-
if (this.
|
|
351
|
-
this.
|
|
352
|
-
this.
|
|
353
|
-
this.
|
|
469
|
+
if (this.currentAttrLocation) {
|
|
470
|
+
this.currentAttrLocation.endLine = this.preprocessor.line;
|
|
471
|
+
this.currentAttrLocation.endCol = this.preprocessor.col;
|
|
472
|
+
this.currentAttrLocation.endOffset = this.preprocessor.offset;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Append the pending run and `text` to the attribute value, comment data or character token being built.
|
|
477
|
+
* The run is a string of its own: V8 copies a slice of fewer than 13 characters but lets a longer one share
|
|
478
|
+
* the input's buffer, which would keep that buffer alive for as long as any attribute value or text cut from
|
|
479
|
+
* it is; a concatenation is flattened into a buffer of its own when it is sliced.
|
|
480
|
+
*/
|
|
481
|
+
_flushRun(text) {
|
|
482
|
+
if (this.runEnd === this.runStart && text.length === 0) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const slice = this.preprocessor.html.slice(this.runStart, this.runEnd);
|
|
486
|
+
const run = (slice.length < 13 ? slice : ` ${slice}`.slice(1)) + text;
|
|
487
|
+
const token = this.currentToken;
|
|
488
|
+
this.runStart = this.runEnd;
|
|
489
|
+
if (token === null) {
|
|
490
|
+
this.currentCharacterToken.chars += run;
|
|
491
|
+
}
|
|
492
|
+
else if (token.type === TokenType.COMMENT) {
|
|
493
|
+
token.data += run;
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
this.currentAttr.value += run;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/** Extend the pending run by the current input code point, read as `cp`, or flush it and start a new one. */
|
|
500
|
+
_extendRun(cp) {
|
|
501
|
+
const { preprocessor } = this;
|
|
502
|
+
//NOTE: a CR is read as LF, which is then not in the input.
|
|
503
|
+
if (cp === $.LINE_FEED && preprocessor.html.charCodeAt(preprocessor.pos) !== $.LINE_FEED) {
|
|
504
|
+
this._flushRun('\n');
|
|
505
|
+
this.runStart = preprocessor.pos + 1;
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
const start = preprocessor.pos - Number(cp > 65535);
|
|
509
|
+
if (start !== this.runEnd) {
|
|
510
|
+
this._flushRun('');
|
|
511
|
+
this.runStart = start;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
this.runEnd = preprocessor.pos + 1;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Extend the pending run by `text`, the input that ends `cpOffset` code points after the current one, or flush
|
|
518
|
+
* the run and append `text` where the input differs (a NUL replacement, a decoded reference, a dropped chunk).
|
|
519
|
+
*/
|
|
520
|
+
_extendRunWith(text, cpOffset) {
|
|
521
|
+
const { preprocessor } = this;
|
|
522
|
+
const start = preprocessor.codePointStart + 1 + cpOffset - text.length;
|
|
523
|
+
if (start >= 0 && preprocessor.html.startsWith(text, start)) {
|
|
524
|
+
if (start !== this.runEnd) {
|
|
525
|
+
this._flushRun('');
|
|
526
|
+
this.runStart = start;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
this._flushRun(text);
|
|
531
|
+
this.runStart = start + text.length;
|
|
354
532
|
}
|
|
533
|
+
this.runEnd = start + text.length;
|
|
355
534
|
}
|
|
356
535
|
//Token emission
|
|
357
536
|
prepareToken(ct) {
|
|
@@ -394,6 +573,7 @@ export class Tokenizer {
|
|
|
394
573
|
this.preprocessor.dropParsedChunk();
|
|
395
574
|
}
|
|
396
575
|
_emitCurrentCharacterToken(nextLocation) {
|
|
576
|
+
this._flushRun('');
|
|
397
577
|
if (this.currentCharacterToken) {
|
|
398
578
|
//NOTE: if we have a pending character token, make it's end location equal to the
|
|
399
579
|
//current token's start location.
|
|
@@ -439,32 +619,48 @@ export class Tokenizer {
|
|
|
439
619
|
//1)TokenType.NULL_CHARACTER - \u0000-character sequences (e.g. '\u0000\u0000\u0000')
|
|
440
620
|
//2)TokenType.WHITESPACE_CHARACTER - any whitespace/new-line character sequences (e.g. '\n \r\t \f')
|
|
441
621
|
//3)TokenType.CHARACTER - any character sequence which don't belong to groups 1 and 2 (e.g. 'abcdef1234@@#$%^')
|
|
442
|
-
|
|
622
|
+
/** Make the current character token one of `type`, emitting the current one if it has another type. */
|
|
623
|
+
_ensureCharacterToken(type) {
|
|
443
624
|
if (this.currentCharacterToken) {
|
|
444
625
|
if (this.currentCharacterToken.type === type) {
|
|
445
|
-
this.currentCharacterToken.chars += ch;
|
|
446
626
|
return;
|
|
447
627
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
this.preprocessor.dropParsedChunk();
|
|
452
|
-
}
|
|
628
|
+
this.currentLocation = this.getCurrentLocation(0);
|
|
629
|
+
this._emitCurrentCharacterToken(this.currentLocation);
|
|
630
|
+
this.preprocessor.dropParsedChunk();
|
|
453
631
|
}
|
|
454
|
-
this._createCharacterToken(type
|
|
632
|
+
this._createCharacterToken(type);
|
|
455
633
|
}
|
|
634
|
+
/** Emit the current input code point, read as `cp`. */
|
|
456
635
|
_emitCodePoint(cp) {
|
|
457
636
|
const type = isWhitespace(cp)
|
|
458
637
|
? TokenType.WHITESPACE_CHARACTER
|
|
459
638
|
: cp === $.NULL
|
|
460
639
|
? TokenType.NULL_CHARACTER
|
|
461
640
|
: TokenType.CHARACTER;
|
|
462
|
-
this.
|
|
641
|
+
const token = this.currentCharacterToken;
|
|
642
|
+
if (token === null || token.type !== type) {
|
|
643
|
+
this._ensureCharacterToken(type);
|
|
644
|
+
}
|
|
645
|
+
this._extendRun(cp);
|
|
463
646
|
}
|
|
464
647
|
//NOTE: used when we emit characters explicitly.
|
|
465
648
|
//This is always for non-whitespace and non-null characters, which allows us to avoid additional checks.
|
|
466
|
-
|
|
467
|
-
|
|
649
|
+
// Negative `cpOffset` values locate text emitted before the current input character.
|
|
650
|
+
_emitChars(ch, cpOffset = 0) {
|
|
651
|
+
if (this.options.sourceCodeLocationInfo &&
|
|
652
|
+
cpOffset !== 0 &&
|
|
653
|
+
this.currentCharacterToken &&
|
|
654
|
+
this.currentCharacterToken.type !== TokenType.CHARACTER) {
|
|
655
|
+
this.currentLocation = this.getCurrentLocation(ch.length - 1 - cpOffset);
|
|
656
|
+
this._emitCurrentCharacterToken(this.currentLocation);
|
|
657
|
+
// Keep source characters that precede the lookahead available for a subsequent flush.
|
|
658
|
+
this._createCharacterToken(TokenType.CHARACTER);
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
this._ensureCharacterToken(TokenType.CHARACTER);
|
|
662
|
+
}
|
|
663
|
+
this._extendRunWith(ch, cpOffset);
|
|
468
664
|
}
|
|
469
665
|
// Character reference helpers
|
|
470
666
|
_startCharacterReference() {
|
|
@@ -480,7 +676,7 @@ export class Tokenizer {
|
|
|
480
676
|
}
|
|
481
677
|
_flushCodePointConsumedAsCharacterReference(cp) {
|
|
482
678
|
if (this._isCharacterReferenceInAttribute()) {
|
|
483
|
-
this.
|
|
679
|
+
this._extendRun(cp);
|
|
484
680
|
}
|
|
485
681
|
else {
|
|
486
682
|
this._emitCodePoint(cp);
|
|
@@ -928,13 +1124,13 @@ export class Tokenizer {
|
|
|
928
1124
|
}
|
|
929
1125
|
case $.EOF: {
|
|
930
1126
|
this._err(ERR.eofBeforeTagName);
|
|
931
|
-
this._emitChars('<');
|
|
1127
|
+
this._emitChars('<', -1);
|
|
932
1128
|
this._emitEOFToken();
|
|
933
1129
|
break;
|
|
934
1130
|
}
|
|
935
1131
|
default: {
|
|
936
1132
|
this._err(ERR.invalidFirstCharacterOfTagName);
|
|
937
|
-
this._emitChars('<');
|
|
1133
|
+
this._emitChars('<', -1);
|
|
938
1134
|
this.state = State.DATA;
|
|
939
1135
|
this._stateData(cp);
|
|
940
1136
|
}
|
|
@@ -957,7 +1153,7 @@ export class Tokenizer {
|
|
|
957
1153
|
}
|
|
958
1154
|
case $.EOF: {
|
|
959
1155
|
this._err(ERR.eofBeforeTagName);
|
|
960
|
-
this._emitChars('</');
|
|
1156
|
+
this._emitChars('</', -1);
|
|
961
1157
|
this._emitEOFToken();
|
|
962
1158
|
break;
|
|
963
1159
|
}
|
|
@@ -1012,7 +1208,7 @@ export class Tokenizer {
|
|
|
1012
1208
|
this.state = State.RCDATA_END_TAG_OPEN;
|
|
1013
1209
|
}
|
|
1014
1210
|
else {
|
|
1015
|
-
this._emitChars('<');
|
|
1211
|
+
this._emitChars('<', -1);
|
|
1016
1212
|
this.state = State.RCDATA;
|
|
1017
1213
|
this._stateRcdata(cp);
|
|
1018
1214
|
}
|
|
@@ -1025,7 +1221,7 @@ export class Tokenizer {
|
|
|
1025
1221
|
this._stateRcdataEndTagName(cp);
|
|
1026
1222
|
}
|
|
1027
1223
|
else {
|
|
1028
|
-
this._emitChars('</');
|
|
1224
|
+
this._emitChars('</', -1);
|
|
1029
1225
|
this.state = State.RCDATA;
|
|
1030
1226
|
this._stateRcdata(cp);
|
|
1031
1227
|
}
|
|
@@ -1059,6 +1255,8 @@ export class Tokenizer {
|
|
|
1059
1255
|
return false;
|
|
1060
1256
|
}
|
|
1061
1257
|
default: {
|
|
1258
|
+
//NOTE: the end tag is text after all, so no token is being built.
|
|
1259
|
+
this.currentToken = null;
|
|
1062
1260
|
return !this._ensureHibernation();
|
|
1063
1261
|
}
|
|
1064
1262
|
}
|
|
@@ -1067,7 +1265,7 @@ export class Tokenizer {
|
|
|
1067
1265
|
//------------------------------------------------------------------
|
|
1068
1266
|
_stateRcdataEndTagName(cp) {
|
|
1069
1267
|
if (this.handleSpecialEndTag(cp)) {
|
|
1070
|
-
this._emitChars('</');
|
|
1268
|
+
this._emitChars('</', -1);
|
|
1071
1269
|
this.state = State.RCDATA;
|
|
1072
1270
|
this._stateRcdata(cp);
|
|
1073
1271
|
}
|
|
@@ -1079,7 +1277,7 @@ export class Tokenizer {
|
|
|
1079
1277
|
this.state = State.RAWTEXT_END_TAG_OPEN;
|
|
1080
1278
|
}
|
|
1081
1279
|
else {
|
|
1082
|
-
this._emitChars('<');
|
|
1280
|
+
this._emitChars('<', -1);
|
|
1083
1281
|
this.state = State.RAWTEXT;
|
|
1084
1282
|
this._stateRawtext(cp);
|
|
1085
1283
|
}
|
|
@@ -1092,7 +1290,7 @@ export class Tokenizer {
|
|
|
1092
1290
|
this._stateRawtextEndTagName(cp);
|
|
1093
1291
|
}
|
|
1094
1292
|
else {
|
|
1095
|
-
this._emitChars('</');
|
|
1293
|
+
this._emitChars('</', -1);
|
|
1096
1294
|
this.state = State.RAWTEXT;
|
|
1097
1295
|
this._stateRawtext(cp);
|
|
1098
1296
|
}
|
|
@@ -1101,7 +1299,7 @@ export class Tokenizer {
|
|
|
1101
1299
|
//------------------------------------------------------------------
|
|
1102
1300
|
_stateRawtextEndTagName(cp) {
|
|
1103
1301
|
if (this.handleSpecialEndTag(cp)) {
|
|
1104
|
-
this._emitChars('</');
|
|
1302
|
+
this._emitChars('</', -1);
|
|
1105
1303
|
this.state = State.RAWTEXT;
|
|
1106
1304
|
this._stateRawtext(cp);
|
|
1107
1305
|
}
|
|
@@ -1120,7 +1318,7 @@ export class Tokenizer {
|
|
|
1120
1318
|
break;
|
|
1121
1319
|
}
|
|
1122
1320
|
default: {
|
|
1123
|
-
this._emitChars('<');
|
|
1321
|
+
this._emitChars('<', -1);
|
|
1124
1322
|
this.state = State.SCRIPT_DATA;
|
|
1125
1323
|
this._stateScriptData(cp);
|
|
1126
1324
|
}
|
|
@@ -1134,7 +1332,7 @@ export class Tokenizer {
|
|
|
1134
1332
|
this._stateScriptDataEndTagName(cp);
|
|
1135
1333
|
}
|
|
1136
1334
|
else {
|
|
1137
|
-
this._emitChars('</');
|
|
1335
|
+
this._emitChars('</', -1);
|
|
1138
1336
|
this.state = State.SCRIPT_DATA;
|
|
1139
1337
|
this._stateScriptData(cp);
|
|
1140
1338
|
}
|
|
@@ -1143,7 +1341,7 @@ export class Tokenizer {
|
|
|
1143
1341
|
//------------------------------------------------------------------
|
|
1144
1342
|
_stateScriptDataEndTagName(cp) {
|
|
1145
1343
|
if (this.handleSpecialEndTag(cp)) {
|
|
1146
|
-
this._emitChars('</');
|
|
1344
|
+
this._emitChars('</', -1);
|
|
1147
1345
|
this.state = State.SCRIPT_DATA;
|
|
1148
1346
|
this._stateScriptData(cp);
|
|
1149
1347
|
}
|
|
@@ -1271,12 +1469,12 @@ export class Tokenizer {
|
|
|
1271
1469
|
this.state = State.SCRIPT_DATA_ESCAPED_END_TAG_OPEN;
|
|
1272
1470
|
}
|
|
1273
1471
|
else if (isAsciiLetter(cp)) {
|
|
1274
|
-
this._emitChars('<');
|
|
1472
|
+
this._emitChars('<', -1);
|
|
1275
1473
|
this.state = State.SCRIPT_DATA_DOUBLE_ESCAPE_START;
|
|
1276
1474
|
this._stateScriptDataDoubleEscapeStart(cp);
|
|
1277
1475
|
}
|
|
1278
1476
|
else {
|
|
1279
|
-
this._emitChars('<');
|
|
1477
|
+
this._emitChars('<', -1);
|
|
1280
1478
|
this.state = State.SCRIPT_DATA_ESCAPED;
|
|
1281
1479
|
this._stateScriptDataEscaped(cp);
|
|
1282
1480
|
}
|
|
@@ -1289,7 +1487,7 @@ export class Tokenizer {
|
|
|
1289
1487
|
this._stateScriptDataEscapedEndTagName(cp);
|
|
1290
1488
|
}
|
|
1291
1489
|
else {
|
|
1292
|
-
this._emitChars('</');
|
|
1490
|
+
this._emitChars('</', -1);
|
|
1293
1491
|
this.state = State.SCRIPT_DATA_ESCAPED;
|
|
1294
1492
|
this._stateScriptDataEscaped(cp);
|
|
1295
1493
|
}
|
|
@@ -1298,7 +1496,7 @@ export class Tokenizer {
|
|
|
1298
1496
|
//------------------------------------------------------------------
|
|
1299
1497
|
_stateScriptDataEscapedEndTagName(cp) {
|
|
1300
1498
|
if (this.handleSpecialEndTag(cp)) {
|
|
1301
|
-
this._emitChars('</');
|
|
1499
|
+
this._emitChars('</', -1);
|
|
1302
1500
|
this.state = State.SCRIPT_DATA_ESCAPED;
|
|
1303
1501
|
this._stateScriptDataEscaped(cp);
|
|
1304
1502
|
}
|
|
@@ -1592,7 +1790,7 @@ export class Tokenizer {
|
|
|
1592
1790
|
}
|
|
1593
1791
|
case $.NULL: {
|
|
1594
1792
|
this._err(ERR.unexpectedNullCharacter);
|
|
1595
|
-
this.
|
|
1793
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1596
1794
|
break;
|
|
1597
1795
|
}
|
|
1598
1796
|
case $.EOF: {
|
|
@@ -1601,7 +1799,7 @@ export class Tokenizer {
|
|
|
1601
1799
|
break;
|
|
1602
1800
|
}
|
|
1603
1801
|
default: {
|
|
1604
|
-
this.
|
|
1802
|
+
this._extendRun(cp);
|
|
1605
1803
|
}
|
|
1606
1804
|
}
|
|
1607
1805
|
}
|
|
@@ -1619,7 +1817,7 @@ export class Tokenizer {
|
|
|
1619
1817
|
}
|
|
1620
1818
|
case $.NULL: {
|
|
1621
1819
|
this._err(ERR.unexpectedNullCharacter);
|
|
1622
|
-
this.
|
|
1820
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1623
1821
|
break;
|
|
1624
1822
|
}
|
|
1625
1823
|
case $.EOF: {
|
|
@@ -1628,7 +1826,7 @@ export class Tokenizer {
|
|
|
1628
1826
|
break;
|
|
1629
1827
|
}
|
|
1630
1828
|
default: {
|
|
1631
|
-
this.
|
|
1829
|
+
this._extendRun(cp);
|
|
1632
1830
|
}
|
|
1633
1831
|
}
|
|
1634
1832
|
}
|
|
@@ -1656,7 +1854,7 @@ export class Tokenizer {
|
|
|
1656
1854
|
}
|
|
1657
1855
|
case $.NULL: {
|
|
1658
1856
|
this._err(ERR.unexpectedNullCharacter);
|
|
1659
|
-
this.
|
|
1857
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1660
1858
|
break;
|
|
1661
1859
|
}
|
|
1662
1860
|
case $.QUOTATION_MARK:
|
|
@@ -1665,7 +1863,7 @@ export class Tokenizer {
|
|
|
1665
1863
|
case $.EQUALS_SIGN:
|
|
1666
1864
|
case $.GRAVE_ACCENT: {
|
|
1667
1865
|
this._err(ERR.unexpectedCharacterInUnquotedAttributeValue);
|
|
1668
|
-
this.
|
|
1866
|
+
this._extendRun(cp);
|
|
1669
1867
|
break;
|
|
1670
1868
|
}
|
|
1671
1869
|
case $.EOF: {
|
|
@@ -1674,7 +1872,7 @@ export class Tokenizer {
|
|
|
1674
1872
|
break;
|
|
1675
1873
|
}
|
|
1676
1874
|
default: {
|
|
1677
|
-
this.
|
|
1875
|
+
this._extendRun(cp);
|
|
1678
1876
|
}
|
|
1679
1877
|
}
|
|
1680
1878
|
}
|
|
@@ -1708,6 +1906,7 @@ export class Tokenizer {
|
|
|
1708
1906
|
}
|
|
1709
1907
|
default: {
|
|
1710
1908
|
this._err(ERR.missingWhitespaceBetweenAttributes);
|
|
1909
|
+
this._leaveAttrValue();
|
|
1711
1910
|
this.state = State.BEFORE_ATTRIBUTE_NAME;
|
|
1712
1911
|
this._stateBeforeAttributeName(cp);
|
|
1713
1912
|
}
|
|
@@ -1753,11 +1952,11 @@ export class Tokenizer {
|
|
|
1753
1952
|
}
|
|
1754
1953
|
case $.NULL: {
|
|
1755
1954
|
this._err(ERR.unexpectedNullCharacter);
|
|
1756
|
-
|
|
1955
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1757
1956
|
break;
|
|
1758
1957
|
}
|
|
1759
1958
|
default: {
|
|
1760
|
-
|
|
1959
|
+
this._extendRun(cp);
|
|
1761
1960
|
}
|
|
1762
1961
|
}
|
|
1763
1962
|
}
|
|
@@ -1836,7 +2035,7 @@ export class Tokenizer {
|
|
|
1836
2035
|
break;
|
|
1837
2036
|
}
|
|
1838
2037
|
default: {
|
|
1839
|
-
|
|
2038
|
+
this._extendRunWith('-', -1);
|
|
1840
2039
|
this.state = State.COMMENT;
|
|
1841
2040
|
this._stateComment(cp);
|
|
1842
2041
|
}
|
|
@@ -1852,13 +2051,13 @@ export class Tokenizer {
|
|
|
1852
2051
|
break;
|
|
1853
2052
|
}
|
|
1854
2053
|
case $.LESS_THAN_SIGN: {
|
|
1855
|
-
|
|
2054
|
+
this._extendRunWith('<', 0);
|
|
1856
2055
|
this.state = State.COMMENT_LESS_THAN_SIGN;
|
|
1857
2056
|
break;
|
|
1858
2057
|
}
|
|
1859
2058
|
case $.NULL: {
|
|
1860
2059
|
this._err(ERR.unexpectedNullCharacter);
|
|
1861
|
-
|
|
2060
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1862
2061
|
break;
|
|
1863
2062
|
}
|
|
1864
2063
|
case $.EOF: {
|
|
@@ -1868,22 +2067,21 @@ export class Tokenizer {
|
|
|
1868
2067
|
break;
|
|
1869
2068
|
}
|
|
1870
2069
|
default: {
|
|
1871
|
-
|
|
2070
|
+
this._extendRun(cp);
|
|
1872
2071
|
}
|
|
1873
2072
|
}
|
|
1874
2073
|
}
|
|
1875
2074
|
// Comment less-than sign state
|
|
1876
2075
|
//------------------------------------------------------------------
|
|
1877
2076
|
_stateCommentLessThanSign(cp) {
|
|
1878
|
-
const token = this.currentToken;
|
|
1879
2077
|
switch (cp) {
|
|
1880
2078
|
case $.EXCLAMATION_MARK: {
|
|
1881
|
-
|
|
2079
|
+
this._extendRunWith('!', 0);
|
|
1882
2080
|
this.state = State.COMMENT_LESS_THAN_SIGN_BANG;
|
|
1883
2081
|
break;
|
|
1884
2082
|
}
|
|
1885
2083
|
case $.LESS_THAN_SIGN: {
|
|
1886
|
-
|
|
2084
|
+
this._extendRunWith('<', 0);
|
|
1887
2085
|
break;
|
|
1888
2086
|
}
|
|
1889
2087
|
default: {
|
|
@@ -1939,7 +2137,7 @@ export class Tokenizer {
|
|
|
1939
2137
|
break;
|
|
1940
2138
|
}
|
|
1941
2139
|
default: {
|
|
1942
|
-
|
|
2140
|
+
this._extendRunWith('-', -1);
|
|
1943
2141
|
this.state = State.COMMENT;
|
|
1944
2142
|
this._stateComment(cp);
|
|
1945
2143
|
}
|
|
@@ -1960,7 +2158,7 @@ export class Tokenizer {
|
|
|
1960
2158
|
break;
|
|
1961
2159
|
}
|
|
1962
2160
|
case $.HYPHEN_MINUS: {
|
|
1963
|
-
|
|
2161
|
+
this._extendRunWith('-', -2);
|
|
1964
2162
|
break;
|
|
1965
2163
|
}
|
|
1966
2164
|
case $.EOF: {
|
|
@@ -1970,7 +2168,7 @@ export class Tokenizer {
|
|
|
1970
2168
|
break;
|
|
1971
2169
|
}
|
|
1972
2170
|
default: {
|
|
1973
|
-
|
|
2171
|
+
this._extendRunWith('--', -1);
|
|
1974
2172
|
this.state = State.COMMENT;
|
|
1975
2173
|
this._stateComment(cp);
|
|
1976
2174
|
}
|
|
@@ -1982,7 +2180,7 @@ export class Tokenizer {
|
|
|
1982
2180
|
const token = this.currentToken;
|
|
1983
2181
|
switch (cp) {
|
|
1984
2182
|
case $.HYPHEN_MINUS: {
|
|
1985
|
-
|
|
2183
|
+
this._extendRunWith('--!', -1);
|
|
1986
2184
|
this.state = State.COMMENT_END_DASH;
|
|
1987
2185
|
break;
|
|
1988
2186
|
}
|
|
@@ -1999,7 +2197,7 @@ export class Tokenizer {
|
|
|
1999
2197
|
break;
|
|
2000
2198
|
}
|
|
2001
2199
|
default: {
|
|
2002
|
-
|
|
2200
|
+
this._extendRunWith('--!', -1);
|
|
2003
2201
|
this.state = State.COMMENT;
|
|
2004
2202
|
this._stateComment(cp);
|
|
2005
2203
|
}
|
|
@@ -2636,7 +2834,7 @@ export class Tokenizer {
|
|
|
2636
2834
|
this.state = State.CDATA_SECTION_END;
|
|
2637
2835
|
}
|
|
2638
2836
|
else {
|
|
2639
|
-
this._emitChars(']');
|
|
2837
|
+
this._emitChars(']', -1);
|
|
2640
2838
|
this.state = State.CDATA_SECTION;
|
|
2641
2839
|
this._stateCdataSection(cp);
|
|
2642
2840
|
}
|
|
@@ -2650,11 +2848,11 @@ export class Tokenizer {
|
|
|
2650
2848
|
break;
|
|
2651
2849
|
}
|
|
2652
2850
|
case $.RIGHT_SQUARE_BRACKET: {
|
|
2653
|
-
this._emitChars(']');
|
|
2851
|
+
this._emitChars(']', -2);
|
|
2654
2852
|
break;
|
|
2655
2853
|
}
|
|
2656
2854
|
default: {
|
|
2657
|
-
this._emitChars(']]');
|
|
2855
|
+
this._emitChars(']]', -1);
|
|
2658
2856
|
this.state = State.CDATA_SECTION;
|
|
2659
2857
|
this._stateCdataSection(cp);
|
|
2660
2858
|
}
|
|
@@ -18,6 +18,9 @@ export declare class Preprocessor {
|
|
|
18
18
|
});
|
|
19
19
|
/** The column on the current line. If we just saw a gap (eg. a surrogate pair), return the index before. */
|
|
20
20
|
get col(): number;
|
|
21
|
+
/** The index in `html` at which the current code point starts (the high surrogate of a pair). */
|
|
22
|
+
get codePointStart(): number;
|
|
23
|
+
/** The offset of the current code point, before the second unit of a surrogate pair. */
|
|
21
24
|
get offset(): number;
|
|
22
25
|
getError(code: ERR, cpOffset: number): ParserError;
|
|
23
26
|
private lastErrOffset;
|
|
@@ -28,8 +28,13 @@ export class Preprocessor {
|
|
|
28
28
|
get col() {
|
|
29
29
|
return this.pos - this.lineStartPos + Number(this.lastGapPos !== this.pos);
|
|
30
30
|
}
|
|
31
|
+
/** The index in `html` at which the current code point starts (the high surrogate of a pair). */
|
|
32
|
+
get codePointStart() {
|
|
33
|
+
return this.pos - Number(this.lastGapPos === this.pos);
|
|
34
|
+
}
|
|
35
|
+
/** The offset of the current code point, before the second unit of a surrogate pair. */
|
|
31
36
|
get offset() {
|
|
32
|
-
return this.droppedBufferSize + this.
|
|
37
|
+
return this.droppedBufferSize + this.codePointStart;
|
|
33
38
|
}
|
|
34
39
|
getError(code, cpOffset) {
|
|
35
40
|
const { line, col, offset } = this;
|
|
@@ -81,12 +86,16 @@ export class Preprocessor {
|
|
|
81
86
|
}
|
|
82
87
|
dropParsedChunk() {
|
|
83
88
|
if (this.willDropParsedChunk()) {
|
|
84
|
-
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
87
|
-
this.
|
|
89
|
+
// Retain the entire current code point, including both units of a surrogate pair.
|
|
90
|
+
const droppedSize = this.offset - this.droppedBufferSize;
|
|
91
|
+
this.html = this.html.substring(droppedSize);
|
|
92
|
+
this.lineStartPos -= droppedSize;
|
|
93
|
+
this.droppedBufferSize += droppedSize;
|
|
94
|
+
this.pos -= droppedSize;
|
|
88
95
|
this.lastGapPos = -2;
|
|
89
96
|
this.gapStack.length = 0;
|
|
97
|
+
if (this.pos > 0)
|
|
98
|
+
this._addGap();
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
write(chunk, isLastChunk) {
|
|
@@ -12,6 +12,15 @@ export interface TreeAdapterTypeMap<Node = unknown, ParentNode = unknown, ChildN
|
|
|
12
12
|
template: Template;
|
|
13
13
|
documentType: DocumentType;
|
|
14
14
|
}
|
|
15
|
+
/** Parser state needed to establish a form-associated element's parser-established form owner. */
|
|
16
|
+
export interface ElementCreationContext<T extends TreeAdapterTypeMap = TreeAdapterTypeMap> {
|
|
17
|
+
/** The target parent from the appropriate place for inserting a node. */
|
|
18
|
+
intendedParent: T['parentNode'];
|
|
19
|
+
/** The element referenced by the parser's form element pointer, if any. */
|
|
20
|
+
formElement: T['element'] | null;
|
|
21
|
+
/** Whether the stack of open elements contains an HTML `template` element. */
|
|
22
|
+
isInTemplate: boolean;
|
|
23
|
+
}
|
|
15
24
|
/**
|
|
16
25
|
* Tree adapter is a set of utility functions that provides minimal required abstraction layer beetween parser and a specific AST format.
|
|
17
26
|
* Note that `TreeAdapter` is not designed to be a general purpose AST manipulation library. You can build such library
|
|
@@ -240,6 +249,14 @@ export interface TreeAdapter<T extends TreeAdapterTypeMap = TreeAdapterTypeMap>
|
|
|
240
249
|
* @param contentElement - Content element.
|
|
241
250
|
*/
|
|
242
251
|
setTemplateContent(templateElement: T['template'], contentElement: T['documentFragment']): void;
|
|
252
|
+
/**
|
|
253
|
+
* Optional callback invoked after an element is created from a token and before it is inserted. This exposes the
|
|
254
|
+
* parser state needed to establish a form-associated element's parser-established form owner.
|
|
255
|
+
*
|
|
256
|
+
* @param element The created element.
|
|
257
|
+
* @param context Relevant parser state at the time of creation.
|
|
258
|
+
*/
|
|
259
|
+
onElementCreated?: (element: T['element'], context: ElementCreationContext<T>) => void;
|
|
243
260
|
/**
|
|
244
261
|
* Optional callback for elements being pushed to the stack of open elements.
|
|
245
262
|
*
|
package/package.json
CHANGED
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
"name": "parse5",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "HTML parser and serializer.",
|
|
5
|
-
"version": "8.
|
|
5
|
+
"version": "8.1.0",
|
|
6
6
|
"author": "Ivan Nikulin <ifaaan@gmail.com> (https://github.com/inikulin)",
|
|
7
|
-
"contributors":
|
|
7
|
+
"contributors": [
|
|
8
|
+
"James Garbutt (https://github.com/43081j)",
|
|
9
|
+
"Felix Boehm (https://github.com/fb55)",
|
|
10
|
+
"Ivan Nikulin (https://github.com/inikulin)",
|
|
11
|
+
"Titus (https://github.com/wooorm)"
|
|
12
|
+
],
|
|
8
13
|
"homepage": "https://parse5.js.org",
|
|
9
14
|
"funding": "https://github.com/inikulin/parse5?sponsor=1",
|
|
10
15
|
"dependencies": {
|
|
11
|
-
"entities": "^
|
|
16
|
+
"entities": "^9.0.0"
|
|
12
17
|
},
|
|
13
18
|
"keywords": [
|
|
14
19
|
"html",
|
|
@@ -44,5 +49,9 @@
|
|
|
44
49
|
"files": [
|
|
45
50
|
"dist/**/*.js",
|
|
46
51
|
"dist/**/*.d.ts"
|
|
47
|
-
]
|
|
52
|
+
],
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"parse5-htmlparser2-tree-adapter": "^8.0.0",
|
|
55
|
+
"parse5-test-utils": "1.0.0"
|
|
56
|
+
}
|
|
48
57
|
}
|