1import { Tokenizer, TokenizerMode, type TokenHandler } from '../tokenizer/index.js'; 2import { OpenElementStack, type StackHandler } from './open-element-stack.js'; 3import { FormattingElementList, EntryType, type ElementEntry } from './formatting-element-list.js'; 4import { defaultTreeAdapter, type DefaultTreeAdapterMap } from '../tree-adapters/default.js'; 5import * as doctype from '../common/doctype.js'; 6import * as foreignContent from '../common/foreign-content.js'; 7import { ERR, type ParserErrorHandler } from '../common/error-codes.js'; 8import * as unicode from '../common/unicode.js'; 9import { 10 TAG_ID as $, 11 TAG_NAMES as TN, 12 NS, 13 ATTRS, 14 SPECIAL_ELEMENTS, 15 DOCUMENT_MODE, 16 NUMBERED_HEADERS, 17 getTagID, 18} from '../common/html.js'; 19import type { TreeAdapter, TreeAdapterTypeMap } from '../tree-adapters/interface.js'; 20import { 21 TokenType, 22 getTokenAttr, 23 type Token, 24 type CommentToken, 25 type CharacterToken, 26 type TagToken, 27 type DoctypeToken, 28 type EOFToken, 29 type LocationWithAttributes, 30 type ElementLocation, 31} from '../common/token.js'; 32 33//Misc constants 34const HIDDEN_INPUT_TYPE = 'hidden'; 35 36//Adoption agency loops iteration count 37const AA_OUTER_LOOP_ITER = 8; 38const AA_INNER_LOOP_ITER = 3; 39 40//Insertion modes 41enum InsertionMode { 42 INITIAL, 43 BEFORE_HTML, 44 BEFORE_HEAD, 45 IN_HEAD, 46 IN_HEAD_NO_SCRIPT, 47 AFTER_HEAD, 48 IN_BODY, 49 TEXT, 50 IN_TABLE, 51 IN_TABLE_TEXT, 52 IN_CAPTION, 53 IN_COLUMN_GROUP, 54 IN_TABLE_BODY, 55 IN_ROW, 56 IN_CELL, 57 IN_SELECT, 58 IN_SELECT_IN_TABLE, 59 IN_TEMPLATE, 60 AFTER_BODY, 61 IN_FRAMESET, 62 AFTER_FRAMESET, 63 AFTER_AFTER_BODY, 64 AFTER_AFTER_FRAMESET, 65} 66 67const BASE_LOC = { 68 startLine: -1, 69 startCol: -1, 70 startOffset: -1, 71 endLine: -1, 72 endCol: -1, 73 endOffset: -1, 74}; 75 76const TABLE_STRUCTURE_TAGS = new Set([$.TABLE, $.TBODY, $.TFOOT, $.THEAD, $.TR]); 77 78export interface ParserOptions<T extends TreeAdapterTypeMap> { 79 /** 80 * The [scripting flag](https://html.spec.whatwg.org/multipage/parsing.html#scripting-flag). If set 81 * to `true`, `noscript` element content will be parsed as text. 82 * 83 * @default `true` 84 */ 85 scriptingEnabled?: boolean; 86 87 /** 88 * Enables source code location information. When enabled, each node (except the root node) 89 * will have a `sourceCodeLocation` property. If the node is not an empty element, `sourceCodeLocation` will 90 * be a {@link ElementLocation} object, otherwise it will be {@link Location}. 91 * If the element was implicitly created by the parser (as part of 92 * [tree correction](https://html.spec.whatwg.org/multipage/syntax.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser)), 93 * its `sourceCodeLocation` property will be `undefined`. 94 * 95 * @default `false` 96 */ 97 sourceCodeLocationInfo?: boolean; 98 99 /** 100 * Specifies the resulting tree format. 101 * 102 * @default `treeAdapters.default` 103 */ 104 treeAdapter?: TreeAdapter<T>; 105 106 /** 107 * Callback for parse errors. 108 * 109 * @default `null` 110 */ 111 onParseError?: ParserErrorHandler | null; 112} 113 114const defaultParserOptions: Required<ParserOptions<DefaultTreeAdapterMap>> = { 115 scriptingEnabled: true, 116 sourceCodeLocationInfo: false, 117 treeAdapter: defaultTreeAdapter, 118 onParseError: null, 119}; 120 121//Parser 122export class Parser<T extends TreeAdapterTypeMap> implements TokenHandler, StackHandler<T> { 123 treeAdapter: TreeAdapter<T>; 124 /** @internal */ 125 onParseError: ParserErrorHandler | null; 126 protected currentToken: Token | null = null; 127 public options: Required<ParserOptions<T>>; 128 public document: T['document']; 129 130 public constructor( 131 options?: ParserOptions<T>, 132 document?: T['document'], 133 /** @internal */ 134 public fragmentContext: T['element'] | null = null, 135 /** @internal */ 136 public scriptHandler: null | ((pendingScript: T['element']) => void) = null, 137 ) { 138 this.options = { 139 ...defaultParserOptions, 140 ...options, 141 } as Required<ParserOptions<T>>; 142 143 this.treeAdapter = this.options.treeAdapter; 144 this.onParseError = this.options.onParseError; 145 146 // Always enable location info if we report parse errors. 147 if (this.onParseError) { 148 this.options.sourceCodeLocationInfo = true; 149 } 150 151 this.document = document ?? this.treeAdapter.createDocument(); 152 153 this.tokenizer = new Tokenizer(this.options, this); 154 this.activeFormattingElements = new FormattingElementList(this.treeAdapter); 155 156 this.fragmentContextID = fragmentContext ? getTagID(this.treeAdapter.getTagName(fragmentContext)) : $.UNKNOWN; 157 this._setContextModes(fragmentContext ?? this.document, this.fragmentContextID); 158 159 this.openElements = new OpenElementStack(this.document, this.treeAdapter, this); 160 } 161 162 // API 163 public static parse<T extends TreeAdapterTypeMap>(html: string, options?: ParserOptions<T>): T['document'] { 164 const parser = new this(options); 165 166 parser.tokenizer.write(html, true); 167 168 return parser.document; 169 } 170 171 public static getFragmentParser<T extends TreeAdapterTypeMap>( 172 fragmentContext?: T['parentNode'] | null, 173 options?: ParserOptions<T>, 174 ): Parser<T> { 175 const opts: Required<ParserOptions<T>> = { 176 ...defaultParserOptions, 177 ...options, 178 } as Required<ParserOptions<T>>; 179 180 //NOTE: use a <template> element as the fragment context if no context element was provided, 181 //so we will parse in a "forgiving" manner 182 fragmentContext ??= opts.treeAdapter.createElement(TN.TEMPLATE, NS.HTML, []); 183 184 //NOTE: create a fake element which will be used as the `document` for fragment parsing. 185 //This is important for jsdom, where a new `document` cannot be created. This led to 186 //fragment parsing messing with the main `document`. 187 const documentMock = opts.treeAdapter.createElement('documentmock', NS.HTML, []); 188 189 const parser = new this(opts, documentMock, fragmentContext); 190 191 if (parser.fragmentContextID === $.TEMPLATE) { 192 parser.tmplInsertionModeStack.unshift(InsertionMode.IN_TEMPLATE); 193 } 194 195 parser._initTokenizerForFragmentParsing(); 196 parser._insertFakeRootElement(); 197 parser._resetInsertionMode(); 198 parser._findFormInFragmentContext(); 199 200 return parser; 201 } 202 203 public getFragment(): T['documentFragment'] { 204 const rootElement = this.treeAdapter.getFirstChild(this.document) as T['parentNode']; 205 const fragment = this.treeAdapter.createDocumentFragment(); 206 207 this._adoptNodes(rootElement, fragment); 208 209 return fragment; 210 } 211 212 tokenizer: Tokenizer; 213 214 public stopped = false; 215 /** @internal */ 216 insertionMode = InsertionMode.INITIAL; 217 /** @internal */ 218 originalInsertionMode = InsertionMode.INITIAL; 219 220 /** @internal */ 221 fragmentContextID: $; 222 223 /** @internal */ 224 headElement: null | T['element'] = null; 225 /** @internal */ 226 formElement: null | T['element'] = null; 227 228 /** @internal */ 229 openElements: OpenElementStack<T>; 230 /** @internal */ 231 activeFormattingElements: FormattingElementList<T>; 232 /** Indicates that the current node is not an element in the HTML namespace */ 233 protected currentNotInHTML = false; 234 235 /** 236 * The template insertion mode stack is maintained from the left. 237 * Ie. the topmost element will always have index 0. 238 * 239 * @internal 240 */ 241 tmplInsertionModeStack: InsertionMode[] = []; 242 243 /** @internal */ 244 pendingCharacterTokens: CharacterToken[] = []; 245 /** @internal */ 246 hasNonWhitespacePendingCharacterToken = false; 247 248 /** @internal */ 249 framesetOk = true; 250 /** @internal */ 251 skipNextNewLine = false; 252 /** @internal */ 253 fosterParentingEnabled = false; 254 255 //Errors 256 257 /** @internal */ 258 _err(token: Token, code: ERR, beforeToken?: boolean): void { 259 if (!this.onParseError) return; 260 261 const loc = token.location ?? BASE_LOC; 262 const err = { 263 code, 264 startLine: loc.startLine, 265 startCol: loc.startCol, 266 startOffset: loc.startOffset, 267 endLine: beforeToken ? loc.startLine : loc.endLine, 268 endCol: beforeToken ? loc.startCol : loc.endCol, 269 endOffset: beforeToken ? loc.startOffset : loc.endOffset, 270 }; 271 272 this.onParseError(err); 273 } 274 275 //Stack events 276 277 /** @internal */ 278 onItemPush(node: T['parentNode'], tid: number, isTop: boolean): void { 279 this.treeAdapter.onItemPush?.(node); 280 if (isTop && this.openElements.stackTop > 0) this._setContextModes(node, tid); 281 } 282 283 /** @internal */ 284 onItemPop(node: T['parentNode'], isTop: boolean): void { 285 if (this.options.sourceCodeLocationInfo) { 286 this._setEndLocation(node, this.currentToken!); 287 } 288 289 this.treeAdapter.onItemPop?.(node, this.openElements.current); 290 291 if (isTop) { 292 let current; 293 let currentTagId; 294 295 if (this.openElements.stackTop === 0 && this.fragmentContext) { 296 current = this.fragmentContext; 297 currentTagId = this.fragmentContextID; 298 } else { 299 ({ current, currentTagId } = this.openElements); 300 } 301 302 this._setContextModes(current, currentTagId); 303 } 304 } 305 306 protected _setContextModes(current: T['parentNode'], tid: number): void { 307 const isHTML = current === this.document || this.treeAdapter.getNamespaceURI(current) === NS.HTML; 308 309 this.currentNotInHTML = !isHTML; 310 this.tokenizer.inForeignNode = !isHTML && !this._isIntegrationPoint(tid, current); 311 } 312 313 /** @protected */ 314 _switchToTextParsing( 315 currentToken: TagToken, 316 nextTokenizerState: (typeof TokenizerMode)[keyof typeof TokenizerMode], 317 ): void { 318 this._insertElement(currentToken, NS.HTML); 319 this.tokenizer.state = nextTokenizerState; 320 this.originalInsertionMode = this.insertionMode; 321 this.insertionMode = InsertionMode.TEXT; 322 } 323 324 switchToPlaintextParsing(): void { 325 this.insertionMode = InsertionMode.TEXT; 326 this.originalInsertionMode = InsertionMode.IN_BODY; 327 this.tokenizer.state = TokenizerMode.PLAINTEXT; 328 } 329 330 //Fragment parsing 331 332 /** @protected */ 333 _getAdjustedCurrentElement(): T['element'] { 334 return this.openElements.stackTop === 0 && this.fragmentContext 335 ? this.fragmentContext 336 : this.openElements.current; 337 } 338 339 /** @protected */ 340 _findFormInFragmentContext(): void { 341 let node = this.fragmentContext; 342 343 while (node) { 344 if (this.treeAdapter.getTagName(node) === TN.FORM) { 345 this.formElement = node; 346 break; 347 } 348 349 node = this.treeAdapter.getParentNode(node); 350 } 351 } 352 353 protected _initTokenizerForFragmentParsing(): void { 354 if (!this.fragmentContext || this.treeAdapter.getNamespaceURI(this.fragmentContext) !== NS.HTML) { 355 return; 356 } 357 358 switch (this.fragmentContextID) { 359 case $.TITLE: 360 case $.TEXTAREA: { 361 this.tokenizer.state = TokenizerMode.RCDATA; 362 break; 363 } 364 case $.STYLE: 365 case $.XMP: 366 case $.IFRAME: 367 case $.NOEMBED: 368 case $.NOFRAMES: 369 case $.NOSCRIPT: { 370 this.tokenizer.state = TokenizerMode.RAWTEXT; 371 break; 372 } 373 case $.SCRIPT: { 374 this.tokenizer.state = TokenizerMode.SCRIPT_DATA; 375 break; 376 } 377 case $.PLAINTEXT: { 378 this.tokenizer.state = TokenizerMode.PLAINTEXT; 379 break; 380 } 381 default: 382 // Do nothing 383 } 384 } 385 386 //Tree mutation 387 388 /** @protected */ 389 _setDocumentType(token: DoctypeToken): void { 390 const name = token.name || ''; 391 const publicId = token.publicId || ''; 392 const systemId = token.systemId || ''; 393 394 this.treeAdapter.setDocumentType(this.document, name, publicId, systemId); 395 396 if (token.location) { 397 const documentChildren = this.treeAdapter.getChildNodes(this.document); 398 const docTypeNode = documentChildren.find((node) => this.treeAdapter.isDocumentTypeNode(node)); 399 400 if (docTypeNode) { 401 this.treeAdapter.setNodeSourceCodeLocation(docTypeNode, token.location); 402 } 403 } 404 } 405 406 /** @protected */ 407 _attachElementToTree(element: T['element'], location: LocationWithAttributes | null): void { 408 if (this.options.sourceCodeLocationInfo) { 409 const loc = location && { 410 ...location, 411 startTag: location, 412 }; 413 414 this.treeAdapter.setNodeSourceCodeLocation(element, loc); 415 } 416 417 if (this._shouldFosterParentOnInsertion()) { 418 this._fosterParentElement(element); 419 } else { 420 const parent = this.openElements.currentTmplContentOrNode; 421 422 this.treeAdapter.appendChild(parent, element); 423 } 424 } 425 426 /** 427 * For self-closing tags. Add an element to the tree, but skip adding it 428 * to the stack. 429 */ 430 431 /** @protected */ 432 _appendElement(token: TagToken, namespaceURI: NS): void { 433 const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs); 434 435 this._attachElementToTree(element, token.location); 436 } 437 438 /** @protected */ 439 _insertElement(token: TagToken, namespaceURI: NS): void { 440 const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs); 441 442 this._attachElementToTree(element, token.location); 443 this.openElements.push(element, token.tagID); 444 } 445 446 /** @protected */ 447 _insertFakeElement(tagName: string, tagID: $): void { 448 const element = this.treeAdapter.createElement(tagName, NS.HTML, []); 449 450 this._attachElementToTree(element, null); 451 this.openElements.push(element, tagID); 452 } 453 454 /** @protected */ 455 _insertTemplate(token: TagToken): void { 456 const tmpl = this.treeAdapter.createElement(token.tagName, NS.HTML, token.attrs); 457 const content = this.treeAdapter.createDocumentFragment(); 458 459 this.treeAdapter.setTemplateContent(tmpl, content); 460 this._attachElementToTree(tmpl, token.location); 461 this.openElements.push(tmpl, token.tagID); 462 if (this.options.sourceCodeLocationInfo) this.treeAdapter.setNodeSourceCodeLocation(content, null); 463 } 464 465 /** @protected */ 466 _insertFakeRootElement(): void { 467 const element = this.treeAdapter.createElement(TN.HTML, NS.HTML, []); 468 if (this.options.sourceCodeLocationInfo) this.treeAdapter.setNodeSourceCodeLocation(element, null); 469 470 this.treeAdapter.appendChild(this.openElements.current, element); 471 this.openElements.push(element, $.HTML); 472 } 473 474 /** @protected */ 475 _appendCommentNode(token: CommentToken, parent: T['parentNode']): void { 476 const commentNode = this.treeAdapter.createCommentNode(token.data); 477 478 this.treeAdapter.appendChild(parent, commentNode); 479 if (this.options.sourceCodeLocationInfo) { 480 this.treeAdapter.setNodeSourceCodeLocation(commentNode, token.location); 481 } 482 } 483 484 /** @protected */ 485 _insertCharacters(token: CharacterToken): void { 486 let parent; 487 let beforeElement; 488 489 if (this._shouldFosterParentOnInsertion()) { 490 ({ parent, beforeElement } = this._findFosterParentingLocation()); 491 492 if (beforeElement) { 493 this.treeAdapter.insertTextBefore(parent, token.chars, beforeElement); 494 } else { 495 this.treeAdapter.insertText(parent, token.chars); 496 } 497 } else { 498 parent = this.openElements.currentTmplContentOrNode; 499 500 this.treeAdapter.insertText(parent, token.chars); 501 } 502 503 if (!token.location) return; 504 505 const siblings = this.treeAdapter.getChildNodes(parent); 506 const textNodeIdx = beforeElement ? siblings.lastIndexOf(beforeElement) : siblings.length; 507 const textNode = siblings[textNodeIdx - 1]; 508 509 //NOTE: if we have a location assigned by another token, then just update the end position 510 const tnLoc = this.treeAdapter.getNodeSourceCodeLocation(textNode); 511 512 if (tnLoc) { 513 const { endLine, endCol, endOffset } = token.location; 514 this.treeAdapter.updateNodeSourceCodeLocation(textNode, { endLine, endCol, endOffset }); 515 } else if (this.options.sourceCodeLocationInfo) { 516 this.treeAdapter.setNodeSourceCodeLocation(textNode, token.location); 517 } 518 } 519 520 /** @protected */ 521 _adoptNodes(donor: T['parentNode'], recipient: T['parentNode']): void { 522 for (let child = this.treeAdapter.getFirstChild(donor); child; child = this.treeAdapter.getFirstChild(donor)) { 523 this.treeAdapter.detachNode(child); 524 this.treeAdapter.appendChild(recipient, child); 525 } 526 } 527 528 /** @protected */ 529 _setEndLocation(element: T['element'], closingToken: Token): void { 530 if (this.treeAdapter.getNodeSourceCodeLocation(element) && closingToken.location) { 531 const ctLoc = closingToken.location; 532 const tn = this.treeAdapter.getTagName(element); 533 534 const endLoc: Partial<ElementLocation> = 535 // NOTE: For cases like <p> <p> </p> - First 'p' closes without a closing 536 // tag and for cases like <td> <p> </td> - 'p' closes without a closing tag. 537 closingToken.type === TokenType.END_TAG && tn === closingToken.tagName 538 ? { 539 endTag: { ...ctLoc }, 540 endLine: ctLoc.endLine, 541 endCol: ctLoc.endCol, 542 endOffset: ctLoc.endOffset, 543 } 544 : { 545 endLine: ctLoc.startLine, 546 endCol: ctLoc.startCol, 547 endOffset: ctLoc.startOffset, 548 }; 549 550 this.treeAdapter.updateNodeSourceCodeLocation(element, endLoc); 551 } 552 } 553 554 //Token processing 555 protected shouldProcessStartTagTokenInForeignContent(token: TagToken): boolean { 556 // Check that neither current === document, or ns === NS.HTML 557 if (!this.currentNotInHTML) return false; 558 559 let current: T['parentNode']; 560 let currentTagId: number; 561 562 if (this.openElements.stackTop === 0 && this.fragmentContext) { 563 current = this.fragmentContext; 564 currentTagId = this.fragmentContextID; 565 } else { 566 ({ current, currentTagId } = this.openElements); 567 } 568 569 if ( 570 token.tagID === $.SVG && 571 this.treeAdapter.getTagName(current) === TN.ANNOTATION_XML && 572 this.treeAdapter.getNamespaceURI(current) === NS.MATHML 573 ) { 574 return false; 575 } 576 577 return ( 578 // Check that `current` is not an integration point for HTML or MathML elements. 579 this.tokenizer.inForeignNode || 580 // If it _is_ an integration point, then we might have to check that it is not an HTML 581 // integration point. 582 ((token.tagID === $.MGLYPH || token.tagID === $.MALIGNMARK) && 583 !this._isIntegrationPoint(currentTagId, current, NS.HTML)) 584 ); 585 } 586 587 /** @protected */ 588 _processToken(token: Token): void { 589 switch (token.type) { 590 case TokenType.CHARACTER: { 591 this.onCharacter(token); 592 break; 593 } 594 case TokenType.NULL_CHARACTER: { 595 this.onNullCharacter(token); 596 break; 597 } 598 case TokenType.COMMENT: { 599 this.onComment(token); 600 break; 601 } 602 case TokenType.DOCTYPE: { 603 this.onDoctype(token); 604 break; 605 } 606 case TokenType.START_TAG: { 607 this._processStartTag(token); 608 break; 609 } 610 case TokenType.END_TAG: { 611 this.onEndTag(token); 612 break; 613 } 614 case TokenType.EOF: { 615 this.onEof(token); 616 break; 617 } 618 case TokenType.WHITESPACE_CHARACTER: { 619 this.onWhitespaceCharacter(token); 620 break; 621 } 622 } 623 } 624 625 //Integration points 626 627 /** @protected */ 628 _isIntegrationPoint(tid: $, element: T['element'], foreignNS?: NS): boolean { 629 const ns = this.treeAdapter.getNamespaceURI(element); 630 const attrs = this.treeAdapter.getAttrList(element); 631 632 return foreignContent.isIntegrationPoint(tid, ns, attrs, foreignNS); 633 } 634 635 //Active formatting elements reconstruction 636 637 /** @protected */ 638 _reconstructActiveFormattingElements(): void { 639 const listLength = this.activeFormattingElements.entries.length; 640 641 if (listLength) { 642 const endIndex = this.activeFormattingElements.entries.findIndex( 643 (entry) => entry.type === EntryType.Marker || this.openElements.contains(entry.element), 644 ); 645 646 const unopenIdx = endIndex < 0 ? listLength - 1 : endIndex - 1; 647 648 for (let i = unopenIdx; i >= 0; i--) { 649 const entry = this.activeFormattingElements.entries[i] as ElementEntry<T>; 650 this._insertElement(entry.token, this.treeAdapter.getNamespaceURI(entry.element)); 651 entry.element = this.openElements.current; 652 } 653 } 654 } 655 656 //Close elements 657 658 /** @protected */ 659 _closeTableCell(): void { 660 this.openElements.generateImpliedEndTags(); 661 this.openElements.popUntilTableCellPopped(); 662 this.activeFormattingElements.clearToLastMarker(); 663 this.insertionMode = InsertionMode.IN_ROW; 664 } 665 666 /** @protected */ 667 _closePElement(): void { 668 this.openElements.generateImpliedEndTagsWithExclusion($.P); 669 this.openElements.popUntilTagNamePopped($.P); 670 } 671 672 //Insertion modes 673 674 /** @protected */ 675 _resetInsertionMode(): void { 676 for (let i = this.openElements.stackTop; i >= 0; i--) { 677 //Insertion mode reset map 678 switch (i === 0 && this.fragmentContext ? this.fragmentContextID : this.openElements.tagIDs[i]) { 679 case $.TR: { 680 this.insertionMode = InsertionMode.IN_ROW; 681 return; 682 } 683 case $.TBODY: 684 case $.THEAD: 685 case $.TFOOT: { 686 this.insertionMode = InsertionMode.IN_TABLE_BODY; 687 return; 688 } 689 case $.CAPTION: { 690 this.insertionMode = InsertionMode.IN_CAPTION; 691 return; 692 } 693 case $.COLGROUP: { 694 this.insertionMode = InsertionMode.IN_COLUMN_GROUP; 695 return; 696 } 697 case $.TABLE: { 698 this.insertionMode = InsertionMode.IN_TABLE; 699 return; 700 } 701 case $.BODY: { 702 this.insertionMode = InsertionMode.IN_BODY; 703 return; 704 } 705 case $.FRAMESET: { 706 this.insertionMode = InsertionMode.IN_FRAMESET; 707 return; 708 } 709 case $.SELECT: { 710 this._resetInsertionModeForSelect(i); 711 return; 712 } 713 case $.TEMPLATE: { 714 this.insertionMode = this.tmplInsertionModeStack[0]; 715 return; 716 } 717 case $.HTML: { 718 this.insertionMode = this.headElement ? InsertionMode.AFTER_HEAD : InsertionMode.BEFORE_HEAD; 719 return; 720 } 721 case $.TD: 722 case $.TH: { 723 if (i > 0) { 724 this.insertionMode = InsertionMode.IN_CELL; 725 return; 726 } 727 break; 728 } 729 case $.HEAD: { 730 if (i > 0) { 731 this.insertionMode = InsertionMode.IN_HEAD; 732 return; 733 } 734 break; 735 } 736 } 737 } 738 739 this.insertionMode = InsertionMode.IN_BODY; 740 } 741 742 /** @protected */ 743 _resetInsertionModeForSelect(selectIdx: number): void { 744 if (selectIdx > 0) { 745 for (let i = selectIdx - 1; i > 0; i--) { 746 const tn = this.openElements.tagIDs[i]; 747 748 if (tn === $.TEMPLATE) { 749 break; 750 } else if (tn === $.TABLE) { 751 this.insertionMode = InsertionMode.IN_SELECT_IN_TABLE; 752 return; 753 } 754 } 755 } 756 757 this.insertionMode = InsertionMode.IN_SELECT; 758 } 759 760 //Foster parenting 761 762 /** @protected */ 763 _isElementCausesFosterParenting(tn: $): boolean { 764 return TABLE_STRUCTURE_TAGS.has(tn); 765 } 766 767 /** @protected */ 768 _shouldFosterParentOnInsertion(): boolean { 769 return this.fosterParentingEnabled && this._isElementCausesFosterParenting(this.openElements.currentTagId); 770 } 771 772 /** @protected */ 773 _findFosterParentingLocation(): { parent: T['parentNode']; beforeElement: T['element'] | null } { 774 for (let i = this.openElements.stackTop; i >= 0; i--) { 775 const openElement = this.openElements.items[i]; 776 777 switch (this.openElements.tagIDs[i]) { 778 case $.TEMPLATE: { 779 if (this.treeAdapter.getNamespaceURI(openElement) === NS.HTML) { 780 return { parent: this.treeAdapter.getTemplateContent(openElement), beforeElement: null }; 781 } 782 break; 783 } 784 case $.TABLE: { 785 const parent = this.treeAdapter.getParentNode(openElement); 786 787 if (parent) { 788 return { parent, beforeElement: openElement }; 789 } 790 791 return { parent: this.openElements.items[i - 1], beforeElement: null }; 792 } 793 default: 794 // Do nothing 795 } 796 } 797 798 return { parent: this.openElements.items[0], beforeElement: null }; 799 } 800 801 /** @protected */ 802 _fosterParentElement(element: T['element']): void { 803 const location = this._findFosterParentingLocation(); 804 805 if (location.beforeElement) { 806 this.treeAdapter.insertBefore(location.parent, element, location.beforeElement); 807 } else { 808 this.treeAdapter.appendChild(location.parent, element); 809 } 810 } 811 812 //Special elements 813 814 /** @protected */ 815 _isSpecialElement(element: T['element'], id: $): boolean { 816 const ns = this.treeAdapter.getNamespaceURI(element); 817 818 return SPECIAL_ELEMENTS[ns].has(id); 819 } 820 821 /** @internal */ 822 onCharacter(token: CharacterToken): void { 823 this.skipNextNewLine = false; 824 825 if (this.tokenizer.inForeignNode) { 826 characterInForeignContent(this, token); 827 return; 828 } 829 830 switch (this.insertionMode) { 831 case InsertionMode.INITIAL: { 832 tokenInInitialMode(this, token); 833 break; 834 } 835 case InsertionMode.BEFORE_HTML: { 836 tokenBeforeHtml(this, token); 837 break; 838 } 839 case InsertionMode.BEFORE_HEAD: { 840 tokenBeforeHead(this, token); 841 break; 842 } 843 case InsertionMode.IN_HEAD: { 844 tokenInHead(this, token); 845 break; 846 } 847 case InsertionMode.IN_HEAD_NO_SCRIPT: { 848 tokenInHeadNoScript(this, token); 849 break; 850 } 851 case InsertionMode.AFTER_HEAD: { 852 tokenAfterHead(this, token); 853 break; 854 } 855 case InsertionMode.IN_BODY: 856 case InsertionMode.IN_CAPTION: 857 case InsertionMode.IN_CELL: 858 case InsertionMode.IN_TEMPLATE: { 859 characterInBody(this, token); 860 break; 861 } 862 case InsertionMode.TEXT: 863 case InsertionMode.IN_SELECT: 864 case InsertionMode.IN_SELECT_IN_TABLE: { 865 this._insertCharacters(token); 866 break; 867 } 868 case InsertionMode.IN_TABLE: 869 case InsertionMode.IN_TABLE_BODY: 870 case InsertionMode.IN_ROW: { 871 characterInTable(this, token); 872 break; 873 } 874 case InsertionMode.IN_TABLE_TEXT: { 875 characterInTableText(this, token); 876 break; 877 } 878 case InsertionMode.IN_COLUMN_GROUP: { 879 tokenInColumnGroup(this, token); 880 break; 881 } 882 case InsertionMode.AFTER_BODY: { 883 tokenAfterBody(this, token); 884 break; 885 } 886 case InsertionMode.AFTER_AFTER_BODY: { 887 tokenAfterAfterBody(this, token); 888 break; 889 } 890 default: 891 // Do nothing 892 } 893 } 894 /** @internal */ 895 onNullCharacter(token: CharacterToken): void { 896 this.skipNextNewLine = false; 897 898 if (this.tokenizer.inForeignNode) { 899 nullCharacterInForeignContent(this, token); 900 return; 901 } 902 903 switch (this.insertionMode) { 904 case InsertionMode.INITIAL: { 905 tokenInInitialMode(this, token); 906 break; 907 } 908 case InsertionMode.BEFORE_HTML: { 909 tokenBeforeHtml(this, token); 910 break; 911 } 912 case InsertionMode.BEFORE_HEAD: { 913 tokenBeforeHead(this, token); 914 break; 915 } 916 case InsertionMode.IN_HEAD: { 917 tokenInHead(this, token); 918 break; 919 } 920 case InsertionMode.IN_HEAD_NO_SCRIPT: { 921 tokenInHeadNoScript(this, token); 922 break; 923 } 924 case InsertionMode.AFTER_HEAD: { 925 tokenAfterHead(this, token); 926 break; 927 } 928 case InsertionMode.TEXT: { 929 this._insertCharacters(token); 930 break; 931 } 932 case InsertionMode.IN_TABLE: 933 case InsertionMode.IN_TABLE_BODY: 934 case InsertionMode.IN_ROW: { 935 characterInTable(this, token); 936 break; 937 } 938 case InsertionMode.IN_COLUMN_GROUP: { 939 tokenInColumnGroup(this, token); 940 break; 941 } 942 case InsertionMode.AFTER_BODY: { 943 tokenAfterBody(this, token); 944 break; 945 } 946 case InsertionMode.AFTER_AFTER_BODY: { 947 tokenAfterAfterBody(this, token); 948 break; 949 } 950 default: 951 // Do nothing 952 } 953 } 954 /** @internal */ 955 onComment(token: CommentToken): void { 956 this.skipNextNewLine = false; 957 958 if (this.currentNotInHTML) { 959 appendComment(this, token); 960 return; 961 } 962 963 switch (this.insertionMode) { 964 case InsertionMode.INITIAL: 965 case InsertionMode.BEFORE_HTML: 966 case InsertionMode.BEFORE_HEAD: 967 case InsertionMode.IN_HEAD: 968 case InsertionMode.IN_HEAD_NO_SCRIPT: 969 case InsertionMode.AFTER_HEAD: 970 case InsertionMode.IN_BODY: 971 case InsertionMode.IN_TABLE: 972 case InsertionMode.IN_CAPTION: 973 case InsertionMode.IN_COLUMN_GROUP: 974 case InsertionMode.IN_TABLE_BODY: 975 case InsertionMode.IN_ROW: 976 case InsertionMode.IN_CELL: 977 case InsertionMode.IN_SELECT: 978 case InsertionMode.IN_SELECT_IN_TABLE: 979 case InsertionMode.IN_TEMPLATE: 980 case InsertionMode.IN_FRAMESET: 981 case InsertionMode.AFTER_FRAMESET: { 982 appendComment(this, token); 983 break; 984 } 985 case InsertionMode.IN_TABLE_TEXT: { 986 tokenInTableText(this, token); 987 break; 988 } 989 case InsertionMode.AFTER_BODY: { 990 appendCommentToRootHtmlElement(this, token); 991 break; 992 } 993 case InsertionMode.AFTER_AFTER_BODY: 994 case InsertionMode.AFTER_AFTER_FRAMESET: { 995 appendCommentToDocument(this, token); 996 break; 997 } 998 default: 999 // Do nothing 1000 } 1001 } 1002 /** @internal */ 1003 onDoctype(token: DoctypeToken): void { 1004 this.skipNextNewLine = false; 1005 switch (this.insertionMode) { 1006 case InsertionMode.INITIAL: { 1007 doctypeInInitialMode(this, token); 1008 break; 1009 } 1010 case InsertionMode.BEFORE_HEAD: 1011 case InsertionMode.IN_HEAD: 1012 case InsertionMode.IN_HEAD_NO_SCRIPT: 1013 case InsertionMode.AFTER_HEAD: { 1014 this._err(token, ERR.misplacedDoctype); 1015 break; 1016 } 1017 case InsertionMode.IN_TABLE_TEXT: { 1018 tokenInTableText(this, token); 1019 break; 1020 } 1021 default: 1022 // Do nothing 1023 } 1024 } 1025 /** @internal */ 1026 onStartTag(token: TagToken): void { 1027 this.skipNextNewLine = false; 1028 this.currentToken = token; 1029 1030 this._processStartTag(token); 1031 1032 if (token.selfClosing && !token.ackSelfClosing) { 1033 this._err(token, ERR.nonVoidHtmlElementStartTagWithTrailingSolidus); 1034 } 1035 } 1036 /** 1037 * Processes a given start tag. 1038 * 1039 * `onStartTag` checks if a self-closing tag was recognized. When a token 1040 * is moved inbetween multiple insertion modes, this check for self-closing 1041 * could lead to false positives. To avoid this, `_processStartTag` is used 1042 * for nested calls. 1043 * 1044 * @param token The token to process. 1045 * @protected 1046 */ 1047 _processStartTag(token: TagToken): void { 1048 if (this.shouldProcessStartTagTokenInForeignContent(token)) { 1049 startTagInForeignContent(this, token); 1050 } else { 1051 this._startTagOutsideForeignContent(token); 1052 } 1053 } 1054 /** @protected */ 1055 _startTagOutsideForeignContent(token: TagToken): void { 1056 switch (this.insertionMode) { 1057 case InsertionMode.INITIAL: { 1058 tokenInInitialMode(this, token); 1059 break; 1060 } 1061 case InsertionMode.BEFORE_HTML: { 1062 startTagBeforeHtml(this, token); 1063 break; 1064 } 1065 case InsertionMode.BEFORE_HEAD: { 1066 startTagBeforeHead(this, token); 1067 break; 1068 } 1069 case InsertionMode.IN_HEAD: { 1070 startTagInHead(this, token); 1071 break; 1072 } 1073 case InsertionMode.IN_HEAD_NO_SCRIPT: { 1074 startTagInHeadNoScript(this, token); 1075 break; 1076 } 1077 case InsertionMode.AFTER_HEAD: { 1078 startTagAfterHead(this, token); 1079 break; 1080 } 1081 case InsertionMode.IN_BODY: { 1082 startTagInBody(this, token); 1083 break; 1084 } 1085 case InsertionMode.IN_TABLE: { 1086 startTagInTable(this, token); 1087 break; 1088 } 1089 case InsertionMode.IN_TABLE_TEXT: { 1090 tokenInTableText(this, token); 1091 break; 1092 } 1093 case InsertionMode.IN_CAPTION: { 1094 startTagInCaption(this, token); 1095 break; 1096 } 1097 case InsertionMode.IN_COLUMN_GROUP: { 1098 startTagInColumnGroup(this, token); 1099 break; 1100 } 1101 case InsertionMode.IN_TABLE_BODY: { 1102 startTagInTableBody(this, token); 1103 break; 1104 } 1105 case InsertionMode.IN_ROW: { 1106 startTagInRow(this, token); 1107 break; 1108 } 1109 case InsertionMode.IN_CELL: { 1110 startTagInCell(this, token); 1111 break; 1112 } 1113 case InsertionMode.IN_SELECT: { 1114 startTagInSelect(this, token); 1115 break; 1116 } 1117 case InsertionMode.IN_SELECT_IN_TABLE: { 1118 startTagInSelectInTable(this, token); 1119 break; 1120 } 1121 case InsertionMode.IN_TEMPLATE: { 1122 startTagInTemplate(this, token); 1123 break; 1124 } 1125 case InsertionMode.AFTER_BODY: { 1126 startTagAfterBody(this, token); 1127 break; 1128 } 1129 case InsertionMode.IN_FRAMESET: { 1130 startTagInFrameset(this, token); 1131 break; 1132 } 1133 case InsertionMode.AFTER_FRAMESET: { 1134 startTagAfterFrameset(this, token); 1135 break; 1136 } 1137 case InsertionMode.AFTER_AFTER_BODY: { 1138 startTagAfterAfterBody(this, token); 1139 break; 1140 } 1141 case InsertionMode.AFTER_AFTER_FRAMESET: { 1142 startTagAfterAfterFrameset(this, token); 1143 break; 1144 } 1145 default: 1146 // Do nothing 1147 } 1148 } 1149 /** @internal */ 1150 onEndTag(token: TagToken): void { 1151 this.skipNextNewLine = false; 1152 this.currentToken = token; 1153 1154 if (this.currentNotInHTML) { 1155 endTagInForeignContent(this, token); 1156 } else { 1157 this._endTagOutsideForeignContent(token); 1158 } 1159 } 1160 /** @protected */ 1161 _endTagOutsideForeignContent(token: TagToken): void { 1162 switch (this.insertionMode) { 1163 case InsertionMode.INITIAL: { 1164 tokenInInitialMode(this, token); 1165 break; 1166 } 1167 case InsertionMode.BEFORE_HTML: { 1168 endTagBeforeHtml(this, token); 1169 break; 1170 } 1171 case InsertionMode.BEFORE_HEAD: { 1172 endTagBeforeHead(this, token); 1173 break; 1174 } 1175 case InsertionMode.IN_HEAD: { 1176 endTagInHead(this, token); 1177 break; 1178 } 1179 case InsertionMode.IN_HEAD_NO_SCRIPT: { 1180 endTagInHeadNoScript(this, token); 1181 break; 1182 } 1183 case InsertionMode.AFTER_HEAD: { 1184 endTagAfterHead(this, token); 1185 break; 1186 } 1187 case InsertionMode.IN_BODY: { 1188 endTagInBody(this, token); 1189 break; 1190 } 1191 case InsertionMode.TEXT: { 1192 endTagInText(this, token); 1193 break; 1194 } 1195 case InsertionMode.IN_TABLE: { 1196 endTagInTable(this, token); 1197 break; 1198 } 1199 case InsertionMode.IN_TABLE_TEXT: { 1200 tokenInTableText(this, token); 1201 break; 1202 } 1203 case InsertionMode.IN_CAPTION: { 1204 endTagInCaption(this, token); 1205 break; 1206 } 1207 case InsertionMode.IN_COLUMN_GROUP: { 1208 endTagInColumnGroup(this, token); 1209 break; 1210 } 1211 case InsertionMode.IN_TABLE_BODY: { 1212 endTagInTableBody(this, token); 1213 break; 1214 } 1215 case InsertionMode.IN_ROW: { 1216 endTagInRow(this, token); 1217 break; 1218 } 1219 case InsertionMode.IN_CELL: { 1220 endTagInCell(this, token); 1221 break; 1222 } 1223 case InsertionMode.IN_SELECT: { 1224 endTagInSelect(this, token); 1225 break; 1226 } 1227 case InsertionMode.IN_SELECT_IN_TABLE: { 1228 endTagInSelectInTable(this, token); 1229 break; 1230 } 1231 case InsertionMode.IN_TEMPLATE: { 1232 endTagInTemplate(this, token); 1233 break; 1234 } 1235 case InsertionMode.AFTER_BODY: { 1236 endTagAfterBody(this, token); 1237 break; 1238 } 1239 case InsertionMode.IN_FRAMESET: { 1240 endTagInFrameset(this, token); 1241 break; 1242 } 1243 case InsertionMode.AFTER_FRAMESET: { 1244 endTagAfterFrameset(this, token); 1245 break; 1246 } 1247 case InsertionMode.AFTER_AFTER_BODY: { 1248 tokenAfterAfterBody(this, token); 1249 break; 1250 } 1251 default: 1252 // Do nothing 1253 } 1254 } 1255 /** @internal */ 1256 onEof(token: EOFToken): void { 1257 switch (this.insertionMode) { 1258 case InsertionMode.INITIAL: { 1259 tokenInInitialMode(this, token); 1260 break; 1261 } 1262 case InsertionMode.BEFORE_HTML: { 1263 tokenBeforeHtml(this, token); 1264 break; 1265 } 1266 case InsertionMode.BEFORE_HEAD: { 1267 tokenBeforeHead(this, token); 1268 break; 1269 } 1270 case InsertionMode.IN_HEAD: { 1271 tokenInHead(this, token); 1272 break; 1273 } 1274 case InsertionMode.IN_HEAD_NO_SCRIPT: { 1275 tokenInHeadNoScript(this, token); 1276 break; 1277 } 1278 case InsertionMode.AFTER_HEAD: { 1279 tokenAfterHead(this, token); 1280 break; 1281 } 1282 case InsertionMode.IN_BODY: 1283 case InsertionMode.IN_TABLE: 1284 case InsertionMode.IN_CAPTION: 1285 case InsertionMode.IN_COLUMN_GROUP: 1286 case InsertionMode.IN_TABLE_BODY: 1287 case InsertionMode.IN_ROW: 1288 case InsertionMode.IN_CELL: 1289 case InsertionMode.IN_SELECT: 1290 case InsertionMode.IN_SELECT_IN_TABLE: { 1291 eofInBody(this, token); 1292 break; 1293 } 1294 case InsertionMode.TEXT: { 1295 eofInText(this, token); 1296 break; 1297 } 1298 case InsertionMode.IN_TABLE_TEXT: { 1299 tokenInTableText(this, token); 1300 break; 1301 } 1302 case InsertionMode.IN_TEMPLATE: { 1303 eofInTemplate(this, token); 1304 break; 1305 } 1306 case InsertionMode.AFTER_BODY: 1307 case InsertionMode.IN_FRAMESET: 1308 case InsertionMode.AFTER_FRAMESET: 1309 case InsertionMode.AFTER_AFTER_BODY: 1310 case InsertionMode.AFTER_AFTER_FRAMESET: { 1311 stopParsing(this, token); 1312 break; 1313 } 1314 default: 1315 // Do nothing 1316 } 1317 } 1318 /** @internal */ 1319 onWhitespaceCharacter(token: CharacterToken): void { 1320 if (this.skipNextNewLine) { 1321 this.skipNextNewLine = false; 1322 1323 if (token.chars.charCodeAt(0) === unicode.CODE_POINTS.LINE_FEED) { 1324 if (token.chars.length === 1) { 1325 return; 1326 } 1327 1328 token.chars = token.chars.substr(1); 1329 } 1330 } 1331 1332 if (this.tokenizer.inForeignNode) { 1333 this._insertCharacters(token); 1334 return; 1335 } 1336 1337 switch (this.insertionMode) { 1338 case InsertionMode.IN_HEAD: 1339 case InsertionMode.IN_HEAD_NO_SCRIPT: 1340 case InsertionMode.AFTER_HEAD: 1341 case InsertionMode.TEXT: 1342 case InsertionMode.IN_COLUMN_GROUP: 1343 case InsertionMode.IN_SELECT: 1344 case InsertionMode.IN_SELECT_IN_TABLE: 1345 case InsertionMode.IN_FRAMESET: 1346 case InsertionMode.AFTER_FRAMESET: { 1347 this._insertCharacters(token); 1348 break; 1349 } 1350 case InsertionMode.IN_BODY: 1351 case InsertionMode.IN_CAPTION: 1352 case InsertionMode.IN_CELL: 1353 case InsertionMode.IN_TEMPLATE: 1354 case InsertionMode.AFTER_BODY: 1355 case InsertionMode.AFTER_AFTER_BODY: 1356 case InsertionMode.AFTER_AFTER_FRAMESET: { 1357 whitespaceCharacterInBody(this, token); 1358 break; 1359 } 1360 case InsertionMode.IN_TABLE: 1361 case InsertionMode.IN_TABLE_BODY: 1362 case InsertionMode.IN_ROW: { 1363 characterInTable(this, token); 1364 break; 1365 } 1366 case InsertionMode.IN_TABLE_TEXT: { 1367 whitespaceCharacterInTableText(this, token); 1368 break; 1369 } 1370 default: 1371 // Do nothing 1372 } 1373 } 1374} 1375 1376//Adoption agency algorithm 1377//(see: http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adoptionAgency) 1378//------------------------------------------------------------------ 1379 1380//Steps 5-8 of the algorithm 1381function aaObtainFormattingElementEntry<T extends TreeAdapterTypeMap>( 1382 p: Parser<T>, 1383 token: TagToken, 1384): ElementEntry<T> | null { 1385 let formattingElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName(token.tagName); 1386 1387 if (formattingElementEntry) { 1388 if (!p.openElements.contains(formattingElementEntry.element)) { 1389 p.activeFormattingElements.removeEntry(formattingElementEntry); 1390 formattingElementEntry = null; 1391 } else if (!p.openElements.hasInScope(token.tagID)) { 1392 formattingElementEntry = null; 1393 } 1394 } else { 1395 genericEndTagInBody(p, token); 1396 } 1397 1398 return formattingElementEntry; 1399} 1400 1401//Steps 9 and 10 of the algorithm 1402function aaObtainFurthestBlock<T extends TreeAdapterTypeMap>( 1403 p: Parser<T>, 1404 formattingElementEntry: ElementEntry<T>, 1405): T['parentNode'] | null { 1406 let furthestBlock = null; 1407 let idx = p.openElements.stackTop; 1408 1409 for (; idx >= 0; idx--) { 1410 const element = p.openElements.items[idx]; 1411 1412 if (element === formattingElementEntry.element) { 1413 break; 1414 } 1415 1416 if (p._isSpecialElement(element, p.openElements.tagIDs[idx])) { 1417 furthestBlock = element; 1418 } 1419 } 1420 1421 if (!furthestBlock) { 1422 p.openElements.shortenToLength(idx < 0 ? 0 : idx); 1423 p.activeFormattingElements.removeEntry(formattingElementEntry); 1424 } 1425 1426 return furthestBlock; 1427} 1428 1429//Step 13 of the algorithm 1430function aaInnerLoop<T extends TreeAdapterTypeMap>( 1431 p: Parser<T>, 1432 furthestBlock: T['element'], 1433 formattingElement: T['element'], 1434): T['element'] { 1435 let lastElement = furthestBlock; 1436 let nextElement = p.openElements.getCommonAncestor(furthestBlock) as T['element']; 1437 1438 for (let i = 0, element = nextElement; element !== formattingElement; i++, element = nextElement) { 1439 //NOTE: store the next element for the next loop iteration (it may be deleted from the stack by step 9.5) 1440 nextElement = p.openElements.getCommonAncestor(element) as T['element']; 1441 1442 const elementEntry = p.activeFormattingElements.getElementEntry(element); 1443 const counterOverflow = elementEntry && i >= AA_INNER_LOOP_ITER; 1444 const shouldRemoveFromOpenElements = !elementEntry || counterOverflow; 1445 1446 if (shouldRemoveFromOpenElements) { 1447 if (counterOverflow) { 1448 p.activeFormattingElements.removeEntry(elementEntry); 1449 } 1450 1451 p.openElements.remove(element); 1452 } else { 1453 element = aaRecreateElementFromEntry(p, elementEntry); 1454 1455 if (lastElement === furthestBlock) { 1456 p.activeFormattingElements.bookmark = elementEntry; 1457 } 1458 1459 p.treeAdapter.detachNode(lastElement); 1460 p.treeAdapter.appendChild(element, lastElement); 1461 lastElement = element; 1462 } 1463 } 1464 1465 return lastElement; 1466} 1467 1468//Step 13.7 of the algorithm 1469function aaRecreateElementFromEntry<T extends TreeAdapterTypeMap>( 1470 p: Parser<T>, 1471 elementEntry: ElementEntry<T>, 1472): T['element'] { 1473 const ns = p.treeAdapter.getNamespaceURI(elementEntry.element); 1474 const newElement = p.treeAdapter.createElement(elementEntry.token.tagName, ns, elementEntry.token.attrs); 1475 1476 p.openElements.replace(elementEntry.element, newElement); 1477 elementEntry.element = newElement; 1478 1479 return newElement; 1480} 1481 1482//Step 14 of the algorithm 1483function aaInsertLastNodeInCommonAncestor<T extends TreeAdapterTypeMap>( 1484 p: Parser<T>, 1485 commonAncestor: T['parentNode'], 1486 lastElement: T['element'], 1487): void { 1488 const tn = p.treeAdapter.getTagName(commonAncestor); 1489 const tid = getTagID(tn); 1490 1491 if (p._isElementCausesFosterParenting(tid)) { 1492 p._fosterParentElement(lastElement); 1493 } else { 1494 const ns = p.treeAdapter.getNamespaceURI(commonAncestor); 1495 1496 if (tid === $.TEMPLATE && ns === NS.HTML) { 1497 commonAncestor = p.treeAdapter.getTemplateContent(commonAncestor); 1498 } 1499 1500 p.treeAdapter.appendChild(commonAncestor, lastElement); 1501 } 1502} 1503 1504//Steps 15-19 of the algorithm 1505function aaReplaceFormattingElement<T extends TreeAdapterTypeMap>( 1506 p: Parser<T>, 1507 furthestBlock: T['parentNode'], 1508 formattingElementEntry: ElementEntry<T>, 1509): void { 1510 const ns = p.treeAdapter.getNamespaceURI(formattingElementEntry.element); 1511 const { token } = formattingElementEntry; 1512 const newElement = p.treeAdapter.createElement(token.tagName, ns, token.attrs); 1513 1514 p._adoptNodes(furthestBlock, newElement); 1515 p.treeAdapter.appendChild(furthestBlock, newElement); 1516 1517 p.activeFormattingElements.insertElementAfterBookmark(newElement, token); 1518 p.activeFormattingElements.removeEntry(formattingElementEntry); 1519 1520 p.openElements.remove(formattingElementEntry.element); 1521 p.openElements.insertAfter(furthestBlock, newElement, token.tagID); 1522} 1523 1524//Algorithm entry point 1525function callAdoptionAgency<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1526 for (let i = 0; i < AA_OUTER_LOOP_ITER; i++) { 1527 const formattingElementEntry = aaObtainFormattingElementEntry(p, token); 1528 1529 if (!formattingElementEntry) { 1530 break; 1531 } 1532 1533 const furthestBlock = aaObtainFurthestBlock(p, formattingElementEntry); 1534 1535 if (!furthestBlock) { 1536 break; 1537 } 1538 1539 p.activeFormattingElements.bookmark = formattingElementEntry; 1540 1541 const lastElement = aaInnerLoop(p, furthestBlock, formattingElementEntry.element); 1542 const commonAncestor = p.openElements.getCommonAncestor(formattingElementEntry.element); 1543 1544 p.treeAdapter.detachNode(lastElement); 1545 if (commonAncestor) aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement); 1546 aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry); 1547 } 1548} 1549 1550//Generic token handlers 1551//------------------------------------------------------------------ 1552function appendComment<T extends TreeAdapterTypeMap>(p: Parser<T>, token: CommentToken): void { 1553 p._appendCommentNode(token, p.openElements.currentTmplContentOrNode); 1554} 1555 1556function appendCommentToRootHtmlElement<T extends TreeAdapterTypeMap>(p: Parser<T>, token: CommentToken): void { 1557 p._appendCommentNode(token, p.openElements.items[0]); 1558} 1559 1560function appendCommentToDocument<T extends TreeAdapterTypeMap>(p: Parser<T>, token: CommentToken): void { 1561 p._appendCommentNode(token, p.document); 1562} 1563 1564function stopParsing<T extends TreeAdapterTypeMap>(p: Parser<T>, token: EOFToken): void { 1565 p.stopped = true; 1566 1567 // NOTE: Set end locations for elements that remain on the open element stack. 1568 if (token.location) { 1569 // NOTE: If we are not in a fragment, `html` and `body` will stay on the stack. 1570 // This is a problem, as we might overwrite their end position here. 1571 const target = p.fragmentContext ? 0 : 2; 1572 for (let i = p.openElements.stackTop; i >= target; i--) { 1573 p._setEndLocation(p.openElements.items[i], token); 1574 } 1575 1576 // Handle `html` and `body` 1577 if (!p.fragmentContext && p.openElements.stackTop >= 0) { 1578 const htmlElement = p.openElements.items[0]; 1579 const htmlLocation = p.treeAdapter.getNodeSourceCodeLocation(htmlElement); 1580 if (htmlLocation && !htmlLocation.endTag) { 1581 p._setEndLocation(htmlElement, token); 1582 1583 if (p.openElements.stackTop >= 1) { 1584 const bodyElement = p.openElements.items[1]; 1585 const bodyLocation = p.treeAdapter.getNodeSourceCodeLocation(bodyElement); 1586 if (bodyLocation && !bodyLocation.endTag) { 1587 p._setEndLocation(bodyElement, token); 1588 } 1589 } 1590 } 1591 } 1592 } 1593} 1594 1595// The "initial" insertion mode 1596//------------------------------------------------------------------ 1597function doctypeInInitialMode<T extends TreeAdapterTypeMap>(p: Parser<T>, token: DoctypeToken): void { 1598 p._setDocumentType(token); 1599 1600 const mode = token.forceQuirks ? DOCUMENT_MODE.QUIRKS : doctype.getDocumentMode(token); 1601 1602 if (!doctype.isConforming(token)) { 1603 p._err(token, ERR.nonConformingDoctype); 1604 } 1605 1606 p.treeAdapter.setDocumentMode(p.document, mode); 1607 1608 p.insertionMode = InsertionMode.BEFORE_HTML; 1609} 1610 1611function tokenInInitialMode<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 1612 p._err(token, ERR.missingDoctype, true); 1613 p.treeAdapter.setDocumentMode(p.document, DOCUMENT_MODE.QUIRKS); 1614 p.insertionMode = InsertionMode.BEFORE_HTML; 1615 p._processToken(token); 1616} 1617 1618// The "before html" insertion mode 1619//------------------------------------------------------------------ 1620function startTagBeforeHtml<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1621 if (token.tagID === $.HTML) { 1622 p._insertElement(token, NS.HTML); 1623 p.insertionMode = InsertionMode.BEFORE_HEAD; 1624 } else { 1625 tokenBeforeHtml(p, token); 1626 } 1627} 1628 1629function endTagBeforeHtml<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1630 const tn = token.tagID; 1631 1632 if (tn === $.HTML || tn === $.HEAD || tn === $.BODY || tn === $.BR) { 1633 tokenBeforeHtml(p, token); 1634 } 1635} 1636 1637function tokenBeforeHtml<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 1638 p._insertFakeRootElement(); 1639 p.insertionMode = InsertionMode.BEFORE_HEAD; 1640 p._processToken(token); 1641} 1642 1643// The "before head" insertion mode 1644//------------------------------------------------------------------ 1645function startTagBeforeHead<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1646 switch (token.tagID) { 1647 case $.HTML: { 1648 startTagInBody(p, token); 1649 break; 1650 } 1651 case $.HEAD: { 1652 p._insertElement(token, NS.HTML); 1653 p.headElement = p.openElements.current; 1654 p.insertionMode = InsertionMode.IN_HEAD; 1655 break; 1656 } 1657 default: { 1658 tokenBeforeHead(p, token); 1659 } 1660 } 1661} 1662 1663function endTagBeforeHead<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1664 const tn = token.tagID; 1665 1666 if (tn === $.HEAD || tn === $.BODY || tn === $.HTML || tn === $.BR) { 1667 tokenBeforeHead(p, token); 1668 } else { 1669 p._err(token, ERR.endTagWithoutMatchingOpenElement); 1670 } 1671} 1672 1673function tokenBeforeHead<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 1674 p._insertFakeElement(TN.HEAD, $.HEAD); 1675 p.headElement = p.openElements.current; 1676 p.insertionMode = InsertionMode.IN_HEAD; 1677 p._processToken(token); 1678} 1679 1680// The "in head" insertion mode 1681//------------------------------------------------------------------ 1682function startTagInHead<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1683 switch (token.tagID) { 1684 case $.HTML: { 1685 startTagInBody(p, token); 1686 break; 1687 } 1688 case $.BASE: 1689 case $.BASEFONT: 1690 case $.BGSOUND: 1691 case $.LINK: 1692 case $.META: { 1693 p._appendElement(token, NS.HTML); 1694 token.ackSelfClosing = true; 1695 break; 1696 } 1697 case $.TITLE: { 1698 p._switchToTextParsing(token, TokenizerMode.RCDATA); 1699 break; 1700 } 1701 case $.NOSCRIPT: { 1702 if (p.options.scriptingEnabled) { 1703 p._switchToTextParsing(token, TokenizerMode.RAWTEXT); 1704 } else { 1705 p._insertElement(token, NS.HTML); 1706 p.insertionMode = InsertionMode.IN_HEAD_NO_SCRIPT; 1707 } 1708 break; 1709 } 1710 case $.NOFRAMES: 1711 case $.STYLE: { 1712 p._switchToTextParsing(token, TokenizerMode.RAWTEXT); 1713 break; 1714 } 1715 case $.SCRIPT: { 1716 p._switchToTextParsing(token, TokenizerMode.SCRIPT_DATA); 1717 break; 1718 } 1719 case $.TEMPLATE: { 1720 p._insertTemplate(token); 1721 p.activeFormattingElements.insertMarker(); 1722 p.framesetOk = false; 1723 p.insertionMode = InsertionMode.IN_TEMPLATE; 1724 p.tmplInsertionModeStack.unshift(InsertionMode.IN_TEMPLATE); 1725 break; 1726 } 1727 case $.HEAD: { 1728 p._err(token, ERR.misplacedStartTagForHeadElement); 1729 break; 1730 } 1731 default: { 1732 tokenInHead(p, token); 1733 } 1734 } 1735} 1736 1737function endTagInHead<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1738 switch (token.tagID) { 1739 case $.HEAD: { 1740 p.openElements.pop(); 1741 p.insertionMode = InsertionMode.AFTER_HEAD; 1742 break; 1743 } 1744 case $.BODY: 1745 case $.BR: 1746 case $.HTML: { 1747 tokenInHead(p, token); 1748 break; 1749 } 1750 case $.TEMPLATE: { 1751 templateEndTagInHead<T>(p, token); 1752 break; 1753 } 1754 default: { 1755 p._err(token, ERR.endTagWithoutMatchingOpenElement); 1756 } 1757 } 1758} 1759 1760function templateEndTagInHead<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1761 if (p.openElements.tmplCount > 0) { 1762 p.openElements.generateImpliedEndTagsThoroughly(); 1763 1764 if (p.openElements.currentTagId !== $.TEMPLATE) { 1765 p._err(token, ERR.closingOfElementWithOpenChildElements); 1766 } 1767 1768 p.openElements.popUntilTagNamePopped($.TEMPLATE); 1769 p.activeFormattingElements.clearToLastMarker(); 1770 p.tmplInsertionModeStack.shift(); 1771 p._resetInsertionMode(); 1772 } else { 1773 p._err(token, ERR.endTagWithoutMatchingOpenElement); 1774 } 1775} 1776 1777function tokenInHead<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 1778 p.openElements.pop(); 1779 p.insertionMode = InsertionMode.AFTER_HEAD; 1780 p._processToken(token); 1781} 1782 1783// The "in head no script" insertion mode 1784//------------------------------------------------------------------ 1785function startTagInHeadNoScript<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1786 switch (token.tagID) { 1787 case $.HTML: { 1788 startTagInBody(p, token); 1789 break; 1790 } 1791 case $.BASEFONT: 1792 case $.BGSOUND: 1793 case $.HEAD: 1794 case $.LINK: 1795 case $.META: 1796 case $.NOFRAMES: 1797 case $.STYLE: { 1798 startTagInHead(p, token); 1799 break; 1800 } 1801 case $.NOSCRIPT: { 1802 p._err(token, ERR.nestedNoscriptInHead); 1803 break; 1804 } 1805 default: { 1806 tokenInHeadNoScript(p, token); 1807 } 1808 } 1809} 1810 1811function endTagInHeadNoScript<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1812 switch (token.tagID) { 1813 case $.NOSCRIPT: { 1814 p.openElements.pop(); 1815 p.insertionMode = InsertionMode.IN_HEAD; 1816 break; 1817 } 1818 case $.BR: { 1819 tokenInHeadNoScript(p, token); 1820 break; 1821 } 1822 default: { 1823 p._err(token, ERR.endTagWithoutMatchingOpenElement); 1824 } 1825 } 1826} 1827 1828function tokenInHeadNoScript<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 1829 const errCode = token.type === TokenType.EOF ? ERR.openElementsLeftAfterEof : ERR.disallowedContentInNoscriptInHead; 1830 1831 p._err(token, errCode); 1832 p.openElements.pop(); 1833 p.insertionMode = InsertionMode.IN_HEAD; 1834 p._processToken(token); 1835} 1836 1837// The "after head" insertion mode 1838//------------------------------------------------------------------ 1839function startTagAfterHead<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1840 switch (token.tagID) { 1841 case $.HTML: { 1842 startTagInBody(p, token); 1843 break; 1844 } 1845 case $.BODY: { 1846 p._insertElement(token, NS.HTML); 1847 p.framesetOk = false; 1848 p.insertionMode = InsertionMode.IN_BODY; 1849 break; 1850 } 1851 case $.FRAMESET: { 1852 p._insertElement(token, NS.HTML); 1853 p.insertionMode = InsertionMode.IN_FRAMESET; 1854 break; 1855 } 1856 case $.BASE: 1857 case $.BASEFONT: 1858 case $.BGSOUND: 1859 case $.LINK: 1860 case $.META: 1861 case $.NOFRAMES: 1862 case $.SCRIPT: 1863 case $.STYLE: 1864 case $.TEMPLATE: 1865 case $.TITLE: { 1866 p._err(token, ERR.abandonedHeadElementChild); 1867 p.openElements.push(p.headElement!, $.HEAD); 1868 startTagInHead(p, token); 1869 p.openElements.remove(p.headElement!); 1870 break; 1871 } 1872 case $.HEAD: { 1873 p._err(token, ERR.misplacedStartTagForHeadElement); 1874 break; 1875 } 1876 default: { 1877 tokenAfterHead(p, token); 1878 } 1879 } 1880} 1881 1882function endTagAfterHead<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1883 switch (token.tagID) { 1884 case $.BODY: 1885 case $.HTML: 1886 case $.BR: { 1887 tokenAfterHead(p, token); 1888 break; 1889 } 1890 case $.TEMPLATE: { 1891 templateEndTagInHead(p, token); 1892 break; 1893 } 1894 default: { 1895 p._err(token, ERR.endTagWithoutMatchingOpenElement); 1896 } 1897 } 1898} 1899 1900function tokenAfterHead<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 1901 p._insertFakeElement(TN.BODY, $.BODY); 1902 p.insertionMode = InsertionMode.IN_BODY; 1903 modeInBody(p, token); 1904} 1905 1906// The "in body" insertion mode 1907//------------------------------------------------------------------ 1908function modeInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 1909 switch (token.type) { 1910 case TokenType.CHARACTER: { 1911 characterInBody(p, token); 1912 break; 1913 } 1914 case TokenType.WHITESPACE_CHARACTER: { 1915 whitespaceCharacterInBody(p, token); 1916 break; 1917 } 1918 case TokenType.COMMENT: { 1919 appendComment(p, token); 1920 break; 1921 } 1922 case TokenType.START_TAG: { 1923 startTagInBody(p, token); 1924 break; 1925 } 1926 case TokenType.END_TAG: { 1927 endTagInBody(p, token); 1928 break; 1929 } 1930 case TokenType.EOF: { 1931 eofInBody(p, token); 1932 break; 1933 } 1934 default: 1935 // Do nothing 1936 } 1937} 1938 1939function whitespaceCharacterInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: CharacterToken): void { 1940 p._reconstructActiveFormattingElements(); 1941 p._insertCharacters(token); 1942} 1943 1944function characterInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: CharacterToken): void { 1945 p._reconstructActiveFormattingElements(); 1946 p._insertCharacters(token); 1947 p.framesetOk = false; 1948} 1949 1950function htmlStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1951 if (p.openElements.tmplCount === 0) { 1952 p.treeAdapter.adoptAttributes(p.openElements.items[0], token.attrs); 1953 } 1954} 1955 1956function bodyStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1957 const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement(); 1958 1959 if (bodyElement && p.openElements.tmplCount === 0) { 1960 p.framesetOk = false; 1961 p.treeAdapter.adoptAttributes(bodyElement, token.attrs); 1962 } 1963} 1964 1965function framesetStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1966 const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement(); 1967 1968 if (p.framesetOk && bodyElement) { 1969 p.treeAdapter.detachNode(bodyElement); 1970 p.openElements.popAllUpToHtmlElement(); 1971 p._insertElement(token, NS.HTML); 1972 p.insertionMode = InsertionMode.IN_FRAMESET; 1973 } 1974} 1975 1976function addressStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1977 if (p.openElements.hasInButtonScope($.P)) { 1978 p._closePElement(); 1979 } 1980 1981 p._insertElement(token, NS.HTML); 1982} 1983 1984function numberedHeaderStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1985 if (p.openElements.hasInButtonScope($.P)) { 1986 p._closePElement(); 1987 } 1988 1989 if (NUMBERED_HEADERS.has(p.openElements.currentTagId)) { 1990 p.openElements.pop(); 1991 } 1992 1993 p._insertElement(token, NS.HTML); 1994} 1995 1996function preStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 1997 if (p.openElements.hasInButtonScope($.P)) { 1998 p._closePElement(); 1999 } 2000 2001 p._insertElement(token, NS.HTML); 2002 //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move 2003 //on to the next one. (Newlines at the start of pre blocks are ignored as an authoring convenience.) 2004 p.skipNextNewLine = true; 2005 p.framesetOk = false; 2006} 2007 2008function formStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2009 const inTemplate = p.openElements.tmplCount > 0; 2010 2011 if (!p.formElement || inTemplate) { 2012 if (p.openElements.hasInButtonScope($.P)) { 2013 p._closePElement(); 2014 } 2015 2016 p._insertElement(token, NS.HTML); 2017 2018 if (!inTemplate) { 2019 p.formElement = p.openElements.current; 2020 } 2021 } 2022} 2023 2024function listItemStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2025 p.framesetOk = false; 2026 2027 const tn = token.tagID; 2028 2029 for (let i = p.openElements.stackTop; i >= 0; i--) { 2030 const elementId = p.openElements.tagIDs[i]; 2031 2032 if ( 2033 (tn === $.LI && elementId === $.LI) || 2034 ((tn === $.DD || tn === $.DT) && (elementId === $.DD || elementId === $.DT)) 2035 ) { 2036 p.openElements.generateImpliedEndTagsWithExclusion(elementId); 2037 p.openElements.popUntilTagNamePopped(elementId); 2038 break; 2039 } 2040 2041 if ( 2042 elementId !== $.ADDRESS && 2043 elementId !== $.DIV && 2044 elementId !== $.P && 2045 p._isSpecialElement(p.openElements.items[i], elementId) 2046 ) { 2047 break; 2048 } 2049 } 2050 2051 if (p.openElements.hasInButtonScope($.P)) { 2052 p._closePElement(); 2053 } 2054 2055 p._insertElement(token, NS.HTML); 2056} 2057 2058function plaintextStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2059 if (p.openElements.hasInButtonScope($.P)) { 2060 p._closePElement(); 2061 } 2062 2063 p._insertElement(token, NS.HTML); 2064 p.tokenizer.state = TokenizerMode.PLAINTEXT; 2065} 2066 2067function buttonStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2068 if (p.openElements.hasInScope($.BUTTON)) { 2069 p.openElements.generateImpliedEndTags(); 2070 p.openElements.popUntilTagNamePopped($.BUTTON); 2071 } 2072 2073 p._reconstructActiveFormattingElements(); 2074 p._insertElement(token, NS.HTML); 2075 p.framesetOk = false; 2076} 2077 2078function aStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2079 const activeElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName(TN.A); 2080 2081 if (activeElementEntry) { 2082 callAdoptionAgency(p, token); 2083 p.openElements.remove(activeElementEntry.element); 2084 p.activeFormattingElements.removeEntry(activeElementEntry); 2085 } 2086 2087 p._reconstructActiveFormattingElements(); 2088 p._insertElement(token, NS.HTML); 2089 p.activeFormattingElements.pushElement(p.openElements.current, token); 2090} 2091 2092function bStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2093 p._reconstructActiveFormattingElements(); 2094 p._insertElement(token, NS.HTML); 2095 p.activeFormattingElements.pushElement(p.openElements.current, token); 2096} 2097 2098function nobrStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2099 p._reconstructActiveFormattingElements(); 2100 2101 if (p.openElements.hasInScope($.NOBR)) { 2102 callAdoptionAgency(p, token); 2103 p._reconstructActiveFormattingElements(); 2104 } 2105 2106 p._insertElement(token, NS.HTML); 2107 p.activeFormattingElements.pushElement(p.openElements.current, token); 2108} 2109 2110function appletStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2111 p._reconstructActiveFormattingElements(); 2112 p._insertElement(token, NS.HTML); 2113 p.activeFormattingElements.insertMarker(); 2114 p.framesetOk = false; 2115} 2116 2117function tableStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2118 if (p.treeAdapter.getDocumentMode(p.document) !== DOCUMENT_MODE.QUIRKS && p.openElements.hasInButtonScope($.P)) { 2119 p._closePElement(); 2120 } 2121 2122 p._insertElement(token, NS.HTML); 2123 p.framesetOk = false; 2124 p.insertionMode = InsertionMode.IN_TABLE; 2125} 2126 2127function areaStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2128 p._reconstructActiveFormattingElements(); 2129 p._appendElement(token, NS.HTML); 2130 p.framesetOk = false; 2131 token.ackSelfClosing = true; 2132} 2133 2134function isHiddenInput(token: TagToken): boolean { 2135 const inputType = getTokenAttr(token, ATTRS.TYPE); 2136 2137 return inputType != null && inputType.toLowerCase() === HIDDEN_INPUT_TYPE; 2138} 2139 2140function inputStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2141 p._reconstructActiveFormattingElements(); 2142 p._appendElement(token, NS.HTML); 2143 2144 if (!isHiddenInput(token)) { 2145 p.framesetOk = false; 2146 } 2147 2148 token.ackSelfClosing = true; 2149} 2150 2151function paramStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2152 p._appendElement(token, NS.HTML); 2153 token.ackSelfClosing = true; 2154} 2155 2156function hrStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2157 if (p.openElements.hasInButtonScope($.P)) { 2158 p._closePElement(); 2159 } 2160 2161 p._appendElement(token, NS.HTML); 2162 p.framesetOk = false; 2163 token.ackSelfClosing = true; 2164} 2165 2166function imageStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2167 token.tagName = TN.IMG; 2168 token.tagID = $.IMG; 2169 areaStartTagInBody(p, token); 2170} 2171 2172function textareaStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2173 p._insertElement(token, NS.HTML); 2174 //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move 2175 //on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.) 2176 p.skipNextNewLine = true; 2177 p.tokenizer.state = TokenizerMode.RCDATA; 2178 p.originalInsertionMode = p.insertionMode; 2179 p.framesetOk = false; 2180 p.insertionMode = InsertionMode.TEXT; 2181} 2182 2183function xmpStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2184 if (p.openElements.hasInButtonScope($.P)) { 2185 p._closePElement(); 2186 } 2187 2188 p._reconstructActiveFormattingElements(); 2189 p.framesetOk = false; 2190 p._switchToTextParsing(token, TokenizerMode.RAWTEXT); 2191} 2192 2193function iframeStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2194 p.framesetOk = false; 2195 p._switchToTextParsing(token, TokenizerMode.RAWTEXT); 2196} 2197 2198//NOTE: here we assume that we always act as a user agent with enabled plugins/frames, so we parse 2199//<noembed>/<noframes> as rawtext. 2200function rawTextStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2201 p._switchToTextParsing(token, TokenizerMode.RAWTEXT); 2202} 2203 2204function selectStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2205 p._reconstructActiveFormattingElements(); 2206 p._insertElement(token, NS.HTML); 2207 p.framesetOk = false; 2208 2209 p.insertionMode = 2210 p.insertionMode === InsertionMode.IN_TABLE || 2211 p.insertionMode === InsertionMode.IN_CAPTION || 2212 p.insertionMode === InsertionMode.IN_TABLE_BODY || 2213 p.insertionMode === InsertionMode.IN_ROW || 2214 p.insertionMode === InsertionMode.IN_CELL 2215 ? InsertionMode.IN_SELECT_IN_TABLE 2216 : InsertionMode.IN_SELECT; 2217} 2218 2219function optgroupStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2220 if (p.openElements.currentTagId === $.OPTION) { 2221 p.openElements.pop(); 2222 } 2223 2224 p._reconstructActiveFormattingElements(); 2225 p._insertElement(token, NS.HTML); 2226} 2227 2228function rbStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2229 if (p.openElements.hasInScope($.RUBY)) { 2230 p.openElements.generateImpliedEndTags(); 2231 } 2232 2233 p._insertElement(token, NS.HTML); 2234} 2235 2236function rtStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2237 if (p.openElements.hasInScope($.RUBY)) { 2238 p.openElements.generateImpliedEndTagsWithExclusion($.RTC); 2239 } 2240 2241 p._insertElement(token, NS.HTML); 2242} 2243 2244function mathStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2245 p._reconstructActiveFormattingElements(); 2246 2247 if (token.selfClosing) { 2248 p._appendElement(token, NS.MATHML); 2249 } else { 2250 p._insertElement(token, NS.MATHML); 2251 } 2252 2253 token.ackSelfClosing = true; 2254} 2255 2256function svgStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2257 p._reconstructActiveFormattingElements(); 2258 2259 if (token.selfClosing) { 2260 p._appendElement(token, NS.SVG); 2261 } else { 2262 p._insertElement(token, NS.SVG); 2263 } 2264 2265 token.ackSelfClosing = true; 2266} 2267 2268function genericStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2269 p._reconstructActiveFormattingElements(); 2270 p._insertElement(token, NS.HTML); 2271} 2272 2273function startTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2274 switch (token.tagID) { 2275 case $.I: 2276 case $.S: 2277 case $.B: 2278 case $.U: 2279 case $.EM: 2280 case $.TT: 2281 case $.BIG: 2282 case $.CODE: 2283 case $.FONT: 2284 case $.SMALL: 2285 case $.STRIKE: 2286 case $.STRONG: { 2287 bStartTagInBody(p, token); 2288 break; 2289 } 2290 case $.A: { 2291 aStartTagInBody(p, token); 2292 break; 2293 } 2294 case $.H1: 2295 case $.H2: 2296 case $.H3: 2297 case $.H4: 2298 case $.H5: 2299 case $.H6: { 2300 numberedHeaderStartTagInBody(p, token); 2301 break; 2302 } 2303 case $.P: 2304 case $.DL: 2305 case $.OL: 2306 case $.UL: 2307 case $.DIV: 2308 case $.DIR: 2309 case $.NAV: 2310 case $.MAIN: 2311 case $.MENU: 2312 case $.ASIDE: 2313 case $.CENTER: 2314 case $.FIGURE: 2315 case $.FOOTER: 2316 case $.HEADER: 2317 case $.HGROUP: 2318 case $.DIALOG: 2319 case $.DETAILS: 2320 case $.ADDRESS: 2321 case $.ARTICLE: 2322 case $.SEARCH: 2323 case $.SECTION: 2324 case $.SUMMARY: 2325 case $.FIELDSET: 2326 case $.BLOCKQUOTE: 2327 case $.FIGCAPTION: { 2328 addressStartTagInBody(p, token); 2329 break; 2330 } 2331 case $.LI: 2332 case $.DD: 2333 case $.DT: { 2334 listItemStartTagInBody(p, token); 2335 break; 2336 } 2337 case $.BR: 2338 case $.IMG: 2339 case $.WBR: 2340 case $.AREA: 2341 case $.EMBED: 2342 case $.WEB: 2343 case $.XCOMPONENT: 2344 case $.RATING: 2345 case $.CANVAS: 2346 case $.CAMERA: 2347 case $.AUDIO: 2348 case $.VIDEO: 2349 case $.SWITCH: 2350 case $.QRCODE: 2351 case $.PICKER_VIEW: 2352 case $.PICKER: 2353 case $.PROGRESS: 2354 case $.SLIDER: 2355 case $.CHART: 2356 case $.CALENDAR: 2357 case $.DIVIDER: 2358 case $.IMAGE_ANIMATOR: 2359 case $.KEYGEN: { 2360 areaStartTagInBody(p, token); 2361 break; 2362 } 2363 case $.HR: { 2364 hrStartTagInBody(p, token); 2365 break; 2366 } 2367 case $.RB: 2368 case $.RTC: { 2369 rbStartTagInBody(p, token); 2370 break; 2371 } 2372 case $.RT: 2373 case $.RP: { 2374 rtStartTagInBody(p, token); 2375 break; 2376 } 2377 case $.PRE: 2378 case $.LISTING: { 2379 preStartTagInBody(p, token); 2380 break; 2381 } 2382 case $.XMP: { 2383 xmpStartTagInBody(p, token); 2384 break; 2385 } 2386 case $.SVG: { 2387 svgStartTagInBody(p, token); 2388 break; 2389 } 2390 case $.HTML: { 2391 htmlStartTagInBody(p, token); 2392 break; 2393 } 2394 case $.BASE: 2395 case $.LINK: 2396 case $.META: 2397 case $.STYLE: 2398 case $.TITLE: 2399 case $.SCRIPT: 2400 case $.BGSOUND: 2401 case $.BASEFONT: 2402 case $.TEMPLATE: { 2403 startTagInHead(p, token); 2404 break; 2405 } 2406 case $.BODY: { 2407 bodyStartTagInBody(p, token); 2408 break; 2409 } 2410 case $.FORM: { 2411 formStartTagInBody(p, token); 2412 break; 2413 } 2414 case $.NOBR: { 2415 nobrStartTagInBody(p, token); 2416 break; 2417 } 2418 case $.MATH: { 2419 mathStartTagInBody(p, token); 2420 break; 2421 } 2422 case $.TABLE: { 2423 tableStartTagInBody(p, token); 2424 break; 2425 } 2426 case $.INPUT: { 2427 inputStartTagInBody(p, token); 2428 break; 2429 } 2430 case $.PARAM: 2431 case $.TRACK: 2432 case $.SOURCE: { 2433 paramStartTagInBody(p, token); 2434 break; 2435 } 2436 case $.IMAGE: { 2437 imageStartTagInBody(p, token); 2438 break; 2439 } 2440 case $.BUTTON: { 2441 buttonStartTagInBody(p, token); 2442 break; 2443 } 2444 case $.APPLET: 2445 case $.OBJECT: 2446 case $.MARQUEE: { 2447 appletStartTagInBody(p, token); 2448 break; 2449 } 2450 case $.IFRAME: { 2451 iframeStartTagInBody(p, token); 2452 break; 2453 } 2454 case $.SELECT: { 2455 selectStartTagInBody(p, token); 2456 break; 2457 } 2458 case $.OPTION: 2459 case $.OPTGROUP: { 2460 optgroupStartTagInBody(p, token); 2461 break; 2462 } 2463 case $.NOEMBED: 2464 case $.NOFRAMES: { 2465 rawTextStartTagInBody(p, token); 2466 break; 2467 } 2468 case $.FRAMESET: { 2469 framesetStartTagInBody(p, token); 2470 break; 2471 } 2472 case $.TEXTAREA: { 2473 textareaStartTagInBody(p, token); 2474 break; 2475 } 2476 case $.NOSCRIPT: { 2477 if (p.options.scriptingEnabled) { 2478 rawTextStartTagInBody(p, token); 2479 } else { 2480 genericStartTagInBody(p, token); 2481 } 2482 break; 2483 } 2484 case $.PLAINTEXT: { 2485 plaintextStartTagInBody(p, token); 2486 break; 2487 } 2488 2489 case $.COL: 2490 case $.TH: 2491 case $.TD: 2492 case $.TR: 2493 case $.HEAD: 2494 case $.FRAME: 2495 case $.TBODY: 2496 case $.TFOOT: 2497 case $.THEAD: 2498 case $.CAPTION: 2499 case $.COLGROUP: { 2500 // Ignore token 2501 break; 2502 } 2503 default: { 2504 genericStartTagInBody(p, token); 2505 } 2506 } 2507} 2508 2509function bodyEndTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2510 if (p.openElements.hasInScope($.BODY)) { 2511 p.insertionMode = InsertionMode.AFTER_BODY; 2512 2513 //NOTE: <body> is never popped from the stack, so we need to updated 2514 //the end location explicitly. 2515 if (p.options.sourceCodeLocationInfo) { 2516 const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement(); 2517 if (bodyElement) { 2518 p._setEndLocation(bodyElement, token); 2519 } 2520 } 2521 } 2522} 2523 2524function htmlEndTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2525 if (p.openElements.hasInScope($.BODY)) { 2526 p.insertionMode = InsertionMode.AFTER_BODY; 2527 endTagAfterBody(p, token); 2528 } 2529} 2530 2531function addressEndTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2532 const tn = token.tagID; 2533 2534 if (p.openElements.hasInScope(tn)) { 2535 p.openElements.generateImpliedEndTags(); 2536 p.openElements.popUntilTagNamePopped(tn); 2537 } 2538} 2539 2540function formEndTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>): void { 2541 const inTemplate = p.openElements.tmplCount > 0; 2542 const { formElement } = p; 2543 2544 if (!inTemplate) { 2545 p.formElement = null; 2546 } 2547 2548 if ((formElement || inTemplate) && p.openElements.hasInScope($.FORM)) { 2549 p.openElements.generateImpliedEndTags(); 2550 2551 if (inTemplate) { 2552 p.openElements.popUntilTagNamePopped($.FORM); 2553 } else if (formElement) { 2554 p.openElements.remove(formElement); 2555 } 2556 } 2557} 2558 2559function pEndTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>): void { 2560 if (!p.openElements.hasInButtonScope($.P)) { 2561 p._insertFakeElement(TN.P, $.P); 2562 } 2563 2564 p._closePElement(); 2565} 2566 2567function liEndTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>): void { 2568 if (p.openElements.hasInListItemScope($.LI)) { 2569 p.openElements.generateImpliedEndTagsWithExclusion($.LI); 2570 p.openElements.popUntilTagNamePopped($.LI); 2571 } 2572} 2573 2574function ddEndTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2575 const tn = token.tagID; 2576 2577 if (p.openElements.hasInScope(tn)) { 2578 p.openElements.generateImpliedEndTagsWithExclusion(tn); 2579 p.openElements.popUntilTagNamePopped(tn); 2580 } 2581} 2582 2583function numberedHeaderEndTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>): void { 2584 if (p.openElements.hasNumberedHeaderInScope()) { 2585 p.openElements.generateImpliedEndTags(); 2586 p.openElements.popUntilNumberedHeaderPopped(); 2587 } 2588} 2589 2590function appletEndTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2591 const tn = token.tagID; 2592 2593 if (p.openElements.hasInScope(tn)) { 2594 p.openElements.generateImpliedEndTags(); 2595 p.openElements.popUntilTagNamePopped(tn); 2596 p.activeFormattingElements.clearToLastMarker(); 2597 } 2598} 2599 2600function brEndTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>): void { 2601 p._reconstructActiveFormattingElements(); 2602 p._insertFakeElement(TN.BR, $.BR); 2603 p.openElements.pop(); 2604 p.framesetOk = false; 2605} 2606 2607function genericEndTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2608 const tn = token.tagName; 2609 const tid = token.tagID; 2610 2611 for (let i = p.openElements.stackTop; i > 0; i--) { 2612 const element = p.openElements.items[i]; 2613 const elementId = p.openElements.tagIDs[i]; 2614 2615 // Compare the tag name here, as the tag might not be a known tag with an ID. 2616 if (tid === elementId && (tid !== $.UNKNOWN || p.treeAdapter.getTagName(element) === tn)) { 2617 p.openElements.generateImpliedEndTagsWithExclusion(tid); 2618 if (p.openElements.stackTop >= i) p.openElements.shortenToLength(i); 2619 break; 2620 } 2621 2622 if (p._isSpecialElement(element, elementId)) { 2623 break; 2624 } 2625 } 2626} 2627 2628function endTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2629 switch (token.tagID) { 2630 case $.A: 2631 case $.B: 2632 case $.I: 2633 case $.S: 2634 case $.U: 2635 case $.EM: 2636 case $.TT: 2637 case $.BIG: 2638 case $.CODE: 2639 case $.FONT: 2640 case $.NOBR: 2641 case $.SMALL: 2642 case $.STRIKE: 2643 case $.STRONG: { 2644 callAdoptionAgency(p, token); 2645 break; 2646 } 2647 case $.P: { 2648 pEndTagInBody(p); 2649 break; 2650 } 2651 case $.DL: 2652 case $.UL: 2653 case $.OL: 2654 case $.DIR: 2655 case $.DIV: 2656 case $.NAV: 2657 case $.PRE: 2658 case $.MAIN: 2659 case $.MENU: 2660 case $.ASIDE: 2661 case $.BUTTON: 2662 case $.CENTER: 2663 case $.FIGURE: 2664 case $.FOOTER: 2665 case $.HEADER: 2666 case $.HGROUP: 2667 case $.DIALOG: 2668 case $.ADDRESS: 2669 case $.ARTICLE: 2670 case $.DETAILS: 2671 case $.SEARCH: 2672 case $.SECTION: 2673 case $.SUMMARY: 2674 case $.LISTING: 2675 case $.FIELDSET: 2676 case $.BLOCKQUOTE: 2677 case $.FIGCAPTION: { 2678 addressEndTagInBody(p, token); 2679 break; 2680 } 2681 case $.LI: { 2682 liEndTagInBody(p); 2683 break; 2684 } 2685 case $.DD: 2686 case $.DT: { 2687 ddEndTagInBody(p, token); 2688 break; 2689 } 2690 case $.H1: 2691 case $.H2: 2692 case $.H3: 2693 case $.H4: 2694 case $.H5: 2695 case $.H6: { 2696 numberedHeaderEndTagInBody(p); 2697 break; 2698 } 2699 case $.BR: { 2700 brEndTagInBody(p); 2701 break; 2702 } 2703 case $.BODY: { 2704 bodyEndTagInBody(p, token); 2705 break; 2706 } 2707 case $.HTML: { 2708 htmlEndTagInBody(p, token); 2709 break; 2710 } 2711 case $.FORM: { 2712 formEndTagInBody(p); 2713 break; 2714 } 2715 case $.APPLET: 2716 case $.OBJECT: 2717 case $.MARQUEE: { 2718 appletEndTagInBody(p, token); 2719 break; 2720 } 2721 case $.TEMPLATE: { 2722 templateEndTagInHead(p, token); 2723 break; 2724 } 2725 default: { 2726 genericEndTagInBody(p, token); 2727 } 2728 } 2729} 2730 2731function eofInBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: EOFToken): void { 2732 if (p.tmplInsertionModeStack.length > 0) { 2733 eofInTemplate(p, token); 2734 } else { 2735 stopParsing(p, token); 2736 } 2737} 2738 2739// The "text" insertion mode 2740//------------------------------------------------------------------ 2741function endTagInText<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2742 if (token.tagID === $.SCRIPT) { 2743 p.scriptHandler?.(p.openElements.current); 2744 } 2745 2746 p.openElements.pop(); 2747 p.insertionMode = p.originalInsertionMode; 2748} 2749 2750function eofInText<T extends TreeAdapterTypeMap>(p: Parser<T>, token: EOFToken): void { 2751 p._err(token, ERR.eofInElementThatCanContainOnlyText); 2752 p.openElements.pop(); 2753 p.insertionMode = p.originalInsertionMode; 2754 p.onEof(token); 2755} 2756 2757// The "in table" insertion mode 2758//------------------------------------------------------------------ 2759function characterInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: CharacterToken): void { 2760 if (TABLE_STRUCTURE_TAGS.has(p.openElements.currentTagId)) { 2761 p.pendingCharacterTokens.length = 0; 2762 p.hasNonWhitespacePendingCharacterToken = false; 2763 p.originalInsertionMode = p.insertionMode; 2764 p.insertionMode = InsertionMode.IN_TABLE_TEXT; 2765 2766 switch (token.type) { 2767 case TokenType.CHARACTER: { 2768 characterInTableText(p, token); 2769 break; 2770 } 2771 case TokenType.WHITESPACE_CHARACTER: { 2772 whitespaceCharacterInTableText(p, token); 2773 break; 2774 } 2775 // Ignore null 2776 } 2777 } else { 2778 tokenInTable(p, token); 2779 } 2780} 2781 2782function captionStartTagInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2783 p.openElements.clearBackToTableContext(); 2784 p.activeFormattingElements.insertMarker(); 2785 p._insertElement(token, NS.HTML); 2786 p.insertionMode = InsertionMode.IN_CAPTION; 2787} 2788 2789function colgroupStartTagInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2790 p.openElements.clearBackToTableContext(); 2791 p._insertElement(token, NS.HTML); 2792 p.insertionMode = InsertionMode.IN_COLUMN_GROUP; 2793} 2794 2795function colStartTagInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2796 p.openElements.clearBackToTableContext(); 2797 p._insertFakeElement(TN.COLGROUP, $.COLGROUP); 2798 p.insertionMode = InsertionMode.IN_COLUMN_GROUP; 2799 startTagInColumnGroup(p, token); 2800} 2801 2802function tbodyStartTagInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2803 p.openElements.clearBackToTableContext(); 2804 p._insertElement(token, NS.HTML); 2805 p.insertionMode = InsertionMode.IN_TABLE_BODY; 2806} 2807 2808function tdStartTagInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2809 p.openElements.clearBackToTableContext(); 2810 p._insertFakeElement(TN.TBODY, $.TBODY); 2811 p.insertionMode = InsertionMode.IN_TABLE_BODY; 2812 startTagInTableBody(p, token); 2813} 2814 2815function tableStartTagInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2816 if (p.openElements.hasInTableScope($.TABLE)) { 2817 p.openElements.popUntilTagNamePopped($.TABLE); 2818 p._resetInsertionMode(); 2819 p._processStartTag(token); 2820 } 2821} 2822 2823function inputStartTagInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2824 if (isHiddenInput(token)) { 2825 p._appendElement(token, NS.HTML); 2826 } else { 2827 tokenInTable(p, token); 2828 } 2829 2830 token.ackSelfClosing = true; 2831} 2832 2833function formStartTagInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2834 if (!p.formElement && p.openElements.tmplCount === 0) { 2835 p._insertElement(token, NS.HTML); 2836 p.formElement = p.openElements.current; 2837 p.openElements.pop(); 2838 } 2839} 2840 2841function startTagInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2842 switch (token.tagID) { 2843 case $.TD: 2844 case $.TH: 2845 case $.TR: { 2846 tdStartTagInTable(p, token); 2847 break; 2848 } 2849 case $.STYLE: 2850 case $.SCRIPT: 2851 case $.TEMPLATE: { 2852 startTagInHead(p, token); 2853 break; 2854 } 2855 case $.COL: { 2856 colStartTagInTable(p, token); 2857 break; 2858 } 2859 case $.FORM: { 2860 formStartTagInTable(p, token); 2861 break; 2862 } 2863 case $.TABLE: { 2864 tableStartTagInTable(p, token); 2865 break; 2866 } 2867 case $.TBODY: 2868 case $.TFOOT: 2869 case $.THEAD: { 2870 tbodyStartTagInTable(p, token); 2871 break; 2872 } 2873 case $.INPUT: { 2874 inputStartTagInTable(p, token); 2875 break; 2876 } 2877 case $.CAPTION: { 2878 captionStartTagInTable(p, token); 2879 break; 2880 } 2881 case $.COLGROUP: { 2882 colgroupStartTagInTable(p, token); 2883 break; 2884 } 2885 default: { 2886 tokenInTable(p, token); 2887 } 2888 } 2889} 2890 2891function endTagInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2892 switch (token.tagID) { 2893 case $.TABLE: { 2894 if (p.openElements.hasInTableScope($.TABLE)) { 2895 p.openElements.popUntilTagNamePopped($.TABLE); 2896 p._resetInsertionMode(); 2897 } 2898 break; 2899 } 2900 case $.TEMPLATE: { 2901 templateEndTagInHead(p, token); 2902 break; 2903 } 2904 case $.BODY: 2905 case $.CAPTION: 2906 case $.COL: 2907 case $.COLGROUP: 2908 case $.HTML: 2909 case $.TBODY: 2910 case $.TD: 2911 case $.TFOOT: 2912 case $.TH: 2913 case $.THEAD: 2914 case $.TR: { 2915 // Ignore token 2916 break; 2917 } 2918 default: { 2919 tokenInTable(p, token); 2920 } 2921 } 2922} 2923 2924function tokenInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 2925 const savedFosterParentingState = p.fosterParentingEnabled; 2926 2927 p.fosterParentingEnabled = true; 2928 // Process token in `In Body` mode 2929 modeInBody(p, token); 2930 p.fosterParentingEnabled = savedFosterParentingState; 2931} 2932 2933// The "in table text" insertion mode 2934//------------------------------------------------------------------ 2935function whitespaceCharacterInTableText<T extends TreeAdapterTypeMap>(p: Parser<T>, token: CharacterToken): void { 2936 p.pendingCharacterTokens.push(token); 2937} 2938 2939function characterInTableText<T extends TreeAdapterTypeMap>(p: Parser<T>, token: CharacterToken): void { 2940 p.pendingCharacterTokens.push(token); 2941 p.hasNonWhitespacePendingCharacterToken = true; 2942} 2943 2944function tokenInTableText<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 2945 let i = 0; 2946 2947 if (p.hasNonWhitespacePendingCharacterToken) { 2948 for (; i < p.pendingCharacterTokens.length; i++) { 2949 tokenInTable(p, p.pendingCharacterTokens[i]); 2950 } 2951 } else { 2952 for (; i < p.pendingCharacterTokens.length; i++) { 2953 p._insertCharacters(p.pendingCharacterTokens[i]); 2954 } 2955 } 2956 2957 p.insertionMode = p.originalInsertionMode; 2958 p._processToken(token); 2959} 2960 2961// The "in caption" insertion mode 2962//------------------------------------------------------------------ 2963const TABLE_VOID_ELEMENTS = new Set([$.CAPTION, $.COL, $.COLGROUP, $.TBODY, $.TD, $.TFOOT, $.TH, $.THEAD, $.TR]); 2964 2965function startTagInCaption<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2966 const tn = token.tagID; 2967 2968 if (TABLE_VOID_ELEMENTS.has(tn)) { 2969 if (p.openElements.hasInTableScope($.CAPTION)) { 2970 p.openElements.generateImpliedEndTags(); 2971 p.openElements.popUntilTagNamePopped($.CAPTION); 2972 p.activeFormattingElements.clearToLastMarker(); 2973 p.insertionMode = InsertionMode.IN_TABLE; 2974 startTagInTable(p, token); 2975 } 2976 } else { 2977 startTagInBody(p, token); 2978 } 2979} 2980 2981function endTagInCaption<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 2982 const tn = token.tagID; 2983 2984 switch (tn) { 2985 case $.CAPTION: 2986 case $.TABLE: { 2987 if (p.openElements.hasInTableScope($.CAPTION)) { 2988 p.openElements.generateImpliedEndTags(); 2989 p.openElements.popUntilTagNamePopped($.CAPTION); 2990 p.activeFormattingElements.clearToLastMarker(); 2991 p.insertionMode = InsertionMode.IN_TABLE; 2992 2993 if (tn === $.TABLE) { 2994 endTagInTable(p, token); 2995 } 2996 } 2997 break; 2998 } 2999 case $.BODY: 3000 case $.COL: 3001 case $.COLGROUP: 3002 case $.HTML: 3003 case $.TBODY: 3004 case $.TD: 3005 case $.TFOOT: 3006 case $.TH: 3007 case $.THEAD: 3008 case $.TR: { 3009 // Ignore token 3010 break; 3011 } 3012 default: { 3013 endTagInBody(p, token); 3014 } 3015 } 3016} 3017 3018// The "in column group" insertion mode 3019//------------------------------------------------------------------ 3020function startTagInColumnGroup<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3021 switch (token.tagID) { 3022 case $.HTML: { 3023 startTagInBody(p, token); 3024 break; 3025 } 3026 case $.COL: { 3027 p._appendElement(token, NS.HTML); 3028 token.ackSelfClosing = true; 3029 break; 3030 } 3031 case $.TEMPLATE: { 3032 startTagInHead(p, token); 3033 break; 3034 } 3035 default: { 3036 tokenInColumnGroup(p, token); 3037 } 3038 } 3039} 3040 3041function endTagInColumnGroup<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3042 switch (token.tagID) { 3043 case $.COLGROUP: { 3044 if (p.openElements.currentTagId === $.COLGROUP) { 3045 p.openElements.pop(); 3046 p.insertionMode = InsertionMode.IN_TABLE; 3047 } 3048 break; 3049 } 3050 case $.TEMPLATE: { 3051 templateEndTagInHead(p, token); 3052 break; 3053 } 3054 case $.COL: { 3055 // Ignore token 3056 break; 3057 } 3058 default: { 3059 tokenInColumnGroup(p, token); 3060 } 3061 } 3062} 3063 3064function tokenInColumnGroup<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 3065 if (p.openElements.currentTagId === $.COLGROUP) { 3066 p.openElements.pop(); 3067 p.insertionMode = InsertionMode.IN_TABLE; 3068 p._processToken(token); 3069 } 3070} 3071 3072// The "in table body" insertion mode 3073//------------------------------------------------------------------ 3074function startTagInTableBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3075 switch (token.tagID) { 3076 case $.TR: { 3077 p.openElements.clearBackToTableBodyContext(); 3078 p._insertElement(token, NS.HTML); 3079 p.insertionMode = InsertionMode.IN_ROW; 3080 break; 3081 } 3082 case $.TH: 3083 case $.TD: { 3084 p.openElements.clearBackToTableBodyContext(); 3085 p._insertFakeElement(TN.TR, $.TR); 3086 p.insertionMode = InsertionMode.IN_ROW; 3087 startTagInRow(p, token); 3088 break; 3089 } 3090 case $.CAPTION: 3091 case $.COL: 3092 case $.COLGROUP: 3093 case $.TBODY: 3094 case $.TFOOT: 3095 case $.THEAD: { 3096 if (p.openElements.hasTableBodyContextInTableScope()) { 3097 p.openElements.clearBackToTableBodyContext(); 3098 p.openElements.pop(); 3099 p.insertionMode = InsertionMode.IN_TABLE; 3100 startTagInTable(p, token); 3101 } 3102 break; 3103 } 3104 default: { 3105 startTagInTable(p, token); 3106 } 3107 } 3108} 3109 3110function endTagInTableBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3111 const tn = token.tagID; 3112 3113 switch (token.tagID) { 3114 case $.TBODY: 3115 case $.TFOOT: 3116 case $.THEAD: { 3117 if (p.openElements.hasInTableScope(tn)) { 3118 p.openElements.clearBackToTableBodyContext(); 3119 p.openElements.pop(); 3120 p.insertionMode = InsertionMode.IN_TABLE; 3121 } 3122 break; 3123 } 3124 case $.TABLE: { 3125 if (p.openElements.hasTableBodyContextInTableScope()) { 3126 p.openElements.clearBackToTableBodyContext(); 3127 p.openElements.pop(); 3128 p.insertionMode = InsertionMode.IN_TABLE; 3129 endTagInTable(p, token); 3130 } 3131 break; 3132 } 3133 case $.BODY: 3134 case $.CAPTION: 3135 case $.COL: 3136 case $.COLGROUP: 3137 case $.HTML: 3138 case $.TD: 3139 case $.TH: 3140 case $.TR: { 3141 // Ignore token 3142 break; 3143 } 3144 default: { 3145 endTagInTable(p, token); 3146 } 3147 } 3148} 3149 3150// The "in row" insertion mode 3151//------------------------------------------------------------------ 3152function startTagInRow<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3153 switch (token.tagID) { 3154 case $.TH: 3155 case $.TD: { 3156 p.openElements.clearBackToTableRowContext(); 3157 p._insertElement(token, NS.HTML); 3158 p.insertionMode = InsertionMode.IN_CELL; 3159 p.activeFormattingElements.insertMarker(); 3160 break; 3161 } 3162 case $.CAPTION: 3163 case $.COL: 3164 case $.COLGROUP: 3165 case $.TBODY: 3166 case $.TFOOT: 3167 case $.THEAD: 3168 case $.TR: { 3169 if (p.openElements.hasInTableScope($.TR)) { 3170 p.openElements.clearBackToTableRowContext(); 3171 p.openElements.pop(); 3172 p.insertionMode = InsertionMode.IN_TABLE_BODY; 3173 startTagInTableBody(p, token); 3174 } 3175 break; 3176 } 3177 default: { 3178 startTagInTable(p, token); 3179 } 3180 } 3181} 3182 3183function endTagInRow<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3184 switch (token.tagID) { 3185 case $.TR: { 3186 if (p.openElements.hasInTableScope($.TR)) { 3187 p.openElements.clearBackToTableRowContext(); 3188 p.openElements.pop(); 3189 p.insertionMode = InsertionMode.IN_TABLE_BODY; 3190 } 3191 break; 3192 } 3193 case $.TABLE: { 3194 if (p.openElements.hasInTableScope($.TR)) { 3195 p.openElements.clearBackToTableRowContext(); 3196 p.openElements.pop(); 3197 p.insertionMode = InsertionMode.IN_TABLE_BODY; 3198 endTagInTableBody(p, token); 3199 } 3200 break; 3201 } 3202 case $.TBODY: 3203 case $.TFOOT: 3204 case $.THEAD: { 3205 if (p.openElements.hasInTableScope(token.tagID) || p.openElements.hasInTableScope($.TR)) { 3206 p.openElements.clearBackToTableRowContext(); 3207 p.openElements.pop(); 3208 p.insertionMode = InsertionMode.IN_TABLE_BODY; 3209 endTagInTableBody(p, token); 3210 } 3211 break; 3212 } 3213 case $.BODY: 3214 case $.CAPTION: 3215 case $.COL: 3216 case $.COLGROUP: 3217 case $.HTML: 3218 case $.TD: 3219 case $.TH: { 3220 // Ignore end tag 3221 break; 3222 } 3223 default: { 3224 endTagInTable(p, token); 3225 } 3226 } 3227} 3228 3229// The "in cell" insertion mode 3230//------------------------------------------------------------------ 3231function startTagInCell<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3232 const tn = token.tagID; 3233 3234 if (TABLE_VOID_ELEMENTS.has(tn)) { 3235 if (p.openElements.hasInTableScope($.TD) || p.openElements.hasInTableScope($.TH)) { 3236 p._closeTableCell(); 3237 startTagInRow(p, token); 3238 } 3239 } else { 3240 startTagInBody(p, token); 3241 } 3242} 3243 3244function endTagInCell<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3245 const tn = token.tagID; 3246 3247 switch (tn) { 3248 case $.TD: 3249 case $.TH: { 3250 if (p.openElements.hasInTableScope(tn)) { 3251 p.openElements.generateImpliedEndTags(); 3252 p.openElements.popUntilTagNamePopped(tn); 3253 p.activeFormattingElements.clearToLastMarker(); 3254 p.insertionMode = InsertionMode.IN_ROW; 3255 } 3256 break; 3257 } 3258 case $.TABLE: 3259 case $.TBODY: 3260 case $.TFOOT: 3261 case $.THEAD: 3262 case $.TR: { 3263 if (p.openElements.hasInTableScope(tn)) { 3264 p._closeTableCell(); 3265 endTagInRow(p, token); 3266 } 3267 break; 3268 } 3269 case $.BODY: 3270 case $.CAPTION: 3271 case $.COL: 3272 case $.COLGROUP: 3273 case $.HTML: { 3274 // Ignore token 3275 break; 3276 } 3277 default: { 3278 endTagInBody(p, token); 3279 } 3280 } 3281} 3282 3283// The "in select" insertion mode 3284//------------------------------------------------------------------ 3285function startTagInSelect<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3286 switch (token.tagID) { 3287 case $.HTML: { 3288 startTagInBody(p, token); 3289 break; 3290 } 3291 case $.OPTION: { 3292 if (p.openElements.currentTagId === $.OPTION) { 3293 p.openElements.pop(); 3294 } 3295 3296 p._insertElement(token, NS.HTML); 3297 break; 3298 } 3299 case $.OPTGROUP: { 3300 if (p.openElements.currentTagId === $.OPTION) { 3301 p.openElements.pop(); 3302 } 3303 3304 if (p.openElements.currentTagId === $.OPTGROUP) { 3305 p.openElements.pop(); 3306 } 3307 3308 p._insertElement(token, NS.HTML); 3309 break; 3310 } 3311 case $.HR: { 3312 if (p.openElements.currentTagId === $.OPTION) { 3313 p.openElements.pop(); 3314 } 3315 3316 if (p.openElements.currentTagId === $.OPTGROUP) { 3317 p.openElements.pop(); 3318 } 3319 3320 p._appendElement(token, NS.HTML); 3321 token.ackSelfClosing = true; 3322 break; 3323 } 3324 case $.INPUT: 3325 case $.KEYGEN: 3326 case $.TEXTAREA: 3327 case $.SELECT: { 3328 if (p.openElements.hasInSelectScope($.SELECT)) { 3329 p.openElements.popUntilTagNamePopped($.SELECT); 3330 p._resetInsertionMode(); 3331 3332 if (token.tagID !== $.SELECT) { 3333 p._processStartTag(token); 3334 } 3335 } 3336 break; 3337 } 3338 case $.SCRIPT: 3339 case $.TEMPLATE: { 3340 startTagInHead(p, token); 3341 break; 3342 } 3343 default: 3344 // Do nothing 3345 } 3346} 3347 3348function endTagInSelect<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3349 switch (token.tagID) { 3350 case $.OPTGROUP: { 3351 if ( 3352 p.openElements.stackTop > 0 && 3353 p.openElements.currentTagId === $.OPTION && 3354 p.openElements.tagIDs[p.openElements.stackTop - 1] === $.OPTGROUP 3355 ) { 3356 p.openElements.pop(); 3357 } 3358 3359 if (p.openElements.currentTagId === $.OPTGROUP) { 3360 p.openElements.pop(); 3361 } 3362 break; 3363 } 3364 case $.OPTION: { 3365 if (p.openElements.currentTagId === $.OPTION) { 3366 p.openElements.pop(); 3367 } 3368 break; 3369 } 3370 case $.SELECT: { 3371 if (p.openElements.hasInSelectScope($.SELECT)) { 3372 p.openElements.popUntilTagNamePopped($.SELECT); 3373 p._resetInsertionMode(); 3374 } 3375 break; 3376 } 3377 case $.TEMPLATE: { 3378 templateEndTagInHead(p, token); 3379 break; 3380 } 3381 default: 3382 // Do nothing 3383 } 3384} 3385 3386// The "in select in table" insertion mode 3387//------------------------------------------------------------------ 3388function startTagInSelectInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3389 const tn = token.tagID; 3390 3391 if ( 3392 tn === $.CAPTION || 3393 tn === $.TABLE || 3394 tn === $.TBODY || 3395 tn === $.TFOOT || 3396 tn === $.THEAD || 3397 tn === $.TR || 3398 tn === $.TD || 3399 tn === $.TH 3400 ) { 3401 p.openElements.popUntilTagNamePopped($.SELECT); 3402 p._resetInsertionMode(); 3403 p._processStartTag(token); 3404 } else { 3405 startTagInSelect(p, token); 3406 } 3407} 3408 3409function endTagInSelectInTable<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3410 const tn = token.tagID; 3411 3412 if ( 3413 tn === $.CAPTION || 3414 tn === $.TABLE || 3415 tn === $.TBODY || 3416 tn === $.TFOOT || 3417 tn === $.THEAD || 3418 tn === $.TR || 3419 tn === $.TD || 3420 tn === $.TH 3421 ) { 3422 if (p.openElements.hasInTableScope(tn)) { 3423 p.openElements.popUntilTagNamePopped($.SELECT); 3424 p._resetInsertionMode(); 3425 p.onEndTag(token); 3426 } 3427 } else { 3428 endTagInSelect(p, token); 3429 } 3430} 3431 3432// The "in template" insertion mode 3433//------------------------------------------------------------------ 3434function startTagInTemplate<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3435 switch (token.tagID) { 3436 // First, handle tags that can start without a mode change 3437 case $.BASE: 3438 case $.BASEFONT: 3439 case $.BGSOUND: 3440 case $.LINK: 3441 case $.META: 3442 case $.NOFRAMES: 3443 case $.SCRIPT: 3444 case $.STYLE: 3445 case $.TEMPLATE: 3446 case $.TITLE: { 3447 startTagInHead(p, token); 3448 break; 3449 } 3450 3451 // Re-process the token in the appropriate mode 3452 case $.CAPTION: 3453 case $.COLGROUP: 3454 case $.TBODY: 3455 case $.TFOOT: 3456 case $.THEAD: { 3457 p.tmplInsertionModeStack[0] = InsertionMode.IN_TABLE; 3458 p.insertionMode = InsertionMode.IN_TABLE; 3459 startTagInTable(p, token); 3460 break; 3461 } 3462 case $.COL: { 3463 p.tmplInsertionModeStack[0] = InsertionMode.IN_COLUMN_GROUP; 3464 p.insertionMode = InsertionMode.IN_COLUMN_GROUP; 3465 startTagInColumnGroup(p, token); 3466 break; 3467 } 3468 case $.TR: { 3469 p.tmplInsertionModeStack[0] = InsertionMode.IN_TABLE_BODY; 3470 p.insertionMode = InsertionMode.IN_TABLE_BODY; 3471 startTagInTableBody(p, token); 3472 break; 3473 } 3474 case $.TD: 3475 case $.TH: { 3476 p.tmplInsertionModeStack[0] = InsertionMode.IN_ROW; 3477 p.insertionMode = InsertionMode.IN_ROW; 3478 startTagInRow(p, token); 3479 break; 3480 } 3481 default: { 3482 p.tmplInsertionModeStack[0] = InsertionMode.IN_BODY; 3483 p.insertionMode = InsertionMode.IN_BODY; 3484 startTagInBody(p, token); 3485 } 3486 } 3487} 3488 3489function endTagInTemplate<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3490 if (token.tagID === $.TEMPLATE) { 3491 templateEndTagInHead(p, token); 3492 } 3493} 3494 3495function eofInTemplate<T extends TreeAdapterTypeMap>(p: Parser<T>, token: EOFToken): void { 3496 if (p.openElements.tmplCount > 0) { 3497 p.openElements.popUntilTagNamePopped($.TEMPLATE); 3498 p.activeFormattingElements.clearToLastMarker(); 3499 p.tmplInsertionModeStack.shift(); 3500 p._resetInsertionMode(); 3501 p.onEof(token); 3502 } else { 3503 stopParsing(p, token); 3504 } 3505} 3506 3507// The "after body" insertion mode 3508//------------------------------------------------------------------ 3509function startTagAfterBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3510 if (token.tagID === $.HTML) { 3511 startTagInBody(p, token); 3512 } else { 3513 tokenAfterBody(p, token); 3514 } 3515} 3516 3517function endTagAfterBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3518 if (token.tagID === $.HTML) { 3519 if (!p.fragmentContext) { 3520 p.insertionMode = InsertionMode.AFTER_AFTER_BODY; 3521 } 3522 3523 //NOTE: <html> is never popped from the stack, so we need to updated 3524 //the end location explicitly. 3525 if (p.options.sourceCodeLocationInfo && p.openElements.tagIDs[0] === $.HTML) { 3526 p._setEndLocation(p.openElements.items[0], token); 3527 3528 // Update the body element, if it doesn't have an end tag 3529 const bodyElement = p.openElements.items[1]; 3530 if (bodyElement && !p.treeAdapter.getNodeSourceCodeLocation(bodyElement)?.endTag) { 3531 p._setEndLocation(bodyElement, token); 3532 } 3533 } 3534 } else { 3535 tokenAfterBody(p, token); 3536 } 3537} 3538 3539function tokenAfterBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 3540 p.insertionMode = InsertionMode.IN_BODY; 3541 modeInBody(p, token); 3542} 3543 3544// The "in frameset" insertion mode 3545//------------------------------------------------------------------ 3546function startTagInFrameset<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3547 switch (token.tagID) { 3548 case $.HTML: { 3549 startTagInBody(p, token); 3550 break; 3551 } 3552 case $.FRAMESET: { 3553 p._insertElement(token, NS.HTML); 3554 break; 3555 } 3556 case $.FRAME: { 3557 p._appendElement(token, NS.HTML); 3558 token.ackSelfClosing = true; 3559 break; 3560 } 3561 case $.NOFRAMES: { 3562 startTagInHead(p, token); 3563 break; 3564 } 3565 default: 3566 // Do nothing 3567 } 3568} 3569 3570function endTagInFrameset<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3571 if (token.tagID === $.FRAMESET && !p.openElements.isRootHtmlElementCurrent()) { 3572 p.openElements.pop(); 3573 3574 if (!p.fragmentContext && p.openElements.currentTagId !== $.FRAMESET) { 3575 p.insertionMode = InsertionMode.AFTER_FRAMESET; 3576 } 3577 } 3578} 3579 3580// The "after frameset" insertion mode 3581//------------------------------------------------------------------ 3582function startTagAfterFrameset<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3583 switch (token.tagID) { 3584 case $.HTML: { 3585 startTagInBody(p, token); 3586 break; 3587 } 3588 case $.NOFRAMES: { 3589 startTagInHead(p, token); 3590 break; 3591 } 3592 default: 3593 // Do nothing 3594 } 3595} 3596 3597function endTagAfterFrameset<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3598 if (token.tagID === $.HTML) { 3599 p.insertionMode = InsertionMode.AFTER_AFTER_FRAMESET; 3600 } 3601} 3602 3603// The "after after body" insertion mode 3604//------------------------------------------------------------------ 3605function startTagAfterAfterBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3606 if (token.tagID === $.HTML) { 3607 startTagInBody(p, token); 3608 } else { 3609 tokenAfterAfterBody(p, token); 3610 } 3611} 3612 3613function tokenAfterAfterBody<T extends TreeAdapterTypeMap>(p: Parser<T>, token: Token): void { 3614 p.insertionMode = InsertionMode.IN_BODY; 3615 modeInBody(p, token); 3616} 3617 3618// The "after after frameset" insertion mode 3619//------------------------------------------------------------------ 3620function startTagAfterAfterFrameset<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3621 switch (token.tagID) { 3622 case $.HTML: { 3623 startTagInBody(p, token); 3624 break; 3625 } 3626 case $.NOFRAMES: { 3627 startTagInHead(p, token); 3628 break; 3629 } 3630 default: 3631 // Do nothing 3632 } 3633} 3634 3635// The rules for parsing tokens in foreign content 3636//------------------------------------------------------------------ 3637function nullCharacterInForeignContent<T extends TreeAdapterTypeMap>(p: Parser<T>, token: CharacterToken): void { 3638 token.chars = unicode.REPLACEMENT_CHARACTER; 3639 p._insertCharacters(token); 3640} 3641 3642function characterInForeignContent<T extends TreeAdapterTypeMap>(p: Parser<T>, token: CharacterToken): void { 3643 p._insertCharacters(token); 3644 p.framesetOk = false; 3645} 3646 3647function popUntilHtmlOrIntegrationPoint<T extends TreeAdapterTypeMap>(p: Parser<T>): void { 3648 while ( 3649 p.treeAdapter.getNamespaceURI(p.openElements.current) !== NS.HTML && 3650 !p._isIntegrationPoint(p.openElements.currentTagId, p.openElements.current) 3651 ) { 3652 p.openElements.pop(); 3653 } 3654} 3655 3656function startTagInForeignContent<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3657 if (foreignContent.causesExit(token)) { 3658 popUntilHtmlOrIntegrationPoint(p); 3659 3660 p._startTagOutsideForeignContent(token); 3661 } else { 3662 const current = p._getAdjustedCurrentElement(); 3663 const currentNs = p.treeAdapter.getNamespaceURI(current); 3664 3665 if (token.selfClosing) { 3666 p._appendElement(token, currentNs); 3667 } else { 3668 p._insertElement(token, currentNs); 3669 } 3670 3671 token.ackSelfClosing = true; 3672 } 3673} 3674 3675function endTagInForeignContent<T extends TreeAdapterTypeMap>(p: Parser<T>, token: TagToken): void { 3676 if (token.tagID === $.P || token.tagID === $.BR) { 3677 popUntilHtmlOrIntegrationPoint(p); 3678 3679 p._endTagOutsideForeignContent(token); 3680 3681 return; 3682 } 3683 for (let i = p.openElements.stackTop; i > 0; i--) { 3684 const element = p.openElements.items[i]; 3685 3686 if (p.treeAdapter.getNamespaceURI(element) === NS.HTML) { 3687 p._endTagOutsideForeignContent(token); 3688 break; 3689 } 3690 3691 const tagName = p.treeAdapter.getTagName(element); 3692 3693 if (tagName.toLowerCase() === token.tagName) { 3694 //NOTE: update the token tag name for `_setEndLocation`. 3695 token.tagName = tagName; 3696 p.openElements.shortenToLength(i); 3697 break; 3698 } 3699 } 3700} 3701