parse5 8.0.1 → 8.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/common/foreign-content.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/parser/formatting-element-list.d.ts +38 -10
- package/dist/parser/formatting-element-list.js +153 -70
- package/dist/parser/index.d.ts +18 -9
- package/dist/parser/index.js +215 -152
- package/dist/parser/open-element-stack.d.ts +85 -8
- package/dist/parser/open-element-stack.js +500 -150
- package/dist/serializer/index.js +48 -34
- package/dist/tokenizer/index.d.ts +37 -7
- package/dist/tokenizer/index.js +321 -71
- package/dist/tokenizer/preprocessor.d.ts +8 -1
- package/dist/tokenizer/preprocessor.js +20 -7
- package/dist/tree-adapters/default.js +50 -6
- package/dist/tree-adapters/interface.d.ts +17 -0
- package/package.json +7 -3
package/dist/parser/index.js
CHANGED
|
@@ -9,12 +9,32 @@ import * as unicode from '../common/unicode.js';
|
|
|
9
9
|
import { TAG_ID as $, TAG_NAMES as TN, NS, ATTRS, SPECIAL_ELEMENTS, DOCUMENT_MODE, NUMBERED_HEADERS, getTagID, } from '../common/html.js';
|
|
10
10
|
import { TokenType, getTokenAttr, } from '../common/token.js';
|
|
11
11
|
//Misc constants
|
|
12
|
+
// Elements that can select an insertion mode when it is reset.
|
|
13
|
+
const INSERTION_MODE_TAGS = [
|
|
14
|
+
$.TR,
|
|
15
|
+
$.TBODY,
|
|
16
|
+
$.THEAD,
|
|
17
|
+
$.TFOOT,
|
|
18
|
+
$.CAPTION,
|
|
19
|
+
$.COLGROUP,
|
|
20
|
+
$.TABLE,
|
|
21
|
+
$.FRAMESET,
|
|
22
|
+
$.SELECT,
|
|
23
|
+
$.TEMPLATE,
|
|
24
|
+
$.TD,
|
|
25
|
+
$.TH,
|
|
26
|
+
$.HEAD,
|
|
27
|
+
$.BODY,
|
|
28
|
+
$.HTML,
|
|
29
|
+
];
|
|
12
30
|
const HIDDEN_INPUT_TYPE = 'hidden';
|
|
31
|
+
// For the integration-point check of elements whose attributes do not matter; never attached to a node.
|
|
32
|
+
const EMPTY_ATTRS = [];
|
|
13
33
|
//Adoption agency loops iteration count
|
|
14
34
|
const AA_OUTER_LOOP_ITER = 8;
|
|
15
35
|
const AA_INNER_LOOP_ITER = 3;
|
|
16
36
|
//Insertion modes
|
|
17
|
-
var InsertionMode;
|
|
37
|
+
export var InsertionMode;
|
|
18
38
|
(function (InsertionMode) {
|
|
19
39
|
InsertionMode[InsertionMode["INITIAL"] = 0] = "INITIAL";
|
|
20
40
|
InsertionMode[InsertionMode["BEFORE_HTML"] = 1] = "BEFORE_HTML";
|
|
@@ -77,8 +97,7 @@ export class Parser {
|
|
|
77
97
|
/** Indicates that the current node is not an element in the HTML namespace */
|
|
78
98
|
this.currentNotInHTML = false;
|
|
79
99
|
/**
|
|
80
|
-
* The template insertion mode
|
|
81
|
-
* Ie. the topmost element will always have index 0.
|
|
100
|
+
* The topmost template insertion mode is the last element.
|
|
82
101
|
*
|
|
83
102
|
* @internal
|
|
84
103
|
*/
|
|
@@ -105,7 +124,7 @@ export class Parser {
|
|
|
105
124
|
}
|
|
106
125
|
this.document = document !== null && document !== void 0 ? document : this.treeAdapter.createDocument();
|
|
107
126
|
this.tokenizer = new Tokenizer(this.options, this);
|
|
108
|
-
this.activeFormattingElements = new FormattingElementList(
|
|
127
|
+
this.activeFormattingElements = new FormattingElementList();
|
|
109
128
|
this.fragmentContextID = fragmentContext ? getTagID(this.treeAdapter.getTagName(fragmentContext)) : $.UNKNOWN;
|
|
110
129
|
this._setContextModes(fragmentContext !== null && fragmentContext !== void 0 ? fragmentContext : this.document, this.fragmentContextID);
|
|
111
130
|
this.openElements = new OpenElementStack(this.document, this.treeAdapter, this);
|
|
@@ -130,7 +149,7 @@ export class Parser {
|
|
|
130
149
|
const documentMock = opts.treeAdapter.createElement('documentmock', NS.HTML, []);
|
|
131
150
|
const parser = new this(opts, documentMock, fragmentContext);
|
|
132
151
|
if (parser.fragmentContextID === $.TEMPLATE) {
|
|
133
|
-
parser.tmplInsertionModeStack.
|
|
152
|
+
parser.tmplInsertionModeStack.push(InsertionMode.IN_TEMPLATE);
|
|
134
153
|
}
|
|
135
154
|
parser._initTokenizerForFragmentParsing();
|
|
136
155
|
parser._insertFakeRootElement();
|
|
@@ -172,11 +191,12 @@ export class Parser {
|
|
|
172
191
|
}
|
|
173
192
|
/** @internal */
|
|
174
193
|
onItemPop(node, isTop) {
|
|
175
|
-
var _a, _b;
|
|
194
|
+
var _a, _b, _c;
|
|
176
195
|
if (this.options.sourceCodeLocationInfo) {
|
|
177
196
|
this._setEndLocation(node, this.currentToken);
|
|
178
197
|
}
|
|
179
198
|
(_b = (_a = this.treeAdapter).onItemPop) === null || _b === void 0 ? void 0 : _b.call(_a, node, this.openElements.current);
|
|
199
|
+
(_c = this.annotationXmlIntegrationPoints) === null || _c === void 0 ? void 0 : _c.delete(node);
|
|
180
200
|
if (isTop) {
|
|
181
201
|
let current;
|
|
182
202
|
let currentTagId;
|
|
@@ -219,7 +239,7 @@ export class Parser {
|
|
|
219
239
|
_findFormInFragmentContext() {
|
|
220
240
|
let node = this.fragmentContext;
|
|
221
241
|
while (node) {
|
|
222
|
-
if (this.treeAdapter.getTagName(node) === TN.FORM) {
|
|
242
|
+
if (this.treeAdapter.getTagName(node) === TN.FORM && this.treeAdapter.getNamespaceURI(node) === NS.HTML) {
|
|
223
243
|
this.formElement = node;
|
|
224
244
|
break;
|
|
225
245
|
}
|
|
@@ -273,7 +293,7 @@ export class Parser {
|
|
|
273
293
|
}
|
|
274
294
|
}
|
|
275
295
|
/** @protected */
|
|
276
|
-
_attachElementToTree(element, location) {
|
|
296
|
+
_attachElementToTree(element, location, insertionLocation) {
|
|
277
297
|
if (this.options.sourceCodeLocationInfo) {
|
|
278
298
|
const loc = location && {
|
|
279
299
|
...location,
|
|
@@ -281,13 +301,34 @@ export class Parser {
|
|
|
281
301
|
};
|
|
282
302
|
this.treeAdapter.setNodeSourceCodeLocation(element, loc);
|
|
283
303
|
}
|
|
284
|
-
if (
|
|
285
|
-
this.
|
|
304
|
+
if (insertionLocation.beforeElement) {
|
|
305
|
+
this.treeAdapter.insertBefore(insertionLocation.parent, element, insertionLocation.beforeElement);
|
|
286
306
|
}
|
|
287
307
|
else {
|
|
288
|
-
|
|
289
|
-
|
|
308
|
+
this.treeAdapter.appendChild(insertionLocation.parent, element);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** @protected */
|
|
312
|
+
_getInsertionLocation() {
|
|
313
|
+
var _a;
|
|
314
|
+
if (this._shouldFosterParentOnInsertion()) {
|
|
315
|
+
return this._findFosterParentingLocation();
|
|
290
316
|
}
|
|
317
|
+
return {
|
|
318
|
+
parent: (_a = this.openElements.currentTmplContentOrNode) !== null && _a !== void 0 ? _a : this.document,
|
|
319
|
+
beforeElement: null,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/** @protected */
|
|
323
|
+
_createElement(tagName, namespaceURI, attrs, intendedParent) {
|
|
324
|
+
var _a, _b;
|
|
325
|
+
const element = this.treeAdapter.createElement(tagName, namespaceURI, attrs);
|
|
326
|
+
(_b = (_a = this.treeAdapter).onElementCreated) === null || _b === void 0 ? void 0 : _b.call(_a, element, {
|
|
327
|
+
intendedParent,
|
|
328
|
+
formElement: this.formElement,
|
|
329
|
+
isInTemplate: this.openElements.tmplCount > 0,
|
|
330
|
+
});
|
|
331
|
+
return element;
|
|
291
332
|
}
|
|
292
333
|
/**
|
|
293
334
|
* For self-closing tags. Add an element to the tree, but skip adding it
|
|
@@ -295,37 +336,41 @@ export class Parser {
|
|
|
295
336
|
*/
|
|
296
337
|
/** @protected */
|
|
297
338
|
_appendElement(token, namespaceURI) {
|
|
298
|
-
const
|
|
299
|
-
this.
|
|
339
|
+
const insertionLocation = this._getInsertionLocation();
|
|
340
|
+
const element = this._createElement(token.tagName, namespaceURI, token.attrs, insertionLocation.parent);
|
|
341
|
+
this._attachElementToTree(element, token.location, insertionLocation);
|
|
300
342
|
}
|
|
301
343
|
/** @protected */
|
|
302
344
|
_insertElement(token, namespaceURI) {
|
|
303
|
-
const
|
|
304
|
-
this.
|
|
345
|
+
const insertionLocation = this._getInsertionLocation();
|
|
346
|
+
const element = this._createElement(token.tagName, namespaceURI, token.attrs, insertionLocation.parent);
|
|
347
|
+
this._attachElementToTree(element, token.location, insertionLocation);
|
|
305
348
|
this.openElements.push(element, token.tagID);
|
|
306
349
|
}
|
|
307
350
|
/** @protected */
|
|
308
351
|
_insertFakeElement(tagName, tagID) {
|
|
309
|
-
const
|
|
310
|
-
this.
|
|
352
|
+
const insertionLocation = this._getInsertionLocation();
|
|
353
|
+
const element = this._createElement(tagName, NS.HTML, [], insertionLocation.parent);
|
|
354
|
+
this._attachElementToTree(element, null, insertionLocation);
|
|
311
355
|
this.openElements.push(element, tagID);
|
|
312
356
|
}
|
|
313
357
|
/** @protected */
|
|
314
358
|
_insertTemplate(token) {
|
|
315
|
-
const
|
|
359
|
+
const insertionLocation = this._getInsertionLocation();
|
|
360
|
+
const tmpl = this._createElement(token.tagName, NS.HTML, token.attrs, insertionLocation.parent);
|
|
316
361
|
const content = this.treeAdapter.createDocumentFragment();
|
|
317
362
|
this.treeAdapter.setTemplateContent(tmpl, content);
|
|
318
|
-
this._attachElementToTree(tmpl, token.location);
|
|
363
|
+
this._attachElementToTree(tmpl, token.location, insertionLocation);
|
|
319
364
|
this.openElements.push(tmpl, token.tagID);
|
|
320
365
|
if (this.options.sourceCodeLocationInfo)
|
|
321
366
|
this.treeAdapter.setNodeSourceCodeLocation(content, null);
|
|
322
367
|
}
|
|
323
368
|
/** @protected */
|
|
324
369
|
_insertFakeRootElement() {
|
|
325
|
-
const element = this.
|
|
370
|
+
const element = this._createElement(TN.HTML, NS.HTML, [], this.document);
|
|
326
371
|
if (this.options.sourceCodeLocationInfo)
|
|
327
372
|
this.treeAdapter.setNodeSourceCodeLocation(element, null);
|
|
328
|
-
this.treeAdapter.appendChild(this.
|
|
373
|
+
this.treeAdapter.appendChild(this.document, element);
|
|
329
374
|
this.openElements.push(element, $.HTML);
|
|
330
375
|
}
|
|
331
376
|
/** @protected */
|
|
@@ -370,8 +415,12 @@ export class Parser {
|
|
|
370
415
|
}
|
|
371
416
|
/** @protected */
|
|
372
417
|
_adoptNodes(donor, recipient) {
|
|
373
|
-
|
|
374
|
-
|
|
418
|
+
// Detaching from the end keeps an array-backed child list from shifting once per child.
|
|
419
|
+
const children = [...this.treeAdapter.getChildNodes(donor)];
|
|
420
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
421
|
+
this.treeAdapter.detachNode(children[i]);
|
|
422
|
+
}
|
|
423
|
+
for (const child of children) {
|
|
375
424
|
this.treeAdapter.appendChild(recipient, child);
|
|
376
425
|
}
|
|
377
426
|
}
|
|
@@ -463,25 +512,31 @@ export class Parser {
|
|
|
463
512
|
}
|
|
464
513
|
}
|
|
465
514
|
}
|
|
466
|
-
//Integration points
|
|
467
515
|
/** @protected */
|
|
468
516
|
_isIntegrationPoint(tid, element, foreignNS) {
|
|
517
|
+
var _a;
|
|
469
518
|
const ns = this.treeAdapter.getNamespaceURI(element);
|
|
470
|
-
|
|
471
|
-
|
|
519
|
+
if (tid === $.ANNOTATION_XML && ns === NS.MATHML && foreignNS !== NS.MATHML) {
|
|
520
|
+
const verdicts = ((_a = this.annotationXmlIntegrationPoints) !== null && _a !== void 0 ? _a : (this.annotationXmlIntegrationPoints = new Map()));
|
|
521
|
+
let isIntegrationPoint = verdicts.get(element);
|
|
522
|
+
if (isIntegrationPoint === undefined) {
|
|
523
|
+
isIntegrationPoint = foreignContent.isIntegrationPoint(tid, ns, this.treeAdapter.getAttrList(element));
|
|
524
|
+
verdicts.set(element, isIntegrationPoint);
|
|
525
|
+
}
|
|
526
|
+
return isIntegrationPoint;
|
|
527
|
+
}
|
|
528
|
+
return foreignContent.isIntegrationPoint(tid, ns, EMPTY_ATTRS, foreignNS);
|
|
472
529
|
}
|
|
473
530
|
//Active formatting elements reconstruction
|
|
474
531
|
/** @protected */
|
|
475
532
|
_reconstructActiveFormattingElements() {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
entry.element = this.openElements.current;
|
|
484
|
-
}
|
|
533
|
+
let oldest = null;
|
|
534
|
+
for (let entry = this.activeFormattingElements.newest; entry !== null && entry.type === EntryType.Element && !this.openElements.contains(entry.element); entry = entry.older) {
|
|
535
|
+
oldest = entry;
|
|
536
|
+
}
|
|
537
|
+
for (let entry = oldest; entry !== null && entry.type === EntryType.Element; entry = entry.newer) {
|
|
538
|
+
this._insertElement(entry.token, this.treeAdapter.getNamespaceURI(entry.element));
|
|
539
|
+
this.activeFormattingElements.replaceElement(entry, this.openElements.current);
|
|
485
540
|
}
|
|
486
541
|
}
|
|
487
542
|
//Close elements
|
|
@@ -500,9 +555,14 @@ export class Parser {
|
|
|
500
555
|
//Insertion modes
|
|
501
556
|
/** @protected */
|
|
502
557
|
_resetInsertionMode() {
|
|
503
|
-
|
|
558
|
+
// The topmost open HTML element that selects a mode; the fragment context stands in for the root.
|
|
559
|
+
const i = this.openElements.indexOfTopmostOf(INSERTION_MODE_TAGS);
|
|
560
|
+
const tn = i === 0 && this.fragmentContext ? this.fragmentContextID : this.openElements.tagIDs[i];
|
|
561
|
+
const node = i === 0 && this.fragmentContext ? this.fragmentContext : this.openElements.items[i];
|
|
562
|
+
// Only HTML elements select an insertion mode.
|
|
563
|
+
if (node !== undefined && this.treeAdapter.getNamespaceURI(node) === NS.HTML) {
|
|
504
564
|
//Insertion mode reset map
|
|
505
|
-
switch (
|
|
565
|
+
switch (tn) {
|
|
506
566
|
case $.TR: {
|
|
507
567
|
this.insertionMode = InsertionMode.IN_ROW;
|
|
508
568
|
return;
|
|
@@ -538,7 +598,7 @@ export class Parser {
|
|
|
538
598
|
return;
|
|
539
599
|
}
|
|
540
600
|
case $.TEMPLATE: {
|
|
541
|
-
this.insertionMode = this.tmplInsertionModeStack[
|
|
601
|
+
this.insertionMode = this.tmplInsertionModeStack[this.tmplInsertionModeStack.length - 1];
|
|
542
602
|
return;
|
|
543
603
|
}
|
|
544
604
|
case $.HTML: {
|
|
@@ -566,16 +626,12 @@ export class Parser {
|
|
|
566
626
|
}
|
|
567
627
|
/** @protected */
|
|
568
628
|
_resetInsertionModeForSelect(selectIdx) {
|
|
629
|
+
// The select is the topmost element that selects a mode, so open tables and templates are below it.
|
|
569
630
|
if (selectIdx > 0) {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
else if (tn === $.TABLE) {
|
|
576
|
-
this.insertionMode = InsertionMode.IN_SELECT_IN_TABLE;
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
631
|
+
const tableIdx = this.openElements.indexOfTopmost($.TABLE);
|
|
632
|
+
if (tableIdx > 0 && tableIdx > this.openElements.indexOfTopmost($.TEMPLATE)) {
|
|
633
|
+
this.insertionMode = InsertionMode.IN_SELECT_IN_TABLE;
|
|
634
|
+
return;
|
|
579
635
|
}
|
|
580
636
|
}
|
|
581
637
|
this.insertionMode = InsertionMode.IN_SELECT;
|
|
@@ -593,27 +649,24 @@ export class Parser {
|
|
|
593
649
|
}
|
|
594
650
|
/** @protected */
|
|
595
651
|
_findFosterParentingLocation() {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
return { parent: this.openElements.items[i - 1], beforeElement: null };
|
|
611
|
-
}
|
|
612
|
-
default:
|
|
613
|
-
// Do nothing
|
|
652
|
+
const { openElements } = this;
|
|
653
|
+
const tableIdx = openElements.indexOfTopmost($.TABLE);
|
|
654
|
+
const templateIdx = openElements.indexOfTopmost($.TEMPLATE);
|
|
655
|
+
if (templateIdx > tableIdx) {
|
|
656
|
+
return {
|
|
657
|
+
parent: this.treeAdapter.getTemplateContent(openElements.items[templateIdx]),
|
|
658
|
+
beforeElement: null,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
if (tableIdx !== -1) {
|
|
662
|
+
const table = openElements.items[tableIdx];
|
|
663
|
+
const parent = this.treeAdapter.getParentNode(table);
|
|
664
|
+
if (parent) {
|
|
665
|
+
return { parent, beforeElement: table };
|
|
614
666
|
}
|
|
667
|
+
return { parent: openElements.items[openElements.slotBelow(tableIdx)], beforeElement: null };
|
|
615
668
|
}
|
|
616
|
-
return { parent:
|
|
669
|
+
return { parent: openElements.items[0], beforeElement: null };
|
|
617
670
|
}
|
|
618
671
|
/** @protected */
|
|
619
672
|
_fosterParentElement(element) {
|
|
@@ -1185,7 +1238,7 @@ function aaObtainFormattingElementEntry(p, token) {
|
|
|
1185
1238
|
p.activeFormattingElements.removeEntry(formattingElementEntry);
|
|
1186
1239
|
formattingElementEntry = null;
|
|
1187
1240
|
}
|
|
1188
|
-
else if (!p.openElements.hasInScope(token.tagID)) {
|
|
1241
|
+
else if (!p.openElements.hasInScope(token.tagID, formattingElementEntry.element)) {
|
|
1189
1242
|
formattingElementEntry = null;
|
|
1190
1243
|
}
|
|
1191
1244
|
}
|
|
@@ -1196,30 +1249,28 @@ function aaObtainFormattingElementEntry(p, token) {
|
|
|
1196
1249
|
}
|
|
1197
1250
|
//Steps 9 and 10 of the algorithm
|
|
1198
1251
|
function aaObtainFurthestBlock(p, formattingElementEntry) {
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
if (
|
|
1204
|
-
|
|
1205
|
-
}
|
|
1206
|
-
if (p._isSpecialElement(element, p.openElements.tagIDs[idx])) {
|
|
1207
|
-
furthestBlock = element;
|
|
1252
|
+
const { openElements } = p;
|
|
1253
|
+
const formattingIdx = openElements._indexOf(formattingElementEntry.element);
|
|
1254
|
+
// The furthest block is the lowest special element above the formatting element.
|
|
1255
|
+
for (let idx = openElements.slotAbove(formattingIdx); idx !== -1; idx = openElements.slotAbove(idx)) {
|
|
1256
|
+
if (p._isSpecialElement(openElements.items[idx], openElements.tagIDs[idx])) {
|
|
1257
|
+
return idx;
|
|
1208
1258
|
}
|
|
1209
1259
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
}
|
|
1214
|
-
return furthestBlock;
|
|
1260
|
+
openElements.shortenToLength(formattingIdx);
|
|
1261
|
+
p.activeFormattingElements.removeEntry(formattingElementEntry);
|
|
1262
|
+
return -1;
|
|
1215
1263
|
}
|
|
1216
1264
|
//Step 13 of the algorithm
|
|
1217
|
-
function aaInnerLoop(p,
|
|
1265
|
+
function aaInnerLoop(p, furthestBlockIdx, formattingElement) {
|
|
1266
|
+
const furthestBlock = p.openElements.items[furthestBlockIdx];
|
|
1218
1267
|
let lastElement = furthestBlock;
|
|
1219
|
-
let
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1268
|
+
let nextIdx = p.openElements.slotBelow(furthestBlockIdx);
|
|
1269
|
+
const intendedParent = p.openElements.getCommonAncestor(formattingElement);
|
|
1270
|
+
for (let i = 0, idx = nextIdx; p.openElements.items[idx] !== formattingElement; i++, idx = nextIdx) {
|
|
1271
|
+
let element = p.openElements.items[idx];
|
|
1272
|
+
// Store the next element for the next loop iteration (this one may be removed from the stack by step 9.5).
|
|
1273
|
+
nextIdx = p.openElements.slotBelow(idx);
|
|
1223
1274
|
const elementEntry = p.activeFormattingElements.getElementEntry(element);
|
|
1224
1275
|
const counterOverflow = elementEntry && i >= AA_INNER_LOOP_ITER;
|
|
1225
1276
|
const shouldRemoveFromOpenElements = !elementEntry || counterOverflow;
|
|
@@ -1227,10 +1278,10 @@ function aaInnerLoop(p, furthestBlock, formattingElement) {
|
|
|
1227
1278
|
if (counterOverflow) {
|
|
1228
1279
|
p.activeFormattingElements.removeEntry(elementEntry);
|
|
1229
1280
|
}
|
|
1230
|
-
p.openElements.
|
|
1281
|
+
p.openElements.removeAt(idx);
|
|
1231
1282
|
}
|
|
1232
1283
|
else {
|
|
1233
|
-
element = aaRecreateElementFromEntry(p, elementEntry);
|
|
1284
|
+
element = aaRecreateElementFromEntry(p, elementEntry, intendedParent);
|
|
1234
1285
|
if (lastElement === furthestBlock) {
|
|
1235
1286
|
p.activeFormattingElements.bookmark = elementEntry;
|
|
1236
1287
|
}
|
|
@@ -1242,11 +1293,11 @@ function aaInnerLoop(p, furthestBlock, formattingElement) {
|
|
|
1242
1293
|
return lastElement;
|
|
1243
1294
|
}
|
|
1244
1295
|
//Step 13.7 of the algorithm
|
|
1245
|
-
function aaRecreateElementFromEntry(p, elementEntry) {
|
|
1296
|
+
function aaRecreateElementFromEntry(p, elementEntry, intendedParent) {
|
|
1246
1297
|
const ns = p.treeAdapter.getNamespaceURI(elementEntry.element);
|
|
1247
|
-
const newElement = p.
|
|
1298
|
+
const newElement = p._createElement(elementEntry.token.tagName, ns, elementEntry.token.attrs, intendedParent);
|
|
1248
1299
|
p.openElements.replace(elementEntry.element, newElement);
|
|
1249
|
-
elementEntry
|
|
1300
|
+
p.activeFormattingElements.replaceElement(elementEntry, newElement);
|
|
1250
1301
|
return newElement;
|
|
1251
1302
|
}
|
|
1252
1303
|
//Step 14 of the algorithm
|
|
@@ -1268,27 +1319,41 @@ function aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement) {
|
|
|
1268
1319
|
function aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry) {
|
|
1269
1320
|
const ns = p.treeAdapter.getNamespaceURI(formattingElementEntry.element);
|
|
1270
1321
|
const { token } = formattingElementEntry;
|
|
1271
|
-
const newElement = p.
|
|
1322
|
+
const newElement = p._createElement(token.tagName, ns, token.attrs, furthestBlock);
|
|
1272
1323
|
p._adoptNodes(furthestBlock, newElement);
|
|
1273
1324
|
p.treeAdapter.appendChild(furthestBlock, newElement);
|
|
1274
1325
|
p.activeFormattingElements.insertElementAfterBookmark(newElement, token);
|
|
1275
1326
|
p.activeFormattingElements.removeEntry(formattingElementEntry);
|
|
1276
|
-
|
|
1277
|
-
p.openElements.
|
|
1327
|
+
// Only the elements the inner loop kept are open between the formatting element and the furthest block.
|
|
1328
|
+
const formattingIdx = p.openElements._indexOf(formattingElementEntry.element);
|
|
1329
|
+
let furthestBlockIdx = p.openElements.slotAbove(formattingIdx);
|
|
1330
|
+
while (p.openElements.items[furthestBlockIdx] !== furthestBlock) {
|
|
1331
|
+
furthestBlockIdx = p.openElements.slotAbove(furthestBlockIdx);
|
|
1332
|
+
}
|
|
1333
|
+
p.openElements.replaceFormattingElement(formattingIdx, furthestBlockIdx, newElement, token.tagID);
|
|
1278
1334
|
}
|
|
1279
1335
|
//Algorithm entry point
|
|
1280
1336
|
function callAdoptionAgency(p, token) {
|
|
1337
|
+
const { current, currentTagId } = p.openElements;
|
|
1338
|
+
//Step 2 of the algorithm
|
|
1339
|
+
if (currentTagId === token.tagID &&
|
|
1340
|
+
p.treeAdapter.getNamespaceURI(current) === NS.HTML &&
|
|
1341
|
+
!p.activeFormattingElements.getElementEntry(current)) {
|
|
1342
|
+
p.openElements.pop();
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1281
1345
|
for (let i = 0; i < AA_OUTER_LOOP_ITER; i++) {
|
|
1282
1346
|
const formattingElementEntry = aaObtainFormattingElementEntry(p, token);
|
|
1283
1347
|
if (!formattingElementEntry) {
|
|
1284
1348
|
break;
|
|
1285
1349
|
}
|
|
1286
|
-
const
|
|
1287
|
-
if (
|
|
1350
|
+
const furthestBlockIdx = aaObtainFurthestBlock(p, formattingElementEntry);
|
|
1351
|
+
if (furthestBlockIdx === -1) {
|
|
1288
1352
|
break;
|
|
1289
1353
|
}
|
|
1354
|
+
const furthestBlock = p.openElements.items[furthestBlockIdx];
|
|
1290
1355
|
p.activeFormattingElements.bookmark = formattingElementEntry;
|
|
1291
|
-
const lastElement = aaInnerLoop(p,
|
|
1356
|
+
const lastElement = aaInnerLoop(p, furthestBlockIdx, formattingElementEntry.element);
|
|
1292
1357
|
const commonAncestor = p.openElements.getCommonAncestor(formattingElementEntry.element);
|
|
1293
1358
|
p.treeAdapter.detachNode(lastElement);
|
|
1294
1359
|
if (commonAncestor)
|
|
@@ -1314,7 +1379,7 @@ function stopParsing(p, token) {
|
|
|
1314
1379
|
// NOTE: If we are not in a fragment, `html` and `body` will stay on the stack.
|
|
1315
1380
|
// This is a problem, as we might overwrite their end position here.
|
|
1316
1381
|
const target = p.fragmentContext ? 0 : 2;
|
|
1317
|
-
for (let i = p.openElements.stackTop; i >= target; i
|
|
1382
|
+
for (let i = p.openElements.stackTop; i >= target; i = p.openElements.slotBelow(i)) {
|
|
1318
1383
|
p._setEndLocation(p.openElements.items[i], token);
|
|
1319
1384
|
}
|
|
1320
1385
|
// Handle `html` and `body`
|
|
@@ -1452,7 +1517,7 @@ function startTagInHead(p, token) {
|
|
|
1452
1517
|
p.activeFormattingElements.insertMarker();
|
|
1453
1518
|
p.framesetOk = false;
|
|
1454
1519
|
p.insertionMode = InsertionMode.IN_TEMPLATE;
|
|
1455
|
-
p.tmplInsertionModeStack.
|
|
1520
|
+
p.tmplInsertionModeStack.push(InsertionMode.IN_TEMPLATE);
|
|
1456
1521
|
break;
|
|
1457
1522
|
}
|
|
1458
1523
|
case $.HEAD: {
|
|
@@ -1494,7 +1559,7 @@ function templateEndTagInHead(p, token) {
|
|
|
1494
1559
|
}
|
|
1495
1560
|
p.openElements.popUntilTagNamePopped($.TEMPLATE);
|
|
1496
1561
|
p.activeFormattingElements.clearToLastMarker();
|
|
1497
|
-
p.tmplInsertionModeStack.
|
|
1562
|
+
p.tmplInsertionModeStack.pop();
|
|
1498
1563
|
p._resetInsertionMode();
|
|
1499
1564
|
}
|
|
1500
1565
|
else {
|
|
@@ -1723,21 +1788,15 @@ function formStartTagInBody(p, token) {
|
|
|
1723
1788
|
}
|
|
1724
1789
|
function listItemStartTagInBody(p, token) {
|
|
1725
1790
|
p.framesetOk = false;
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
if (elementId !== $.ADDRESS &&
|
|
1736
|
-
elementId !== $.DIV &&
|
|
1737
|
-
elementId !== $.P &&
|
|
1738
|
-
p._isSpecialElement(p.openElements.items[i], elementId)) {
|
|
1739
|
-
break;
|
|
1740
|
-
}
|
|
1791
|
+
// The topmost open item of the token's kind closes, unless a special element other than address, div and p is
|
|
1792
|
+
// above it.
|
|
1793
|
+
const idx = token.tagID === $.LI
|
|
1794
|
+
? p.openElements.indexOfTopmost($.LI)
|
|
1795
|
+
: Math.max(p.openElements.indexOfTopmost($.DD), p.openElements.indexOfTopmost($.DT));
|
|
1796
|
+
if (idx !== -1 && !p.openElements.hasListItemBoundaryAbove(idx)) {
|
|
1797
|
+
const elementId = p.openElements.tagIDs[idx];
|
|
1798
|
+
p.openElements.generateImpliedEndTagsWithExclusion(elementId);
|
|
1799
|
+
p.openElements.popUntilTagNamePopped(elementId);
|
|
1741
1800
|
}
|
|
1742
1801
|
if (p.openElements.hasInButtonScope($.P)) {
|
|
1743
1802
|
p._closePElement();
|
|
@@ -2221,21 +2280,16 @@ function brEndTagInBody(p) {
|
|
|
2221
2280
|
p.framesetOk = false;
|
|
2222
2281
|
}
|
|
2223
2282
|
function genericEndTagInBody(p, token) {
|
|
2224
|
-
const tn = token.tagName;
|
|
2225
2283
|
const tid = token.tagID;
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
}
|
|
2236
|
-
if (p._isSpecialElement(element, elementId)) {
|
|
2237
|
-
break;
|
|
2238
|
-
}
|
|
2284
|
+
// The topmost open HTML element with the token's name closes, unless a special element is above it. A tag without
|
|
2285
|
+
// an ID is matched by name.
|
|
2286
|
+
const i = tid === $.UNKNOWN
|
|
2287
|
+
? p.openElements.indexOfTopmostByName(NS.HTML, token.tagName)
|
|
2288
|
+
: p.openElements.indexOfTopmost(tid);
|
|
2289
|
+
if (i > 0 && !p.openElements.hasSpecialElementAbove(i)) {
|
|
2290
|
+
p.openElements.generateImpliedEndTagsWithExclusion(tid);
|
|
2291
|
+
if (p.openElements.stackTop >= i)
|
|
2292
|
+
p.openElements.shortenToLength(i);
|
|
2239
2293
|
}
|
|
2240
2294
|
}
|
|
2241
2295
|
function endTagInBody(p, token) {
|
|
@@ -2920,7 +2974,7 @@ function endTagInSelect(p, token) {
|
|
|
2920
2974
|
case $.OPTGROUP: {
|
|
2921
2975
|
if (p.openElements.stackTop > 0 &&
|
|
2922
2976
|
p.openElements.currentTagId === $.OPTION &&
|
|
2923
|
-
p.openElements.tagIDs[p.openElements.stackTop
|
|
2977
|
+
p.openElements.tagIDs[p.openElements.slotBelow(p.openElements.stackTop)] === $.OPTGROUP) {
|
|
2924
2978
|
p.openElements.pop();
|
|
2925
2979
|
}
|
|
2926
2980
|
if (p.openElements.currentTagId === $.OPTGROUP) {
|
|
@@ -3013,32 +3067,32 @@ function startTagInTemplate(p, token) {
|
|
|
3013
3067
|
case $.TBODY:
|
|
3014
3068
|
case $.TFOOT:
|
|
3015
3069
|
case $.THEAD: {
|
|
3016
|
-
p.tmplInsertionModeStack[
|
|
3070
|
+
p.tmplInsertionModeStack[p.tmplInsertionModeStack.length - 1] = InsertionMode.IN_TABLE;
|
|
3017
3071
|
p.insertionMode = InsertionMode.IN_TABLE;
|
|
3018
3072
|
startTagInTable(p, token);
|
|
3019
3073
|
break;
|
|
3020
3074
|
}
|
|
3021
3075
|
case $.COL: {
|
|
3022
|
-
p.tmplInsertionModeStack[
|
|
3076
|
+
p.tmplInsertionModeStack[p.tmplInsertionModeStack.length - 1] = InsertionMode.IN_COLUMN_GROUP;
|
|
3023
3077
|
p.insertionMode = InsertionMode.IN_COLUMN_GROUP;
|
|
3024
3078
|
startTagInColumnGroup(p, token);
|
|
3025
3079
|
break;
|
|
3026
3080
|
}
|
|
3027
3081
|
case $.TR: {
|
|
3028
|
-
p.tmplInsertionModeStack[
|
|
3082
|
+
p.tmplInsertionModeStack[p.tmplInsertionModeStack.length - 1] = InsertionMode.IN_TABLE_BODY;
|
|
3029
3083
|
p.insertionMode = InsertionMode.IN_TABLE_BODY;
|
|
3030
3084
|
startTagInTableBody(p, token);
|
|
3031
3085
|
break;
|
|
3032
3086
|
}
|
|
3033
3087
|
case $.TD:
|
|
3034
3088
|
case $.TH: {
|
|
3035
|
-
p.tmplInsertionModeStack[
|
|
3089
|
+
p.tmplInsertionModeStack[p.tmplInsertionModeStack.length - 1] = InsertionMode.IN_ROW;
|
|
3036
3090
|
p.insertionMode = InsertionMode.IN_ROW;
|
|
3037
3091
|
startTagInRow(p, token);
|
|
3038
3092
|
break;
|
|
3039
3093
|
}
|
|
3040
3094
|
default: {
|
|
3041
|
-
p.tmplInsertionModeStack[
|
|
3095
|
+
p.tmplInsertionModeStack[p.tmplInsertionModeStack.length - 1] = InsertionMode.IN_BODY;
|
|
3042
3096
|
p.insertionMode = InsertionMode.IN_BODY;
|
|
3043
3097
|
startTagInBody(p, token);
|
|
3044
3098
|
}
|
|
@@ -3051,9 +3105,12 @@ function endTagInTemplate(p, token) {
|
|
|
3051
3105
|
}
|
|
3052
3106
|
function eofInTemplate(p, token) {
|
|
3053
3107
|
if (p.openElements.tmplCount > 0) {
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3108
|
+
// EOF closes every open template.
|
|
3109
|
+
do {
|
|
3110
|
+
p.openElements.popUntilTagNamePopped($.TEMPLATE);
|
|
3111
|
+
p.activeFormattingElements.clearToLastMarker();
|
|
3112
|
+
p.tmplInsertionModeStack.pop();
|
|
3113
|
+
} while (p.openElements.tmplCount > 0);
|
|
3057
3114
|
p._resetInsertionMode();
|
|
3058
3115
|
p.onEof(token);
|
|
3059
3116
|
}
|
|
@@ -3228,18 +3285,24 @@ function endTagInForeignContent(p, token) {
|
|
|
3228
3285
|
p._endTagOutsideForeignContent(token);
|
|
3229
3286
|
return;
|
|
3230
3287
|
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3288
|
+
// The current node is the root element (fragment case): ignore the token.
|
|
3289
|
+
if (p.openElements.stackTop === 0)
|
|
3290
|
+
return;
|
|
3291
|
+
// The topmost foreign element with the token's name closes, unless an HTML element is above it. Foreign names are
|
|
3292
|
+
// the lowercased start-tag name or its SVG adjustment, so these two are the ASCII case-insensitive match.
|
|
3293
|
+
const { tagName } = token;
|
|
3294
|
+
const adjustedTagName = foreignContent.SVG_TAG_NAMES_ADJUSTMENT_MAP.get(tagName);
|
|
3295
|
+
// Elements outside HTML, SVG and MathML only come from a fragment context in their namespace.
|
|
3296
|
+
const contextNs = p.fragmentContext ? p.treeAdapter.getNamespaceURI(p.fragmentContext) : NS.HTML;
|
|
3297
|
+
const i = Math.max(p.openElements.indexOfTopmostByName(NS.SVG, tagName), p.openElements.indexOfTopmostByName(NS.MATHML, tagName), adjustedTagName === undefined ? -1 : p.openElements.indexOfTopmostByName(NS.SVG, adjustedTagName), contextNs === NS.HTML || contextNs === NS.SVG || contextNs === NS.MATHML
|
|
3298
|
+
? -1
|
|
3299
|
+
: p.openElements.indexOfTopmostByName(contextNs, tagName));
|
|
3300
|
+
if (i > p.openElements.indexOfTopmostHtmlElement()) {
|
|
3301
|
+
//NOTE: update the token tag name for `_setEndLocation`.
|
|
3302
|
+
token.tagName = p.treeAdapter.getTagName(p.openElements.items[i]);
|
|
3303
|
+
p.openElements.shortenToLength(i);
|
|
3304
|
+
}
|
|
3305
|
+
else {
|
|
3306
|
+
p._endTagOutsideForeignContent(token);
|
|
3244
3307
|
}
|
|
3245
3308
|
}
|