parse5 8.0.1 → 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 -67
- 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 +7 -3
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,34 +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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
641
|
+
const token = this.currentCharacterToken;
|
|
642
|
+
if (token === null || token.type !== type) {
|
|
643
|
+
this._ensureCharacterToken(type);
|
|
644
|
+
}
|
|
645
|
+
this._extendRun(cp);
|
|
465
646
|
}
|
|
466
647
|
//NOTE: used when we emit characters explicitly.
|
|
467
648
|
//This is always for non-whitespace and non-null characters, which allows us to avoid additional checks.
|
|
468
|
-
|
|
469
|
-
|
|
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);
|
|
470
664
|
}
|
|
471
665
|
// Character reference helpers
|
|
472
666
|
_startCharacterReference() {
|
|
@@ -482,7 +676,7 @@ export class Tokenizer {
|
|
|
482
676
|
}
|
|
483
677
|
_flushCodePointConsumedAsCharacterReference(cp) {
|
|
484
678
|
if (this._isCharacterReferenceInAttribute()) {
|
|
485
|
-
this.
|
|
679
|
+
this._extendRun(cp);
|
|
486
680
|
}
|
|
487
681
|
else {
|
|
488
682
|
this._emitCodePoint(cp);
|
|
@@ -930,13 +1124,13 @@ export class Tokenizer {
|
|
|
930
1124
|
}
|
|
931
1125
|
case $.EOF: {
|
|
932
1126
|
this._err(ERR.eofBeforeTagName);
|
|
933
|
-
this._emitChars('<');
|
|
1127
|
+
this._emitChars('<', -1);
|
|
934
1128
|
this._emitEOFToken();
|
|
935
1129
|
break;
|
|
936
1130
|
}
|
|
937
1131
|
default: {
|
|
938
1132
|
this._err(ERR.invalidFirstCharacterOfTagName);
|
|
939
|
-
this._emitChars('<');
|
|
1133
|
+
this._emitChars('<', -1);
|
|
940
1134
|
this.state = State.DATA;
|
|
941
1135
|
this._stateData(cp);
|
|
942
1136
|
}
|
|
@@ -959,7 +1153,7 @@ export class Tokenizer {
|
|
|
959
1153
|
}
|
|
960
1154
|
case $.EOF: {
|
|
961
1155
|
this._err(ERR.eofBeforeTagName);
|
|
962
|
-
this._emitChars('</');
|
|
1156
|
+
this._emitChars('</', -1);
|
|
963
1157
|
this._emitEOFToken();
|
|
964
1158
|
break;
|
|
965
1159
|
}
|
|
@@ -1014,7 +1208,7 @@ export class Tokenizer {
|
|
|
1014
1208
|
this.state = State.RCDATA_END_TAG_OPEN;
|
|
1015
1209
|
}
|
|
1016
1210
|
else {
|
|
1017
|
-
this._emitChars('<');
|
|
1211
|
+
this._emitChars('<', -1);
|
|
1018
1212
|
this.state = State.RCDATA;
|
|
1019
1213
|
this._stateRcdata(cp);
|
|
1020
1214
|
}
|
|
@@ -1027,7 +1221,7 @@ export class Tokenizer {
|
|
|
1027
1221
|
this._stateRcdataEndTagName(cp);
|
|
1028
1222
|
}
|
|
1029
1223
|
else {
|
|
1030
|
-
this._emitChars('</');
|
|
1224
|
+
this._emitChars('</', -1);
|
|
1031
1225
|
this.state = State.RCDATA;
|
|
1032
1226
|
this._stateRcdata(cp);
|
|
1033
1227
|
}
|
|
@@ -1061,6 +1255,8 @@ export class Tokenizer {
|
|
|
1061
1255
|
return false;
|
|
1062
1256
|
}
|
|
1063
1257
|
default: {
|
|
1258
|
+
//NOTE: the end tag is text after all, so no token is being built.
|
|
1259
|
+
this.currentToken = null;
|
|
1064
1260
|
return !this._ensureHibernation();
|
|
1065
1261
|
}
|
|
1066
1262
|
}
|
|
@@ -1069,7 +1265,7 @@ export class Tokenizer {
|
|
|
1069
1265
|
//------------------------------------------------------------------
|
|
1070
1266
|
_stateRcdataEndTagName(cp) {
|
|
1071
1267
|
if (this.handleSpecialEndTag(cp)) {
|
|
1072
|
-
this._emitChars('</');
|
|
1268
|
+
this._emitChars('</', -1);
|
|
1073
1269
|
this.state = State.RCDATA;
|
|
1074
1270
|
this._stateRcdata(cp);
|
|
1075
1271
|
}
|
|
@@ -1081,7 +1277,7 @@ export class Tokenizer {
|
|
|
1081
1277
|
this.state = State.RAWTEXT_END_TAG_OPEN;
|
|
1082
1278
|
}
|
|
1083
1279
|
else {
|
|
1084
|
-
this._emitChars('<');
|
|
1280
|
+
this._emitChars('<', -1);
|
|
1085
1281
|
this.state = State.RAWTEXT;
|
|
1086
1282
|
this._stateRawtext(cp);
|
|
1087
1283
|
}
|
|
@@ -1094,7 +1290,7 @@ export class Tokenizer {
|
|
|
1094
1290
|
this._stateRawtextEndTagName(cp);
|
|
1095
1291
|
}
|
|
1096
1292
|
else {
|
|
1097
|
-
this._emitChars('</');
|
|
1293
|
+
this._emitChars('</', -1);
|
|
1098
1294
|
this.state = State.RAWTEXT;
|
|
1099
1295
|
this._stateRawtext(cp);
|
|
1100
1296
|
}
|
|
@@ -1103,7 +1299,7 @@ export class Tokenizer {
|
|
|
1103
1299
|
//------------------------------------------------------------------
|
|
1104
1300
|
_stateRawtextEndTagName(cp) {
|
|
1105
1301
|
if (this.handleSpecialEndTag(cp)) {
|
|
1106
|
-
this._emitChars('</');
|
|
1302
|
+
this._emitChars('</', -1);
|
|
1107
1303
|
this.state = State.RAWTEXT;
|
|
1108
1304
|
this._stateRawtext(cp);
|
|
1109
1305
|
}
|
|
@@ -1122,7 +1318,7 @@ export class Tokenizer {
|
|
|
1122
1318
|
break;
|
|
1123
1319
|
}
|
|
1124
1320
|
default: {
|
|
1125
|
-
this._emitChars('<');
|
|
1321
|
+
this._emitChars('<', -1);
|
|
1126
1322
|
this.state = State.SCRIPT_DATA;
|
|
1127
1323
|
this._stateScriptData(cp);
|
|
1128
1324
|
}
|
|
@@ -1136,7 +1332,7 @@ export class Tokenizer {
|
|
|
1136
1332
|
this._stateScriptDataEndTagName(cp);
|
|
1137
1333
|
}
|
|
1138
1334
|
else {
|
|
1139
|
-
this._emitChars('</');
|
|
1335
|
+
this._emitChars('</', -1);
|
|
1140
1336
|
this.state = State.SCRIPT_DATA;
|
|
1141
1337
|
this._stateScriptData(cp);
|
|
1142
1338
|
}
|
|
@@ -1145,7 +1341,7 @@ export class Tokenizer {
|
|
|
1145
1341
|
//------------------------------------------------------------------
|
|
1146
1342
|
_stateScriptDataEndTagName(cp) {
|
|
1147
1343
|
if (this.handleSpecialEndTag(cp)) {
|
|
1148
|
-
this._emitChars('</');
|
|
1344
|
+
this._emitChars('</', -1);
|
|
1149
1345
|
this.state = State.SCRIPT_DATA;
|
|
1150
1346
|
this._stateScriptData(cp);
|
|
1151
1347
|
}
|
|
@@ -1273,12 +1469,12 @@ export class Tokenizer {
|
|
|
1273
1469
|
this.state = State.SCRIPT_DATA_ESCAPED_END_TAG_OPEN;
|
|
1274
1470
|
}
|
|
1275
1471
|
else if (isAsciiLetter(cp)) {
|
|
1276
|
-
this._emitChars('<');
|
|
1472
|
+
this._emitChars('<', -1);
|
|
1277
1473
|
this.state = State.SCRIPT_DATA_DOUBLE_ESCAPE_START;
|
|
1278
1474
|
this._stateScriptDataDoubleEscapeStart(cp);
|
|
1279
1475
|
}
|
|
1280
1476
|
else {
|
|
1281
|
-
this._emitChars('<');
|
|
1477
|
+
this._emitChars('<', -1);
|
|
1282
1478
|
this.state = State.SCRIPT_DATA_ESCAPED;
|
|
1283
1479
|
this._stateScriptDataEscaped(cp);
|
|
1284
1480
|
}
|
|
@@ -1291,7 +1487,7 @@ export class Tokenizer {
|
|
|
1291
1487
|
this._stateScriptDataEscapedEndTagName(cp);
|
|
1292
1488
|
}
|
|
1293
1489
|
else {
|
|
1294
|
-
this._emitChars('</');
|
|
1490
|
+
this._emitChars('</', -1);
|
|
1295
1491
|
this.state = State.SCRIPT_DATA_ESCAPED;
|
|
1296
1492
|
this._stateScriptDataEscaped(cp);
|
|
1297
1493
|
}
|
|
@@ -1300,7 +1496,7 @@ export class Tokenizer {
|
|
|
1300
1496
|
//------------------------------------------------------------------
|
|
1301
1497
|
_stateScriptDataEscapedEndTagName(cp) {
|
|
1302
1498
|
if (this.handleSpecialEndTag(cp)) {
|
|
1303
|
-
this._emitChars('</');
|
|
1499
|
+
this._emitChars('</', -1);
|
|
1304
1500
|
this.state = State.SCRIPT_DATA_ESCAPED;
|
|
1305
1501
|
this._stateScriptDataEscaped(cp);
|
|
1306
1502
|
}
|
|
@@ -1594,7 +1790,7 @@ export class Tokenizer {
|
|
|
1594
1790
|
}
|
|
1595
1791
|
case $.NULL: {
|
|
1596
1792
|
this._err(ERR.unexpectedNullCharacter);
|
|
1597
|
-
this.
|
|
1793
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1598
1794
|
break;
|
|
1599
1795
|
}
|
|
1600
1796
|
case $.EOF: {
|
|
@@ -1603,7 +1799,7 @@ export class Tokenizer {
|
|
|
1603
1799
|
break;
|
|
1604
1800
|
}
|
|
1605
1801
|
default: {
|
|
1606
|
-
this.
|
|
1802
|
+
this._extendRun(cp);
|
|
1607
1803
|
}
|
|
1608
1804
|
}
|
|
1609
1805
|
}
|
|
@@ -1621,7 +1817,7 @@ export class Tokenizer {
|
|
|
1621
1817
|
}
|
|
1622
1818
|
case $.NULL: {
|
|
1623
1819
|
this._err(ERR.unexpectedNullCharacter);
|
|
1624
|
-
this.
|
|
1820
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1625
1821
|
break;
|
|
1626
1822
|
}
|
|
1627
1823
|
case $.EOF: {
|
|
@@ -1630,7 +1826,7 @@ export class Tokenizer {
|
|
|
1630
1826
|
break;
|
|
1631
1827
|
}
|
|
1632
1828
|
default: {
|
|
1633
|
-
this.
|
|
1829
|
+
this._extendRun(cp);
|
|
1634
1830
|
}
|
|
1635
1831
|
}
|
|
1636
1832
|
}
|
|
@@ -1658,7 +1854,7 @@ export class Tokenizer {
|
|
|
1658
1854
|
}
|
|
1659
1855
|
case $.NULL: {
|
|
1660
1856
|
this._err(ERR.unexpectedNullCharacter);
|
|
1661
|
-
this.
|
|
1857
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1662
1858
|
break;
|
|
1663
1859
|
}
|
|
1664
1860
|
case $.QUOTATION_MARK:
|
|
@@ -1667,7 +1863,7 @@ export class Tokenizer {
|
|
|
1667
1863
|
case $.EQUALS_SIGN:
|
|
1668
1864
|
case $.GRAVE_ACCENT: {
|
|
1669
1865
|
this._err(ERR.unexpectedCharacterInUnquotedAttributeValue);
|
|
1670
|
-
this.
|
|
1866
|
+
this._extendRun(cp);
|
|
1671
1867
|
break;
|
|
1672
1868
|
}
|
|
1673
1869
|
case $.EOF: {
|
|
@@ -1676,7 +1872,7 @@ export class Tokenizer {
|
|
|
1676
1872
|
break;
|
|
1677
1873
|
}
|
|
1678
1874
|
default: {
|
|
1679
|
-
this.
|
|
1875
|
+
this._extendRun(cp);
|
|
1680
1876
|
}
|
|
1681
1877
|
}
|
|
1682
1878
|
}
|
|
@@ -1710,6 +1906,7 @@ export class Tokenizer {
|
|
|
1710
1906
|
}
|
|
1711
1907
|
default: {
|
|
1712
1908
|
this._err(ERR.missingWhitespaceBetweenAttributes);
|
|
1909
|
+
this._leaveAttrValue();
|
|
1713
1910
|
this.state = State.BEFORE_ATTRIBUTE_NAME;
|
|
1714
1911
|
this._stateBeforeAttributeName(cp);
|
|
1715
1912
|
}
|
|
@@ -1755,11 +1952,11 @@ export class Tokenizer {
|
|
|
1755
1952
|
}
|
|
1756
1953
|
case $.NULL: {
|
|
1757
1954
|
this._err(ERR.unexpectedNullCharacter);
|
|
1758
|
-
|
|
1955
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1759
1956
|
break;
|
|
1760
1957
|
}
|
|
1761
1958
|
default: {
|
|
1762
|
-
|
|
1959
|
+
this._extendRun(cp);
|
|
1763
1960
|
}
|
|
1764
1961
|
}
|
|
1765
1962
|
}
|
|
@@ -1838,7 +2035,7 @@ export class Tokenizer {
|
|
|
1838
2035
|
break;
|
|
1839
2036
|
}
|
|
1840
2037
|
default: {
|
|
1841
|
-
|
|
2038
|
+
this._extendRunWith('-', -1);
|
|
1842
2039
|
this.state = State.COMMENT;
|
|
1843
2040
|
this._stateComment(cp);
|
|
1844
2041
|
}
|
|
@@ -1854,13 +2051,13 @@ export class Tokenizer {
|
|
|
1854
2051
|
break;
|
|
1855
2052
|
}
|
|
1856
2053
|
case $.LESS_THAN_SIGN: {
|
|
1857
|
-
|
|
2054
|
+
this._extendRunWith('<', 0);
|
|
1858
2055
|
this.state = State.COMMENT_LESS_THAN_SIGN;
|
|
1859
2056
|
break;
|
|
1860
2057
|
}
|
|
1861
2058
|
case $.NULL: {
|
|
1862
2059
|
this._err(ERR.unexpectedNullCharacter);
|
|
1863
|
-
|
|
2060
|
+
this._extendRunWith(REPLACEMENT_CHARACTER, 0);
|
|
1864
2061
|
break;
|
|
1865
2062
|
}
|
|
1866
2063
|
case $.EOF: {
|
|
@@ -1870,22 +2067,21 @@ export class Tokenizer {
|
|
|
1870
2067
|
break;
|
|
1871
2068
|
}
|
|
1872
2069
|
default: {
|
|
1873
|
-
|
|
2070
|
+
this._extendRun(cp);
|
|
1874
2071
|
}
|
|
1875
2072
|
}
|
|
1876
2073
|
}
|
|
1877
2074
|
// Comment less-than sign state
|
|
1878
2075
|
//------------------------------------------------------------------
|
|
1879
2076
|
_stateCommentLessThanSign(cp) {
|
|
1880
|
-
const token = this.currentToken;
|
|
1881
2077
|
switch (cp) {
|
|
1882
2078
|
case $.EXCLAMATION_MARK: {
|
|
1883
|
-
|
|
2079
|
+
this._extendRunWith('!', 0);
|
|
1884
2080
|
this.state = State.COMMENT_LESS_THAN_SIGN_BANG;
|
|
1885
2081
|
break;
|
|
1886
2082
|
}
|
|
1887
2083
|
case $.LESS_THAN_SIGN: {
|
|
1888
|
-
|
|
2084
|
+
this._extendRunWith('<', 0);
|
|
1889
2085
|
break;
|
|
1890
2086
|
}
|
|
1891
2087
|
default: {
|
|
@@ -1941,7 +2137,7 @@ export class Tokenizer {
|
|
|
1941
2137
|
break;
|
|
1942
2138
|
}
|
|
1943
2139
|
default: {
|
|
1944
|
-
|
|
2140
|
+
this._extendRunWith('-', -1);
|
|
1945
2141
|
this.state = State.COMMENT;
|
|
1946
2142
|
this._stateComment(cp);
|
|
1947
2143
|
}
|
|
@@ -1962,7 +2158,7 @@ export class Tokenizer {
|
|
|
1962
2158
|
break;
|
|
1963
2159
|
}
|
|
1964
2160
|
case $.HYPHEN_MINUS: {
|
|
1965
|
-
|
|
2161
|
+
this._extendRunWith('-', -2);
|
|
1966
2162
|
break;
|
|
1967
2163
|
}
|
|
1968
2164
|
case $.EOF: {
|
|
@@ -1972,7 +2168,7 @@ export class Tokenizer {
|
|
|
1972
2168
|
break;
|
|
1973
2169
|
}
|
|
1974
2170
|
default: {
|
|
1975
|
-
|
|
2171
|
+
this._extendRunWith('--', -1);
|
|
1976
2172
|
this.state = State.COMMENT;
|
|
1977
2173
|
this._stateComment(cp);
|
|
1978
2174
|
}
|
|
@@ -1984,7 +2180,7 @@ export class Tokenizer {
|
|
|
1984
2180
|
const token = this.currentToken;
|
|
1985
2181
|
switch (cp) {
|
|
1986
2182
|
case $.HYPHEN_MINUS: {
|
|
1987
|
-
|
|
2183
|
+
this._extendRunWith('--!', -1);
|
|
1988
2184
|
this.state = State.COMMENT_END_DASH;
|
|
1989
2185
|
break;
|
|
1990
2186
|
}
|
|
@@ -2001,7 +2197,7 @@ export class Tokenizer {
|
|
|
2001
2197
|
break;
|
|
2002
2198
|
}
|
|
2003
2199
|
default: {
|
|
2004
|
-
|
|
2200
|
+
this._extendRunWith('--!', -1);
|
|
2005
2201
|
this.state = State.COMMENT;
|
|
2006
2202
|
this._stateComment(cp);
|
|
2007
2203
|
}
|
|
@@ -2638,7 +2834,7 @@ export class Tokenizer {
|
|
|
2638
2834
|
this.state = State.CDATA_SECTION_END;
|
|
2639
2835
|
}
|
|
2640
2836
|
else {
|
|
2641
|
-
this._emitChars(']');
|
|
2837
|
+
this._emitChars(']', -1);
|
|
2642
2838
|
this.state = State.CDATA_SECTION;
|
|
2643
2839
|
this._stateCdataSection(cp);
|
|
2644
2840
|
}
|
|
@@ -2652,11 +2848,11 @@ export class Tokenizer {
|
|
|
2652
2848
|
break;
|
|
2653
2849
|
}
|
|
2654
2850
|
case $.RIGHT_SQUARE_BRACKET: {
|
|
2655
|
-
this._emitChars(']');
|
|
2851
|
+
this._emitChars(']', -2);
|
|
2656
2852
|
break;
|
|
2657
2853
|
}
|
|
2658
2854
|
default: {
|
|
2659
|
-
this._emitChars(']]');
|
|
2855
|
+
this._emitChars(']]', -1);
|
|
2660
2856
|
this.state = State.CDATA_SECTION;
|
|
2661
2857
|
this._stateCdataSection(cp);
|
|
2662
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,7 +2,7 @@
|
|
|
2
2
|
"name": "parse5",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "HTML parser and serializer.",
|
|
5
|
-
"version": "8.0
|
|
5
|
+
"version": "8.1.0",
|
|
6
6
|
"author": "Ivan Nikulin <ifaaan@gmail.com> (https://github.com/inikulin)",
|
|
7
7
|
"contributors": [
|
|
8
8
|
"James Garbutt (https://github.com/43081j)",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"homepage": "https://parse5.js.org",
|
|
14
14
|
"funding": "https://github.com/inikulin/parse5?sponsor=1",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"entities": "^
|
|
16
|
+
"entities": "^9.0.0"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"html",
|
|
@@ -49,5 +49,9 @@
|
|
|
49
49
|
"files": [
|
|
50
50
|
"dist/**/*.js",
|
|
51
51
|
"dist/**/*.d.ts"
|
|
52
|
-
]
|
|
52
|
+
],
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"parse5-htmlparser2-tree-adapter": "^8.0.0",
|
|
55
|
+
"parse5-test-utils": "1.0.0"
|
|
56
|
+
}
|
|
53
57
|
}
|