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