parse5 8.1.0 → 8.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/common/foreign-content.d.ts +1 -1
- package/dist/parser/formatting-element-list.d.ts +38 -10
- package/dist/parser/formatting-element-list.js +153 -70
- package/dist/parser/index.d.ts +6 -2
- package/dist/parser/index.js +169 -135
- package/dist/parser/open-element-stack.d.ts +85 -8
- package/dist/parser/open-element-stack.js +500 -150
- package/dist/serializer/index.js +48 -34
- package/dist/tokenizer/index.d.ts +3 -0
- package/dist/tokenizer/index.js +59 -5
- package/dist/tokenizer/preprocessor.d.ts +5 -1
- package/dist/tokenizer/preprocessor.js +8 -4
- package/dist/tree-adapters/default.js +50 -6
- package/package.json +1 -1
package/dist/serializer/index.js
CHANGED
|
@@ -51,11 +51,19 @@ const defaultOpts = { treeAdapter: defaultTreeAdapter, scriptingEnabled: true };
|
|
|
51
51
|
* @param options Serialization options.
|
|
52
52
|
*/
|
|
53
53
|
export function serialize(node, options) {
|
|
54
|
+
var _a;
|
|
54
55
|
const opts = { ...defaultOpts, ...options };
|
|
55
56
|
if (isVoidElement(node, opts)) {
|
|
56
57
|
return '';
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
+
const { treeAdapter } = opts;
|
|
60
|
+
// A template's children are those of its content fragment.
|
|
61
|
+
const container = treeAdapter.isElementNode(node) &&
|
|
62
|
+
treeAdapter.getTagName(node) === $.TEMPLATE &&
|
|
63
|
+
treeAdapter.getNamespaceURI(node) === NS.HTML
|
|
64
|
+
? treeAdapter.getTemplateContent(node)
|
|
65
|
+
: node;
|
|
66
|
+
return serializeNodes((_a = treeAdapter.getChildNodes(container)) !== null && _a !== void 0 ? _a : [], opts);
|
|
59
67
|
}
|
|
60
68
|
/**
|
|
61
69
|
* Serializes an AST element node to an HTML string, including the element node.
|
|
@@ -78,43 +86,49 @@ export function serialize(node, options) {
|
|
|
78
86
|
*/
|
|
79
87
|
export function serializeOuter(node, options) {
|
|
80
88
|
const opts = { ...defaultOpts, ...options };
|
|
81
|
-
return
|
|
89
|
+
return serializeNodes([node], opts);
|
|
82
90
|
}
|
|
83
|
-
function
|
|
91
|
+
function serializeNodes(nodes, options) {
|
|
92
|
+
var _a;
|
|
93
|
+
const { treeAdapter } = options;
|
|
94
|
+
// The depth of the tree is not bounded, so the open elements (their
|
|
95
|
+
// remaining siblings and end tags) are kept on explicit stacks.
|
|
96
|
+
const stack = [];
|
|
84
97
|
let html = '';
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
let index = 0;
|
|
99
|
+
for (;;) {
|
|
100
|
+
if (index < nodes.length) {
|
|
101
|
+
const node = nodes[index++];
|
|
102
|
+
if (treeAdapter.isElementNode(node)) {
|
|
103
|
+
const tn = treeAdapter.getTagName(node);
|
|
104
|
+
html += `<${tn}${serializeAttributes(node, options)}>`;
|
|
105
|
+
const isHtml = treeAdapter.getNamespaceURI(node) === NS.HTML;
|
|
106
|
+
if (!(isHtml && VOID_ELEMENTS.has(tn))) {
|
|
107
|
+
stack.push({ nodes, index, endTag: `</${tn}>` });
|
|
108
|
+
nodes =
|
|
109
|
+
(_a = treeAdapter.getChildNodes(isHtml && tn === $.TEMPLATE ? treeAdapter.getTemplateContent(node) : node)) !== null && _a !== void 0 ? _a : [];
|
|
110
|
+
index = 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (treeAdapter.isTextNode(node)) {
|
|
114
|
+
html += serializeTextNode(node, options);
|
|
115
|
+
}
|
|
116
|
+
else if (treeAdapter.isCommentNode(node)) {
|
|
117
|
+
html += serializeCommentNode(node, options);
|
|
118
|
+
}
|
|
119
|
+
else if (treeAdapter.isDocumentTypeNode(node)) {
|
|
120
|
+
html += serializeDocumentTypeNode(node, options);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
const parent = stack.pop();
|
|
125
|
+
if (!parent) {
|
|
126
|
+
return html;
|
|
127
|
+
}
|
|
128
|
+
html += parent.endTag;
|
|
129
|
+
({ nodes, index } = parent);
|
|
95
130
|
}
|
|
96
131
|
}
|
|
97
|
-
return html;
|
|
98
|
-
}
|
|
99
|
-
function serializeNode(node, options) {
|
|
100
|
-
if (options.treeAdapter.isElementNode(node)) {
|
|
101
|
-
return serializeElement(node, options);
|
|
102
|
-
}
|
|
103
|
-
if (options.treeAdapter.isTextNode(node)) {
|
|
104
|
-
return serializeTextNode(node, options);
|
|
105
|
-
}
|
|
106
|
-
if (options.treeAdapter.isCommentNode(node)) {
|
|
107
|
-
return serializeCommentNode(node, options);
|
|
108
|
-
}
|
|
109
|
-
if (options.treeAdapter.isDocumentTypeNode(node)) {
|
|
110
|
-
return serializeDocumentTypeNode(node, options);
|
|
111
|
-
}
|
|
112
|
-
// Return an empty string for unknown nodes
|
|
113
|
-
return '';
|
|
114
|
-
}
|
|
115
|
-
function serializeElement(node, options) {
|
|
116
|
-
const tn = options.treeAdapter.getTagName(node);
|
|
117
|
-
return `<${tn}${serializeAttributes(node, options)}>${isVoidElement(node, options) ? '' : `${serializeChildNodes(node, options)}</${tn}>`}`;
|
|
118
132
|
}
|
|
119
133
|
function serializeAttributes(node, { treeAdapter }) {
|
|
120
134
|
let html = '';
|
|
@@ -137,6 +137,7 @@ export declare class Tokenizer {
|
|
|
137
137
|
protected currentToken: Token | null;
|
|
138
138
|
protected currentAttr: Attribute;
|
|
139
139
|
private currentAttrLocation;
|
|
140
|
+
private currentAttrNames;
|
|
140
141
|
/**
|
|
141
142
|
* `[start, end)` of `preprocessor.html` still to be appended to the attribute value, comment data or character
|
|
142
143
|
* token being built (`_flushRun`): input that is used as is is appended as one slice, not a code point at a time.
|
|
@@ -155,6 +156,8 @@ export declare class Tokenizer {
|
|
|
155
156
|
resume(writeCallback?: () => void): void;
|
|
156
157
|
write(chunk: string, isLastChunk: boolean, writeCallback?: () => void): void;
|
|
157
158
|
insertHtmlAtCurrentPos(chunk: string): void;
|
|
159
|
+
/** @internal Flush completed streaming text without splitting an unfinished token. */
|
|
160
|
+
flushCurrentCharacterToken(): void;
|
|
158
161
|
protected _ensureHibernation(): boolean;
|
|
159
162
|
protected _consume(): number;
|
|
160
163
|
protected _advanceBy(count: number): void;
|
package/dist/tokenizer/index.js
CHANGED
|
@@ -4,6 +4,9 @@ import { TokenType, getTokenAttr, } from '../common/token.js';
|
|
|
4
4
|
import { HtmlEntityDecoder, DecodingMode } from 'entities/decode';
|
|
5
5
|
import { ERR } from '../common/error-codes.js';
|
|
6
6
|
import { TAG_ID, getTagID } from '../common/html.js';
|
|
7
|
+
// The few attributes a tag usually has are scanned for a duplicate name; from this many on, the names are
|
|
8
|
+
// collected in a Set instead, so that the check doesn't get quadratic in the number of attributes.
|
|
9
|
+
const ATTR_NAME_SET_THRESHOLD = 64;
|
|
7
10
|
//States
|
|
8
11
|
var State;
|
|
9
12
|
(function (State) {
|
|
@@ -161,6 +164,7 @@ export class Tokenizer {
|
|
|
161
164
|
this.currentToken = null;
|
|
162
165
|
this.currentAttr = { name: '', value: '' };
|
|
163
166
|
this.currentAttrLocation = null;
|
|
167
|
+
this.currentAttrNames = null;
|
|
164
168
|
/**
|
|
165
169
|
* `[start, end)` of `preprocessor.html` still to be appended to the attribute value, comment data or character
|
|
166
170
|
* token being built (`_flushRun`): input that is used as is is appended as one slice, not a code point at a time.
|
|
@@ -181,6 +185,9 @@ export class Tokenizer {
|
|
|
181
185
|
: TokenType.CHARACTER);
|
|
182
186
|
}
|
|
183
187
|
this._extendRunWith(String.fromCodePoint(cp), 0);
|
|
188
|
+
// This callback runs twice for references that decode to two
|
|
189
|
+
// code points; keep `entityStartPos` valid if the chunk was dropped.
|
|
190
|
+
this.entityStartPos = this.preprocessor.pos - consumed + 1;
|
|
184
191
|
}, handler.onParseError
|
|
185
192
|
? {
|
|
186
193
|
missingSemicolonAfterCharacterReference: () => {
|
|
@@ -352,6 +359,33 @@ export class Tokenizer {
|
|
|
352
359
|
this.preprocessor.insertHtmlAtCurrentPos(chunk);
|
|
353
360
|
this._runParsingLoop();
|
|
354
361
|
}
|
|
362
|
+
/** @internal Flush completed streaming text without splitting an unfinished token. */
|
|
363
|
+
flushCurrentCharacterToken() {
|
|
364
|
+
// Only completed text is flushed: an unfinished token stays intact, including a doctype that has no token yet.
|
|
365
|
+
if (this.paused ||
|
|
366
|
+
this.active ||
|
|
367
|
+
!this.currentCharacterToken ||
|
|
368
|
+
this.currentToken ||
|
|
369
|
+
this.state === State.DOCTYPE ||
|
|
370
|
+
this.state === State.BEFORE_DOCTYPE_NAME ||
|
|
371
|
+
!this.preprocessor.willDropParsedChunk()) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
// The last `pending` code units, a delimiter or character reference, are not text yet and stay buffered. They
|
|
375
|
+
// are ASCII, so they hold no preprocessing gap and no line break that the dropped gap history would have to keep.
|
|
376
|
+
const pending = this.preprocessor.droppedBufferSize + this.preprocessor.pos + 1 - this.getCharacterTokenEndOffset();
|
|
377
|
+
// Hibernation left us before the next code point (possibly an incomplete surrogate).
|
|
378
|
+
this.preprocessor.advance();
|
|
379
|
+
this.currentLocation = this.getCurrentLocation(pending);
|
|
380
|
+
this.preprocessor.retreat(1);
|
|
381
|
+
this._emitCurrentCharacterToken(this.currentLocation);
|
|
382
|
+
// Keep a source range for CDATA delimiters or a later tag discarded at EOF. An empty null-character token
|
|
383
|
+
// would become a replacement character in foreign content.
|
|
384
|
+
this._createCharacterToken(TokenType.CHARACTER);
|
|
385
|
+
this.preprocessor.dropParsedChunk(Math.max(pending - 1, 0));
|
|
386
|
+
// Keep `entityStartPos` valid for a pending character reference.
|
|
387
|
+
this.entityStartPos = this.preprocessor.pos + 1 - pending;
|
|
388
|
+
}
|
|
355
389
|
//Hibernation
|
|
356
390
|
_ensureHibernation() {
|
|
357
391
|
if (this.preprocessor.endOfChunkHit) {
|
|
@@ -452,7 +486,26 @@ export class Tokenizer {
|
|
|
452
486
|
var _a;
|
|
453
487
|
var _b;
|
|
454
488
|
const token = this.currentToken;
|
|
455
|
-
|
|
489
|
+
const { name } = this.currentAttr;
|
|
490
|
+
const names = this.currentAttrNames;
|
|
491
|
+
let duplicate;
|
|
492
|
+
if (names === null) {
|
|
493
|
+
duplicate = getTokenAttr(token, name) !== null;
|
|
494
|
+
// Build the name set only once a new (65th) name is accepted, so a tag with 64 names and many
|
|
495
|
+
// duplicates does not build it.
|
|
496
|
+
if (!duplicate && token.attrs.length === ATTR_NAME_SET_THRESHOLD) {
|
|
497
|
+
this.currentAttrNames = new Set(token.attrs.map((attr) => attr.name)).add(name);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
duplicate = names.has(name);
|
|
502
|
+
if (!duplicate)
|
|
503
|
+
names.add(name);
|
|
504
|
+
}
|
|
505
|
+
if (duplicate) {
|
|
506
|
+
this._err(ERR.duplicateAttribute);
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
456
509
|
token.attrs.push(this.currentAttr);
|
|
457
510
|
if (token.location && this.currentAttrLocation) {
|
|
458
511
|
const attrLocations = ((_a = (_b = token.location).attrs) !== null && _a !== void 0 ? _a : (_b.attrs = Object.create(null)));
|
|
@@ -461,9 +514,6 @@ export class Tokenizer {
|
|
|
461
514
|
this._leaveAttrValue();
|
|
462
515
|
}
|
|
463
516
|
}
|
|
464
|
-
else {
|
|
465
|
-
this._err(ERR.duplicateAttribute);
|
|
466
|
-
}
|
|
467
517
|
}
|
|
468
518
|
_leaveAttrValue() {
|
|
469
519
|
if (this.currentAttrLocation) {
|
|
@@ -545,6 +595,7 @@ export class Tokenizer {
|
|
|
545
595
|
}
|
|
546
596
|
emitCurrentTagToken() {
|
|
547
597
|
const ct = this.currentToken;
|
|
598
|
+
this.currentAttrNames = null;
|
|
548
599
|
this.prepareToken(ct);
|
|
549
600
|
ct.tagID = getTagID(ct.tagName);
|
|
550
601
|
if (ct.type === TokenType.START_TAG) {
|
|
@@ -600,13 +651,16 @@ export class Tokenizer {
|
|
|
600
651
|
}
|
|
601
652
|
}
|
|
602
653
|
_emitEOFToken() {
|
|
654
|
+
var _a, _b;
|
|
603
655
|
const location = this.getCurrentLocation(0);
|
|
604
656
|
if (location) {
|
|
605
657
|
location.endLine = location.startLine;
|
|
606
658
|
location.endCol = location.startCol;
|
|
607
659
|
location.endOffset = location.startOffset;
|
|
608
660
|
}
|
|
609
|
-
|
|
661
|
+
// A token that is still open at EOF (eof-in-tag) is dropped. Pending
|
|
662
|
+
// characters end where that token started, rather than at EOF.
|
|
663
|
+
this._emitCurrentCharacterToken((_b = (_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.location) !== null && _b !== void 0 ? _b : location);
|
|
610
664
|
this.handler.onEof({ type: TokenType.EOF, location });
|
|
611
665
|
this.active = false;
|
|
612
666
|
}
|
|
@@ -28,7 +28,11 @@ export declare class Preprocessor {
|
|
|
28
28
|
private _addGap;
|
|
29
29
|
private _processSurrogate;
|
|
30
30
|
willDropParsedChunk(): boolean;
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Drop the parsed input, except for the current code point and the `keep` code units before it. The gap history is
|
|
33
|
+
* reset, so the kept units must contain no preprocessing gap.
|
|
34
|
+
*/
|
|
35
|
+
dropParsedChunk(keep?: number): void;
|
|
32
36
|
write(chunk: string, isLastChunk: boolean): void;
|
|
33
37
|
insertHtmlAtCurrentPos(chunk: string): void;
|
|
34
38
|
startsWith(pattern: string, caseSensitive: boolean): boolean;
|
|
@@ -64,7 +64,7 @@ export class Preprocessor {
|
|
|
64
64
|
//NOTE: try to peek a surrogate pair
|
|
65
65
|
if (this.pos !== this.html.length - 1) {
|
|
66
66
|
const nextCp = this.html.charCodeAt(this.pos + 1);
|
|
67
|
-
if (isSurrogatePair(nextCp)) {
|
|
67
|
+
if (!isSurrogatePair(cp) && isSurrogatePair(nextCp)) {
|
|
68
68
|
//NOTE: we have a surrogate pair. Peek pair character and recalculate code point.
|
|
69
69
|
this.pos++;
|
|
70
70
|
//NOTE: add a gap that should be avoided during retreat
|
|
@@ -84,17 +84,21 @@ export class Preprocessor {
|
|
|
84
84
|
willDropParsedChunk() {
|
|
85
85
|
return this.pos > this.bufferWaterline;
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Drop the parsed input, except for the current code point and the `keep` code units before it. The gap history is
|
|
89
|
+
* reset, so the kept units must contain no preprocessing gap.
|
|
90
|
+
*/
|
|
91
|
+
dropParsedChunk(keep = 0) {
|
|
88
92
|
if (this.willDropParsedChunk()) {
|
|
89
93
|
// Retain the entire current code point, including both units of a surrogate pair.
|
|
90
|
-
const droppedSize = this.offset - this.droppedBufferSize;
|
|
94
|
+
const droppedSize = this.offset - this.droppedBufferSize - keep;
|
|
91
95
|
this.html = this.html.substring(droppedSize);
|
|
92
96
|
this.lineStartPos -= droppedSize;
|
|
93
97
|
this.droppedBufferSize += droppedSize;
|
|
94
98
|
this.pos -= droppedSize;
|
|
95
99
|
this.lastGapPos = -2;
|
|
96
100
|
this.gapStack.length = 0;
|
|
97
|
-
if (this.pos >
|
|
101
|
+
if (this.pos > keep)
|
|
98
102
|
this._addGap();
|
|
99
103
|
}
|
|
100
104
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { DOCUMENT_MODE } from '../common/html.js';
|
|
2
|
+
/** The position of each name in the attribute lists `adoptAttributes` merged into, kept for the next merge into that list. */
|
|
3
|
+
const attrListIndexes = new WeakMap();
|
|
2
4
|
export const defaultTreeAdapter = {
|
|
3
5
|
//Node construction
|
|
4
6
|
createDocument() {
|
|
@@ -44,8 +46,12 @@ export const defaultTreeAdapter = {
|
|
|
44
46
|
newNode.parentNode = parentNode;
|
|
45
47
|
},
|
|
46
48
|
insertBefore(parentNode, newNode, referenceNode) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
+
const { childNodes } = parentNode;
|
|
50
|
+
// The reference is usually the last child (fostered content precedes the table), so check that first.
|
|
51
|
+
const insertionIdx = childNodes[childNodes.length - 1] === referenceNode
|
|
52
|
+
? childNodes.length - 1
|
|
53
|
+
: childNodes.indexOf(referenceNode);
|
|
54
|
+
childNodes.splice(insertionIdx, 0, newNode);
|
|
49
55
|
newNode.parentNode = parentNode;
|
|
50
56
|
},
|
|
51
57
|
setTemplateContent(templateElement, contentElement) {
|
|
@@ -80,8 +86,10 @@ export const defaultTreeAdapter = {
|
|
|
80
86
|
},
|
|
81
87
|
detachNode(node) {
|
|
82
88
|
if (node.parentNode) {
|
|
83
|
-
const
|
|
84
|
-
|
|
89
|
+
const { childNodes } = node.parentNode;
|
|
90
|
+
// The parser detaches from the end when it moves children, so check the last child first.
|
|
91
|
+
const idx = childNodes[childNodes.length - 1] === node ? childNodes.length - 1 : childNodes.indexOf(node);
|
|
92
|
+
childNodes.splice(idx, 1);
|
|
85
93
|
node.parentNode = null;
|
|
86
94
|
}
|
|
87
95
|
},
|
|
@@ -96,7 +104,11 @@ export const defaultTreeAdapter = {
|
|
|
96
104
|
defaultTreeAdapter.appendChild(parentNode, defaultTreeAdapter.createTextNode(text));
|
|
97
105
|
},
|
|
98
106
|
insertTextBefore(parentNode, text, referenceNode) {
|
|
99
|
-
const
|
|
107
|
+
const { childNodes } = parentNode;
|
|
108
|
+
const referenceIdx = childNodes[childNodes.length - 1] === referenceNode
|
|
109
|
+
? childNodes.length - 1
|
|
110
|
+
: childNodes.indexOf(referenceNode);
|
|
111
|
+
const prevNode = childNodes[referenceIdx - 1];
|
|
100
112
|
if (prevNode && defaultTreeAdapter.isTextNode(prevNode)) {
|
|
101
113
|
prevNode.value += text;
|
|
102
114
|
}
|
|
@@ -104,13 +116,45 @@ export const defaultTreeAdapter = {
|
|
|
104
116
|
defaultTreeAdapter.insertBefore(parentNode, defaultTreeAdapter.createTextNode(text), referenceNode);
|
|
105
117
|
}
|
|
106
118
|
},
|
|
119
|
+
/**
|
|
120
|
+
* Keeps the recipient's names between merges as a map from name to position. The map is rebuilt when the list changed
|
|
121
|
+
* length or has a repeated name, or when a merged name it holds no longer names the entry at that position. A name that
|
|
122
|
+
* other code gives an existing entry between two merges is missed otherwise, and merging it appends it a second time.
|
|
123
|
+
*/
|
|
107
124
|
adoptAttributes(recipient, attrs) {
|
|
108
|
-
|
|
125
|
+
// Merging a list into itself adds nothing: every name is already present.
|
|
126
|
+
if (attrs.length === 0 || attrs === recipient.attrs)
|
|
127
|
+
return;
|
|
128
|
+
const cached = attrListIndexes.get(recipient.attrs);
|
|
129
|
+
const isIndexed = (cached === null || cached === void 0 ? void 0 : cached.size) === recipient.attrs.length &&
|
|
130
|
+
attrs.every(({ name }) => {
|
|
131
|
+
var _a;
|
|
132
|
+
const index = cached.get(name);
|
|
133
|
+
return index === undefined || ((_a = recipient.attrs[index]) === null || _a === void 0 ? void 0 : _a.name) === name;
|
|
134
|
+
});
|
|
135
|
+
const recipientAttrsMap = isIndexed ? cached : new Map();
|
|
136
|
+
if (!isIndexed) {
|
|
137
|
+
for (let i = 0; i < recipient.attrs.length; i++) {
|
|
138
|
+
const attr = recipient.attrs[i];
|
|
139
|
+
// A sparse list is indexed again on every merge, as its size never matches its length.
|
|
140
|
+
if (attr !== undefined)
|
|
141
|
+
recipientAttrsMap.set(attr.name, i);
|
|
142
|
+
}
|
|
143
|
+
attrListIndexes.set(recipient.attrs, recipientAttrsMap);
|
|
144
|
+
}
|
|
145
|
+
const adoptedFrom = recipient.attrs.length;
|
|
109
146
|
for (let j = 0; j < attrs.length; j++) {
|
|
110
147
|
if (!recipientAttrsMap.has(attrs[j].name)) {
|
|
111
148
|
recipient.attrs.push(attrs[j]);
|
|
112
149
|
}
|
|
113
150
|
}
|
|
151
|
+
for (let j = adoptedFrom; j < recipient.attrs.length; j++) {
|
|
152
|
+
recipientAttrsMap.set(recipient.attrs[j].name, j);
|
|
153
|
+
}
|
|
154
|
+
// A list with a repeated name is indexed again on the next merge.
|
|
155
|
+
if (recipientAttrsMap.size !== recipient.attrs.length) {
|
|
156
|
+
attrListIndexes.delete(recipient.attrs);
|
|
157
|
+
}
|
|
114
158
|
},
|
|
115
159
|
//Tree traversing
|
|
116
160
|
getFirstChild(node) {
|
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.1.
|
|
5
|
+
"version": "8.1.1",
|
|
6
6
|
"author": "Ivan Nikulin <ifaaan@gmail.com> (https://github.com/inikulin)",
|
|
7
7
|
"contributors": [
|
|
8
8
|
"James Garbutt (https://github.com/43081j)",
|