diff options
author | Minteck <contact@minteck.org> | 2023-01-10 14:54:04 +0100 |
---|---|---|
committer | Minteck <contact@minteck.org> | 2023-01-10 14:54:04 +0100 |
commit | 99c1d9af689e5325f3cf535c4007b3aeb8325229 (patch) | |
tree | e663b3c2ebdbd67c818ac0c5147f0ce1d2463cda /school/node_modules/parse5/lib/parser | |
parent | 9871b03912fc28ad38b4037ebf26a78aa937baba (diff) | |
download | pluralconnect-99c1d9af689e5325f3cf535c4007b3aeb8325229.tar.gz pluralconnect-99c1d9af689e5325f3cf535c4007b3aeb8325229.tar.bz2 pluralconnect-99c1d9af689e5325f3cf535c4007b3aeb8325229.zip |
Update - This is an automated commit
Diffstat (limited to 'school/node_modules/parse5/lib/parser')
3 files changed, 3619 insertions, 0 deletions
diff --git a/school/node_modules/parse5/lib/parser/formatting-element-list.js b/school/node_modules/parse5/lib/parser/formatting-element-list.js new file mode 100644 index 0000000..0e241db --- /dev/null +++ b/school/node_modules/parse5/lib/parser/formatting-element-list.js @@ -0,0 +1,181 @@ +'use strict'; + +//Const +const NOAH_ARK_CAPACITY = 3; + +//List of formatting elements +class FormattingElementList { + constructor(treeAdapter) { + this.length = 0; + this.entries = []; + this.treeAdapter = treeAdapter; + this.bookmark = null; + } + + //Noah Ark's condition + //OPTIMIZATION: at first we try to find possible candidates for exclusion using + //lightweight heuristics without thorough attributes check. + _getNoahArkConditionCandidates(newElement) { + const candidates = []; + + if (this.length >= NOAH_ARK_CAPACITY) { + const neAttrsLength = this.treeAdapter.getAttrList(newElement).length; + const neTagName = this.treeAdapter.getTagName(newElement); + const neNamespaceURI = this.treeAdapter.getNamespaceURI(newElement); + + for (let i = this.length - 1; i >= 0; i--) { + const entry = this.entries[i]; + + if (entry.type === FormattingElementList.MARKER_ENTRY) { + break; + } + + const element = entry.element; + const elementAttrs = this.treeAdapter.getAttrList(element); + + const isCandidate = + this.treeAdapter.getTagName(element) === neTagName && + this.treeAdapter.getNamespaceURI(element) === neNamespaceURI && + elementAttrs.length === neAttrsLength; + + if (isCandidate) { + candidates.push({ idx: i, attrs: elementAttrs }); + } + } + } + + return candidates.length < NOAH_ARK_CAPACITY ? [] : candidates; + } + + _ensureNoahArkCondition(newElement) { + const candidates = this._getNoahArkConditionCandidates(newElement); + let cLength = candidates.length; + + if (cLength) { + const neAttrs = this.treeAdapter.getAttrList(newElement); + const neAttrsLength = neAttrs.length; + const neAttrsMap = Object.create(null); + + //NOTE: build attrs map for the new element so we can perform fast lookups + for (let i = 0; i < neAttrsLength; i++) { + const neAttr = neAttrs[i]; + + neAttrsMap[neAttr.name] = neAttr.value; + } + + for (let i = 0; i < neAttrsLength; i++) { + for (let j = 0; j < cLength; j++) { + const cAttr = candidates[j].attrs[i]; + + if (neAttrsMap[cAttr.name] !== cAttr.value) { + candidates.splice(j, 1); + cLength--; + } + + if (candidates.length < NOAH_ARK_CAPACITY) { + return; + } + } + } + + //NOTE: remove bottommost candidates until Noah's Ark condition will not be met + for (let i = cLength - 1; i >= NOAH_ARK_CAPACITY - 1; i--) { + this.entries.splice(candidates[i].idx, 1); + this.length--; + } + } + } + + //Mutations + insertMarker() { + this.entries.push({ type: FormattingElementList.MARKER_ENTRY }); + this.length++; + } + + pushElement(element, token) { + this._ensureNoahArkCondition(element); + + this.entries.push({ + type: FormattingElementList.ELEMENT_ENTRY, + element: element, + token: token + }); + + this.length++; + } + + insertElementAfterBookmark(element, token) { + let bookmarkIdx = this.length - 1; + + for (; bookmarkIdx >= 0; bookmarkIdx--) { + if (this.entries[bookmarkIdx] === this.bookmark) { + break; + } + } + + this.entries.splice(bookmarkIdx + 1, 0, { + type: FormattingElementList.ELEMENT_ENTRY, + element: element, + token: token + }); + + this.length++; + } + + removeEntry(entry) { + for (let i = this.length - 1; i >= 0; i--) { + if (this.entries[i] === entry) { + this.entries.splice(i, 1); + this.length--; + break; + } + } + } + + clearToLastMarker() { + while (this.length) { + const entry = this.entries.pop(); + + this.length--; + + if (entry.type === FormattingElementList.MARKER_ENTRY) { + break; + } + } + } + + //Search + getElementEntryInScopeWithTagName(tagName) { + for (let i = this.length - 1; i >= 0; i--) { + const entry = this.entries[i]; + + if (entry.type === FormattingElementList.MARKER_ENTRY) { + return null; + } + + if (this.treeAdapter.getTagName(entry.element) === tagName) { + return entry; + } + } + + return null; + } + + getElementEntry(element) { + for (let i = this.length - 1; i >= 0; i--) { + const entry = this.entries[i]; + + if (entry.type === FormattingElementList.ELEMENT_ENTRY && entry.element === element) { + return entry; + } + } + + return null; + } +} + +//Entry types +FormattingElementList.MARKER_ENTRY = 'MARKER_ENTRY'; +FormattingElementList.ELEMENT_ENTRY = 'ELEMENT_ENTRY'; + +module.exports = FormattingElementList; diff --git a/school/node_modules/parse5/lib/parser/index.js b/school/node_modules/parse5/lib/parser/index.js new file mode 100644 index 0000000..45d3e83 --- /dev/null +++ b/school/node_modules/parse5/lib/parser/index.js @@ -0,0 +1,2956 @@ +'use strict'; + +const Tokenizer = require('../tokenizer'); +const OpenElementStack = require('./open-element-stack'); +const FormattingElementList = require('./formatting-element-list'); +const LocationInfoParserMixin = require('../extensions/location-info/parser-mixin'); +const ErrorReportingParserMixin = require('../extensions/error-reporting/parser-mixin'); +const Mixin = require('../utils/mixin'); +const defaultTreeAdapter = require('../tree-adapters/default'); +const mergeOptions = require('../utils/merge-options'); +const doctype = require('../common/doctype'); +const foreignContent = require('../common/foreign-content'); +const ERR = require('../common/error-codes'); +const unicode = require('../common/unicode'); +const HTML = require('../common/html'); + +//Aliases +const $ = HTML.TAG_NAMES; +const NS = HTML.NAMESPACES; +const ATTRS = HTML.ATTRS; + +const DEFAULT_OPTIONS = { + scriptingEnabled: true, + sourceCodeLocationInfo: false, + onParseError: null, + treeAdapter: defaultTreeAdapter +}; + +//Misc constants +const HIDDEN_INPUT_TYPE = 'hidden'; + +//Adoption agency loops iteration count +const AA_OUTER_LOOP_ITER = 8; +const AA_INNER_LOOP_ITER = 3; + +//Insertion modes +const INITIAL_MODE = 'INITIAL_MODE'; +const BEFORE_HTML_MODE = 'BEFORE_HTML_MODE'; +const BEFORE_HEAD_MODE = 'BEFORE_HEAD_MODE'; +const IN_HEAD_MODE = 'IN_HEAD_MODE'; +const IN_HEAD_NO_SCRIPT_MODE = 'IN_HEAD_NO_SCRIPT_MODE'; +const AFTER_HEAD_MODE = 'AFTER_HEAD_MODE'; +const IN_BODY_MODE = 'IN_BODY_MODE'; +const TEXT_MODE = 'TEXT_MODE'; +const IN_TABLE_MODE = 'IN_TABLE_MODE'; +const IN_TABLE_TEXT_MODE = 'IN_TABLE_TEXT_MODE'; +const IN_CAPTION_MODE = 'IN_CAPTION_MODE'; +const IN_COLUMN_GROUP_MODE = 'IN_COLUMN_GROUP_MODE'; +const IN_TABLE_BODY_MODE = 'IN_TABLE_BODY_MODE'; +const IN_ROW_MODE = 'IN_ROW_MODE'; +const IN_CELL_MODE = 'IN_CELL_MODE'; +const IN_SELECT_MODE = 'IN_SELECT_MODE'; +const IN_SELECT_IN_TABLE_MODE = 'IN_SELECT_IN_TABLE_MODE'; +const IN_TEMPLATE_MODE = 'IN_TEMPLATE_MODE'; +const AFTER_BODY_MODE = 'AFTER_BODY_MODE'; +const IN_FRAMESET_MODE = 'IN_FRAMESET_MODE'; +const AFTER_FRAMESET_MODE = 'AFTER_FRAMESET_MODE'; +const AFTER_AFTER_BODY_MODE = 'AFTER_AFTER_BODY_MODE'; +const AFTER_AFTER_FRAMESET_MODE = 'AFTER_AFTER_FRAMESET_MODE'; + +//Insertion mode reset map +const INSERTION_MODE_RESET_MAP = { + [$.TR]: IN_ROW_MODE, + [$.TBODY]: IN_TABLE_BODY_MODE, + [$.THEAD]: IN_TABLE_BODY_MODE, + [$.TFOOT]: IN_TABLE_BODY_MODE, + [$.CAPTION]: IN_CAPTION_MODE, + [$.COLGROUP]: IN_COLUMN_GROUP_MODE, + [$.TABLE]: IN_TABLE_MODE, + [$.BODY]: IN_BODY_MODE, + [$.FRAMESET]: IN_FRAMESET_MODE +}; + +//Template insertion mode switch map +const TEMPLATE_INSERTION_MODE_SWITCH_MAP = { + [$.CAPTION]: IN_TABLE_MODE, + [$.COLGROUP]: IN_TABLE_MODE, + [$.TBODY]: IN_TABLE_MODE, + [$.TFOOT]: IN_TABLE_MODE, + [$.THEAD]: IN_TABLE_MODE, + [$.COL]: IN_COLUMN_GROUP_MODE, + [$.TR]: IN_TABLE_BODY_MODE, + [$.TD]: IN_ROW_MODE, + [$.TH]: IN_ROW_MODE +}; + +//Token handlers map for insertion modes +const TOKEN_HANDLERS = { + [INITIAL_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenInInitialMode, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInInitialMode, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: doctypeInInitialMode, + [Tokenizer.START_TAG_TOKEN]: tokenInInitialMode, + [Tokenizer.END_TAG_TOKEN]: tokenInInitialMode, + [Tokenizer.EOF_TOKEN]: tokenInInitialMode + }, + [BEFORE_HTML_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenBeforeHtml, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHtml, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagBeforeHtml, + [Tokenizer.END_TAG_TOKEN]: endTagBeforeHtml, + [Tokenizer.EOF_TOKEN]: tokenBeforeHtml + }, + [BEFORE_HEAD_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenBeforeHead, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHead, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, + [Tokenizer.START_TAG_TOKEN]: startTagBeforeHead, + [Tokenizer.END_TAG_TOKEN]: endTagBeforeHead, + [Tokenizer.EOF_TOKEN]: tokenBeforeHead + }, + [IN_HEAD_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenInHead, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHead, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, + [Tokenizer.START_TAG_TOKEN]: startTagInHead, + [Tokenizer.END_TAG_TOKEN]: endTagInHead, + [Tokenizer.EOF_TOKEN]: tokenInHead + }, + [IN_HEAD_NO_SCRIPT_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenInHeadNoScript, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHeadNoScript, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, + [Tokenizer.START_TAG_TOKEN]: startTagInHeadNoScript, + [Tokenizer.END_TAG_TOKEN]: endTagInHeadNoScript, + [Tokenizer.EOF_TOKEN]: tokenInHeadNoScript + }, + [AFTER_HEAD_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenAfterHead, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterHead, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, + [Tokenizer.START_TAG_TOKEN]: startTagAfterHead, + [Tokenizer.END_TAG_TOKEN]: endTagAfterHead, + [Tokenizer.EOF_TOKEN]: tokenAfterHead + }, + [IN_BODY_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInBody, + [Tokenizer.END_TAG_TOKEN]: endTagInBody, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [TEXT_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.NULL_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: ignoreToken, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: ignoreToken, + [Tokenizer.END_TAG_TOKEN]: endTagInText, + [Tokenizer.EOF_TOKEN]: eofInText + }, + [IN_TABLE_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInTable, + [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInTable, + [Tokenizer.END_TAG_TOKEN]: endTagInTable, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_TABLE_TEXT_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInTableText, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInTableText, + [Tokenizer.COMMENT_TOKEN]: tokenInTableText, + [Tokenizer.DOCTYPE_TOKEN]: tokenInTableText, + [Tokenizer.START_TAG_TOKEN]: tokenInTableText, + [Tokenizer.END_TAG_TOKEN]: tokenInTableText, + [Tokenizer.EOF_TOKEN]: tokenInTableText + }, + [IN_CAPTION_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInCaption, + [Tokenizer.END_TAG_TOKEN]: endTagInCaption, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_COLUMN_GROUP_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenInColumnGroup, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInColumnGroup, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInColumnGroup, + [Tokenizer.END_TAG_TOKEN]: endTagInColumnGroup, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_TABLE_BODY_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInTable, + [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInTableBody, + [Tokenizer.END_TAG_TOKEN]: endTagInTableBody, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_ROW_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInTable, + [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInRow, + [Tokenizer.END_TAG_TOKEN]: endTagInRow, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_CELL_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInCell, + [Tokenizer.END_TAG_TOKEN]: endTagInCell, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_SELECT_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInSelect, + [Tokenizer.END_TAG_TOKEN]: endTagInSelect, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_SELECT_IN_TABLE_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInSelectInTable, + [Tokenizer.END_TAG_TOKEN]: endTagInSelectInTable, + [Tokenizer.EOF_TOKEN]: eofInBody + }, + [IN_TEMPLATE_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: characterInBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInTemplate, + [Tokenizer.END_TAG_TOKEN]: endTagInTemplate, + [Tokenizer.EOF_TOKEN]: eofInTemplate + }, + [AFTER_BODY_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenAfterBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterBody, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendCommentToRootHtmlElement, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagAfterBody, + [Tokenizer.END_TAG_TOKEN]: endTagAfterBody, + [Tokenizer.EOF_TOKEN]: stopParsing + }, + [IN_FRAMESET_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagInFrameset, + [Tokenizer.END_TAG_TOKEN]: endTagInFrameset, + [Tokenizer.EOF_TOKEN]: stopParsing + }, + [AFTER_FRAMESET_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, + [Tokenizer.COMMENT_TOKEN]: appendComment, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagAfterFrameset, + [Tokenizer.END_TAG_TOKEN]: endTagAfterFrameset, + [Tokenizer.EOF_TOKEN]: stopParsing + }, + [AFTER_AFTER_BODY_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: tokenAfterAfterBody, + [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterAfterBody, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendCommentToDocument, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagAfterAfterBody, + [Tokenizer.END_TAG_TOKEN]: tokenAfterAfterBody, + [Tokenizer.EOF_TOKEN]: stopParsing + }, + [AFTER_AFTER_FRAMESET_MODE]: { + [Tokenizer.CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, + [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, + [Tokenizer.COMMENT_TOKEN]: appendCommentToDocument, + [Tokenizer.DOCTYPE_TOKEN]: ignoreToken, + [Tokenizer.START_TAG_TOKEN]: startTagAfterAfterFrameset, + [Tokenizer.END_TAG_TOKEN]: ignoreToken, + [Tokenizer.EOF_TOKEN]: stopParsing + } +}; + +//Parser +class Parser { + constructor(options) { + this.options = mergeOptions(DEFAULT_OPTIONS, options); + + this.treeAdapter = this.options.treeAdapter; + this.pendingScript = null; + + if (this.options.sourceCodeLocationInfo) { + Mixin.install(this, LocationInfoParserMixin); + } + + if (this.options.onParseError) { + Mixin.install(this, ErrorReportingParserMixin, { onParseError: this.options.onParseError }); + } + } + + // API + parse(html) { + const document = this.treeAdapter.createDocument(); + + this._bootstrap(document, null); + this.tokenizer.write(html, true); + this._runParsingLoop(null); + + return document; + } + + parseFragment(html, fragmentContext) { + //NOTE: use <template> element as a fragment context if context element was not provided, + //so we will parse in "forgiving" manner + if (!fragmentContext) { + fragmentContext = this.treeAdapter.createElement($.TEMPLATE, NS.HTML, []); + } + + //NOTE: create fake element which will be used as 'document' for fragment parsing. + //This is important for jsdom there 'document' can't be recreated, therefore + //fragment parsing causes messing of the main `document`. + const documentMock = this.treeAdapter.createElement('documentmock', NS.HTML, []); + + this._bootstrap(documentMock, fragmentContext); + + if (this.treeAdapter.getTagName(fragmentContext) === $.TEMPLATE) { + this._pushTmplInsertionMode(IN_TEMPLATE_MODE); + } + + this._initTokenizerForFragmentParsing(); + this._insertFakeRootElement(); + this._resetInsertionMode(); + this._findFormInFragmentContext(); + this.tokenizer.write(html, true); + this._runParsingLoop(null); + + const rootElement = this.treeAdapter.getFirstChild(documentMock); + const fragment = this.treeAdapter.createDocumentFragment(); + + this._adoptNodes(rootElement, fragment); + + return fragment; + } + + //Bootstrap parser + _bootstrap(document, fragmentContext) { + this.tokenizer = new Tokenizer(this.options); + + this.stopped = false; + + this.insertionMode = INITIAL_MODE; + this.originalInsertionMode = ''; + + this.document = document; + this.fragmentContext = fragmentContext; + + this.headElement = null; + this.formElement = null; + + this.openElements = new OpenElementStack(this.document, this.treeAdapter); + this.activeFormattingElements = new FormattingElementList(this.treeAdapter); + + this.tmplInsertionModeStack = []; + this.tmplInsertionModeStackTop = -1; + this.currentTmplInsertionMode = null; + + this.pendingCharacterTokens = []; + this.hasNonWhitespacePendingCharacterToken = false; + + this.framesetOk = true; + this.skipNextNewLine = false; + this.fosterParentingEnabled = false; + } + + //Errors + _err() { + // NOTE: err reporting is noop by default. Enabled by mixin. + } + + //Parsing loop + _runParsingLoop(scriptHandler) { + while (!this.stopped) { + this._setupTokenizerCDATAMode(); + + const token = this.tokenizer.getNextToken(); + + if (token.type === Tokenizer.HIBERNATION_TOKEN) { + break; + } + + if (this.skipNextNewLine) { + this.skipNextNewLine = false; + + if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN && token.chars[0] === '\n') { + if (token.chars.length === 1) { + continue; + } + + token.chars = token.chars.substr(1); + } + } + + this._processInputToken(token); + + if (scriptHandler && this.pendingScript) { + break; + } + } + } + + runParsingLoopForCurrentChunk(writeCallback, scriptHandler) { + this._runParsingLoop(scriptHandler); + + if (scriptHandler && this.pendingScript) { + const script = this.pendingScript; + + this.pendingScript = null; + + scriptHandler(script); + + return; + } + + if (writeCallback) { + writeCallback(); + } + } + + //Text parsing + _setupTokenizerCDATAMode() { + const current = this._getAdjustedCurrentElement(); + + this.tokenizer.allowCDATA = + current && + current !== this.document && + this.treeAdapter.getNamespaceURI(current) !== NS.HTML && + !this._isIntegrationPoint(current); + } + + _switchToTextParsing(currentToken, nextTokenizerState) { + this._insertElement(currentToken, NS.HTML); + this.tokenizer.state = nextTokenizerState; + this.originalInsertionMode = this.insertionMode; + this.insertionMode = TEXT_MODE; + } + + switchToPlaintextParsing() { + this.insertionMode = TEXT_MODE; + this.originalInsertionMode = IN_BODY_MODE; + this.tokenizer.state = Tokenizer.MODE.PLAINTEXT; + } + + //Fragment parsing + _getAdjustedCurrentElement() { + return this.openElements.stackTop === 0 && this.fragmentContext + ? this.fragmentContext + : this.openElements.current; + } + + _findFormInFragmentContext() { + let node = this.fragmentContext; + + do { + if (this.treeAdapter.getTagName(node) === $.FORM) { + this.formElement = node; + break; + } + + node = this.treeAdapter.getParentNode(node); + } while (node); + } + + _initTokenizerForFragmentParsing() { + if (this.treeAdapter.getNamespaceURI(this.fragmentContext) === NS.HTML) { + const tn = this.treeAdapter.getTagName(this.fragmentContext); + + if (tn === $.TITLE || tn === $.TEXTAREA) { + this.tokenizer.state = Tokenizer.MODE.RCDATA; + } else if ( + tn === $.STYLE || + tn === $.XMP || + tn === $.IFRAME || + tn === $.NOEMBED || + tn === $.NOFRAMES || + tn === $.NOSCRIPT + ) { + this.tokenizer.state = Tokenizer.MODE.RAWTEXT; + } else if (tn === $.SCRIPT) { + this.tokenizer.state = Tokenizer.MODE.SCRIPT_DATA; + } else if (tn === $.PLAINTEXT) { + this.tokenizer.state = Tokenizer.MODE.PLAINTEXT; + } + } + } + + //Tree mutation + _setDocumentType(token) { + const name = token.name || ''; + const publicId = token.publicId || ''; + const systemId = token.systemId || ''; + + this.treeAdapter.setDocumentType(this.document, name, publicId, systemId); + } + + _attachElementToTree(element) { + if (this._shouldFosterParentOnInsertion()) { + this._fosterParentElement(element); + } else { + const parent = this.openElements.currentTmplContent || this.openElements.current; + + this.treeAdapter.appendChild(parent, element); + } + } + + _appendElement(token, namespaceURI) { + const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs); + + this._attachElementToTree(element); + } + + _insertElement(token, namespaceURI) { + const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs); + + this._attachElementToTree(element); + this.openElements.push(element); + } + + _insertFakeElement(tagName) { + const element = this.treeAdapter.createElement(tagName, NS.HTML, []); + + this._attachElementToTree(element); + this.openElements.push(element); + } + + _insertTemplate(token) { + const tmpl = this.treeAdapter.createElement(token.tagName, NS.HTML, token.attrs); + const content = this.treeAdapter.createDocumentFragment(); + + this.treeAdapter.setTemplateContent(tmpl, content); + this._attachElementToTree(tmpl); + this.openElements.push(tmpl); + } + + _insertFakeRootElement() { + const element = this.treeAdapter.createElement($.HTML, NS.HTML, []); + + this.treeAdapter.appendChild(this.openElements.current, element); + this.openElements.push(element); + } + + _appendCommentNode(token, parent) { + const commentNode = this.treeAdapter.createCommentNode(token.data); + + this.treeAdapter.appendChild(parent, commentNode); + } + + _insertCharacters(token) { + if (this._shouldFosterParentOnInsertion()) { + this._fosterParentText(token.chars); + } else { + const parent = this.openElements.currentTmplContent || this.openElements.current; + + this.treeAdapter.insertText(parent, token.chars); + } + } + + _adoptNodes(donor, recipient) { + for (let child = this.treeAdapter.getFirstChild(donor); child; child = this.treeAdapter.getFirstChild(donor)) { + this.treeAdapter.detachNode(child); + this.treeAdapter.appendChild(recipient, child); + } + } + + //Token processing + _shouldProcessTokenInForeignContent(token) { + const current = this._getAdjustedCurrentElement(); + + if (!current || current === this.document) { + return false; + } + + const ns = this.treeAdapter.getNamespaceURI(current); + + if (ns === NS.HTML) { + return false; + } + + if ( + this.treeAdapter.getTagName(current) === $.ANNOTATION_XML && + ns === NS.MATHML && + token.type === Tokenizer.START_TAG_TOKEN && + token.tagName === $.SVG + ) { + return false; + } + + const isCharacterToken = + token.type === Tokenizer.CHARACTER_TOKEN || + token.type === Tokenizer.NULL_CHARACTER_TOKEN || + token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN; + + const isMathMLTextStartTag = + token.type === Tokenizer.START_TAG_TOKEN && token.tagName !== $.MGLYPH && token.tagName !== $.MALIGNMARK; + + if ((isMathMLTextStartTag || isCharacterToken) && this._isIntegrationPoint(current, NS.MATHML)) { + return false; + } + + if ( + (token.type === Tokenizer.START_TAG_TOKEN || isCharacterToken) && + this._isIntegrationPoint(current, NS.HTML) + ) { + return false; + } + + return token.type !== Tokenizer.EOF_TOKEN; + } + + _processToken(token) { + TOKEN_HANDLERS[this.insertionMode][token.type](this, token); + } + + _processTokenInBodyMode(token) { + TOKEN_HANDLERS[IN_BODY_MODE][token.type](this, token); + } + + _processTokenInForeignContent(token) { + if (token.type === Tokenizer.CHARACTER_TOKEN) { + characterInForeignContent(this, token); + } else if (token.type === Tokenizer.NULL_CHARACTER_TOKEN) { + nullCharacterInForeignContent(this, token); + } else if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN) { + insertCharacters(this, token); + } else if (token.type === Tokenizer.COMMENT_TOKEN) { + appendComment(this, token); + } else if (token.type === Tokenizer.START_TAG_TOKEN) { + startTagInForeignContent(this, token); + } else if (token.type === Tokenizer.END_TAG_TOKEN) { + endTagInForeignContent(this, token); + } + } + + _processInputToken(token) { + if (this._shouldProcessTokenInForeignContent(token)) { + this._processTokenInForeignContent(token); + } else { + this._processToken(token); + } + + if (token.type === Tokenizer.START_TAG_TOKEN && token.selfClosing && !token.ackSelfClosing) { + this._err(ERR.nonVoidHtmlElementStartTagWithTrailingSolidus); + } + } + + //Integration points + _isIntegrationPoint(element, foreignNS) { + const tn = this.treeAdapter.getTagName(element); + const ns = this.treeAdapter.getNamespaceURI(element); + const attrs = this.treeAdapter.getAttrList(element); + + return foreignContent.isIntegrationPoint(tn, ns, attrs, foreignNS); + } + + //Active formatting elements reconstruction + _reconstructActiveFormattingElements() { + const listLength = this.activeFormattingElements.length; + + if (listLength) { + let unopenIdx = listLength; + let entry = null; + + do { + unopenIdx--; + entry = this.activeFormattingElements.entries[unopenIdx]; + + if (entry.type === FormattingElementList.MARKER_ENTRY || this.openElements.contains(entry.element)) { + unopenIdx++; + break; + } + } while (unopenIdx > 0); + + for (let i = unopenIdx; i < listLength; i++) { + entry = this.activeFormattingElements.entries[i]; + this._insertElement(entry.token, this.treeAdapter.getNamespaceURI(entry.element)); + entry.element = this.openElements.current; + } + } + } + + //Close elements + _closeTableCell() { + this.openElements.generateImpliedEndTags(); + this.openElements.popUntilTableCellPopped(); + this.activeFormattingElements.clearToLastMarker(); + this.insertionMode = IN_ROW_MODE; + } + + _closePElement() { + this.openElements.generateImpliedEndTagsWithExclusion($.P); + this.openElements.popUntilTagNamePopped($.P); + } + + //Insertion modes + _resetInsertionMode() { + for (let i = this.openElements.stackTop, last = false; i >= 0; i--) { + let element = this.openElements.items[i]; + + if (i === 0) { + last = true; + + if (this.fragmentContext) { + element = this.fragmentContext; + } + } + + const tn = this.treeAdapter.getTagName(element); + const newInsertionMode = INSERTION_MODE_RESET_MAP[tn]; + + if (newInsertionMode) { + this.insertionMode = newInsertionMode; + break; + } else if (!last && (tn === $.TD || tn === $.TH)) { + this.insertionMode = IN_CELL_MODE; + break; + } else if (!last && tn === $.HEAD) { + this.insertionMode = IN_HEAD_MODE; + break; + } else if (tn === $.SELECT) { + this._resetInsertionModeForSelect(i); + break; + } else if (tn === $.TEMPLATE) { + this.insertionMode = this.currentTmplInsertionMode; + break; + } else if (tn === $.HTML) { + this.insertionMode = this.headElement ? AFTER_HEAD_MODE : BEFORE_HEAD_MODE; + break; + } else if (last) { + this.insertionMode = IN_BODY_MODE; + break; + } + } + } + + _resetInsertionModeForSelect(selectIdx) { + if (selectIdx > 0) { + for (let i = selectIdx - 1; i > 0; i--) { + const ancestor = this.openElements.items[i]; + const tn = this.treeAdapter.getTagName(ancestor); + + if (tn === $.TEMPLATE) { + break; + } else if (tn === $.TABLE) { + this.insertionMode = IN_SELECT_IN_TABLE_MODE; + return; + } + } + } + + this.insertionMode = IN_SELECT_MODE; + } + + _pushTmplInsertionMode(mode) { + this.tmplInsertionModeStack.push(mode); + this.tmplInsertionModeStackTop++; + this.currentTmplInsertionMode = mode; + } + + _popTmplInsertionMode() { + this.tmplInsertionModeStack.pop(); + this.tmplInsertionModeStackTop--; + this.currentTmplInsertionMode = this.tmplInsertionModeStack[this.tmplInsertionModeStackTop]; + } + + //Foster parenting + _isElementCausesFosterParenting(element) { + const tn = this.treeAdapter.getTagName(element); + + return tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD || tn === $.TR; + } + + _shouldFosterParentOnInsertion() { + return this.fosterParentingEnabled && this._isElementCausesFosterParenting(this.openElements.current); + } + + _findFosterParentingLocation() { + const location = { + parent: null, + beforeElement: null + }; + + for (let i = this.openElements.stackTop; i >= 0; i--) { + const openElement = this.openElements.items[i]; + const tn = this.treeAdapter.getTagName(openElement); + const ns = this.treeAdapter.getNamespaceURI(openElement); + + if (tn === $.TEMPLATE && ns === NS.HTML) { + location.parent = this.treeAdapter.getTemplateContent(openElement); + break; + } else if (tn === $.TABLE) { + location.parent = this.treeAdapter.getParentNode(openElement); + + if (location.parent) { + location.beforeElement = openElement; + } else { + location.parent = this.openElements.items[i - 1]; + } + + break; + } + } + + if (!location.parent) { + location.parent = this.openElements.items[0]; + } + + return location; + } + + _fosterParentElement(element) { + const location = this._findFosterParentingLocation(); + + if (location.beforeElement) { + this.treeAdapter.insertBefore(location.parent, element, location.beforeElement); + } else { + this.treeAdapter.appendChild(location.parent, element); + } + } + + _fosterParentText(chars) { + const location = this._findFosterParentingLocation(); + + if (location.beforeElement) { + this.treeAdapter.insertTextBefore(location.parent, chars, location.beforeElement); + } else { + this.treeAdapter.insertText(location.parent, chars); + } + } + + //Special elements + _isSpecialElement(element) { + const tn = this.treeAdapter.getTagName(element); + const ns = this.treeAdapter.getNamespaceURI(element); + + return HTML.SPECIAL_ELEMENTS[ns][tn]; + } +} + +module.exports = Parser; + +//Adoption agency algorithm +//(see: http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adoptionAgency) +//------------------------------------------------------------------ + +//Steps 5-8 of the algorithm +function aaObtainFormattingElementEntry(p, token) { + let formattingElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName(token.tagName); + + if (formattingElementEntry) { + if (!p.openElements.contains(formattingElementEntry.element)) { + p.activeFormattingElements.removeEntry(formattingElementEntry); + formattingElementEntry = null; + } else if (!p.openElements.hasInScope(token.tagName)) { + formattingElementEntry = null; + } + } else { + genericEndTagInBody(p, token); + } + + return formattingElementEntry; +} + +//Steps 9 and 10 of the algorithm +function aaObtainFurthestBlock(p, formattingElementEntry) { + let furthestBlock = null; + + for (let i = p.openElements.stackTop; i >= 0; i--) { + const element = p.openElements.items[i]; + + if (element === formattingElementEntry.element) { + break; + } + + if (p._isSpecialElement(element)) { + furthestBlock = element; + } + } + + if (!furthestBlock) { + p.openElements.popUntilElementPopped(formattingElementEntry.element); + p.activeFormattingElements.removeEntry(formattingElementEntry); + } + + return furthestBlock; +} + +//Step 13 of the algorithm +function aaInnerLoop(p, furthestBlock, formattingElement) { + let lastElement = furthestBlock; + let nextElement = p.openElements.getCommonAncestor(furthestBlock); + + for (let i = 0, element = nextElement; element !== formattingElement; i++, element = nextElement) { + //NOTE: store next element for the next loop iteration (it may be deleted from the stack by step 9.5) + nextElement = p.openElements.getCommonAncestor(element); + + const elementEntry = p.activeFormattingElements.getElementEntry(element); + const counterOverflow = elementEntry && i >= AA_INNER_LOOP_ITER; + const shouldRemoveFromOpenElements = !elementEntry || counterOverflow; + + if (shouldRemoveFromOpenElements) { + if (counterOverflow) { + p.activeFormattingElements.removeEntry(elementEntry); + } + + p.openElements.remove(element); + } else { + element = aaRecreateElementFromEntry(p, elementEntry); + + if (lastElement === furthestBlock) { + p.activeFormattingElements.bookmark = elementEntry; + } + + p.treeAdapter.detachNode(lastElement); + p.treeAdapter.appendChild(element, lastElement); + lastElement = element; + } + } + + return lastElement; +} + +//Step 13.7 of the algorithm +function aaRecreateElementFromEntry(p, elementEntry) { + const ns = p.treeAdapter.getNamespaceURI(elementEntry.element); + const newElement = p.treeAdapter.createElement(elementEntry.token.tagName, ns, elementEntry.token.attrs); + + p.openElements.replace(elementEntry.element, newElement); + elementEntry.element = newElement; + + return newElement; +} + +//Step 14 of the algorithm +function aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement) { + if (p._isElementCausesFosterParenting(commonAncestor)) { + p._fosterParentElement(lastElement); + } else { + const tn = p.treeAdapter.getTagName(commonAncestor); + const ns = p.treeAdapter.getNamespaceURI(commonAncestor); + + if (tn === $.TEMPLATE && ns === NS.HTML) { + commonAncestor = p.treeAdapter.getTemplateContent(commonAncestor); + } + + p.treeAdapter.appendChild(commonAncestor, lastElement); + } +} + +//Steps 15-19 of the algorithm +function aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry) { + const ns = p.treeAdapter.getNamespaceURI(formattingElementEntry.element); + const token = formattingElementEntry.token; + const newElement = p.treeAdapter.createElement(token.tagName, ns, token.attrs); + + p._adoptNodes(furthestBlock, newElement); + p.treeAdapter.appendChild(furthestBlock, newElement); + + p.activeFormattingElements.insertElementAfterBookmark(newElement, formattingElementEntry.token); + p.activeFormattingElements.removeEntry(formattingElementEntry); + + p.openElements.remove(formattingElementEntry.element); + p.openElements.insertAfter(furthestBlock, newElement); +} + +//Algorithm entry point +function callAdoptionAgency(p, token) { + let formattingElementEntry; + + for (let i = 0; i < AA_OUTER_LOOP_ITER; i++) { + formattingElementEntry = aaObtainFormattingElementEntry(p, token, formattingElementEntry); + + if (!formattingElementEntry) { + break; + } + + const furthestBlock = aaObtainFurthestBlock(p, formattingElementEntry); + + if (!furthestBlock) { + break; + } + + p.activeFormattingElements.bookmark = formattingElementEntry; + + const lastElement = aaInnerLoop(p, furthestBlock, formattingElementEntry.element); + const commonAncestor = p.openElements.getCommonAncestor(formattingElementEntry.element); + + p.treeAdapter.detachNode(lastElement); + aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement); + aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry); + } +} + +//Generic token handlers +//------------------------------------------------------------------ +function ignoreToken() { + //NOTE: do nothing =) +} + +function misplacedDoctype(p) { + p._err(ERR.misplacedDoctype); +} + +function appendComment(p, token) { + p._appendCommentNode(token, p.openElements.currentTmplContent || p.openElements.current); +} + +function appendCommentToRootHtmlElement(p, token) { + p._appendCommentNode(token, p.openElements.items[0]); +} + +function appendCommentToDocument(p, token) { + p._appendCommentNode(token, p.document); +} + +function insertCharacters(p, token) { + p._insertCharacters(token); +} + +function stopParsing(p) { + p.stopped = true; +} + +// The "initial" insertion mode +//------------------------------------------------------------------ +function doctypeInInitialMode(p, token) { + p._setDocumentType(token); + + const mode = token.forceQuirks ? HTML.DOCUMENT_MODE.QUIRKS : doctype.getDocumentMode(token); + + if (!doctype.isConforming(token)) { + p._err(ERR.nonConformingDoctype); + } + + p.treeAdapter.setDocumentMode(p.document, mode); + + p.insertionMode = BEFORE_HTML_MODE; +} + +function tokenInInitialMode(p, token) { + p._err(ERR.missingDoctype, { beforeToken: true }); + p.treeAdapter.setDocumentMode(p.document, HTML.DOCUMENT_MODE.QUIRKS); + p.insertionMode = BEFORE_HTML_MODE; + p._processToken(token); +} + +// The "before html" insertion mode +//------------------------------------------------------------------ +function startTagBeforeHtml(p, token) { + if (token.tagName === $.HTML) { + p._insertElement(token, NS.HTML); + p.insertionMode = BEFORE_HEAD_MODE; + } else { + tokenBeforeHtml(p, token); + } +} + +function endTagBeforeHtml(p, token) { + const tn = token.tagName; + + if (tn === $.HTML || tn === $.HEAD || tn === $.BODY || tn === $.BR) { + tokenBeforeHtml(p, token); + } +} + +function tokenBeforeHtml(p, token) { + p._insertFakeRootElement(); + p.insertionMode = BEFORE_HEAD_MODE; + p._processToken(token); +} + +// The "before head" insertion mode +//------------------------------------------------------------------ +function startTagBeforeHead(p, token) { + const tn = token.tagName; + + if (tn === $.HTML) { + startTagInBody(p, token); + } else if (tn === $.HEAD) { + p._insertElement(token, NS.HTML); + p.headElement = p.openElements.current; + p.insertionMode = IN_HEAD_MODE; + } else { + tokenBeforeHead(p, token); + } +} + +function endTagBeforeHead(p, token) { + const tn = token.tagName; + + if (tn === $.HEAD || tn === $.BODY || tn === $.HTML || tn === $.BR) { + tokenBeforeHead(p, token); + } else { + p._err(ERR.endTagWithoutMatchingOpenElement); + } +} + +function tokenBeforeHead(p, token) { + p._insertFakeElement($.HEAD); + p.headElement = p.openElements.current; + p.insertionMode = IN_HEAD_MODE; + p._processToken(token); +} + +// The "in head" insertion mode +//------------------------------------------------------------------ +function startTagInHead(p, token) { + const tn = token.tagName; + + if (tn === $.HTML) { + startTagInBody(p, token); + } else if (tn === $.BASE || tn === $.BASEFONT || tn === $.BGSOUND || tn === $.LINK || tn === $.META) { + p._appendElement(token, NS.HTML); + token.ackSelfClosing = true; + } else if (tn === $.TITLE) { + p._switchToTextParsing(token, Tokenizer.MODE.RCDATA); + } else if (tn === $.NOSCRIPT) { + if (p.options.scriptingEnabled) { + p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT); + } else { + p._insertElement(token, NS.HTML); + p.insertionMode = IN_HEAD_NO_SCRIPT_MODE; + } + } else if (tn === $.NOFRAMES || tn === $.STYLE) { + p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT); + } else if (tn === $.SCRIPT) { + p._switchToTextParsing(token, Tokenizer.MODE.SCRIPT_DATA); + } else if (tn === $.TEMPLATE) { + p._insertTemplate(token, NS.HTML); + p.activeFormattingElements.insertMarker(); + p.framesetOk = false; + p.insertionMode = IN_TEMPLATE_MODE; + p._pushTmplInsertionMode(IN_TEMPLATE_MODE); + } else if (tn === $.HEAD) { + p._err(ERR.misplacedStartTagForHeadElement); + } else { + tokenInHead(p, token); + } +} + +function endTagInHead(p, token) { + const tn = token.tagName; + + if (tn === $.HEAD) { + p.openElements.pop(); + p.insertionMode = AFTER_HEAD_MODE; + } else if (tn === $.BODY || tn === $.BR || tn === $.HTML) { + tokenInHead(p, token); + } else if (tn === $.TEMPLATE) { + if (p.openElements.tmplCount > 0) { + p.openElements.generateImpliedEndTagsThoroughly(); + + if (p.openElements.currentTagName !== $.TEMPLATE) { + p._err(ERR.closingOfElementWithOpenChildElements); + } + + p.openElements.popUntilTagNamePopped($.TEMPLATE); + p.activeFormattingElements.clearToLastMarker(); + p._popTmplInsertionMode(); + p._resetInsertionMode(); + } else { + p._err(ERR.endTagWithoutMatchingOpenElement); + } + } else { + p._err(ERR.endTagWithoutMatchingOpenElement); + } +} + +function tokenInHead(p, token) { + p.openElements.pop(); + p.insertionMode = AFTER_HEAD_MODE; + p._processToken(token); +} + +// The "in head no script" insertion mode +//------------------------------------------------------------------ +function startTagInHeadNoScript(p, token) { + const tn = token.tagName; + + if (tn === $.HTML) { + startTagInBody(p, token); + } else if ( + tn === $.BASEFONT || + tn === $.BGSOUND || + tn === $.HEAD || + tn === $.LINK || + tn === $.META || + tn === $.NOFRAMES || + tn === $.STYLE + ) { + startTagInHead(p, token); + } else if (tn === $.NOSCRIPT) { + p._err(ERR.nestedNoscriptInHead); + } else { + tokenInHeadNoScript(p, token); + } +} + +function endTagInHeadNoScript(p, token) { + const tn = token.tagName; + + if (tn === $.NOSCRIPT) { + p.openElements.pop(); + p.insertionMode = IN_HEAD_MODE; + } else if (tn === $.BR) { + tokenInHeadNoScript(p, token); + } else { + p._err(ERR.endTagWithoutMatchingOpenElement); + } +} + +function tokenInHeadNoScript(p, token) { + const errCode = + token.type === Tokenizer.EOF_TOKEN ? ERR.openElementsLeftAfterEof : ERR.disallowedContentInNoscriptInHead; + + p._err(errCode); + p.openElements.pop(); + p.insertionMode = IN_HEAD_MODE; + p._processToken(token); +} + +// The "after head" insertion mode +//------------------------------------------------------------------ +function startTagAfterHead(p, token) { + const tn = token.tagName; + + if (tn === $.HTML) { + startTagInBody(p, token); + } else if (tn === $.BODY) { + p._insertElement(token, NS.HTML); + p.framesetOk = false; + p.insertionMode = IN_BODY_MODE; + } else if (tn === $.FRAMESET) { + p._insertElement(token, NS.HTML); + p.insertionMode = IN_FRAMESET_MODE; + } else if ( + tn === $.BASE || + tn === $.BASEFONT || + tn === $.BGSOUND || + tn === $.LINK || + tn === $.META || + tn === $.NOFRAMES || + tn === $.SCRIPT || + tn === $.STYLE || + tn === $.TEMPLATE || + tn === $.TITLE + ) { + p._err(ERR.abandonedHeadElementChild); + p.openElements.push(p.headElement); + startTagInHead(p, token); + p.openElements.remove(p.headElement); + } else if (tn === $.HEAD) { + p._err(ERR.misplacedStartTagForHeadElement); + } else { + tokenAfterHead(p, token); + } +} + +function endTagAfterHead(p, token) { + const tn = token.tagName; + + if (tn === $.BODY || tn === $.HTML || tn === $.BR) { + tokenAfterHead(p, token); + } else if (tn === $.TEMPLATE) { + endTagInHead(p, token); + } else { + p._err(ERR.endTagWithoutMatchingOpenElement); + } +} + +function tokenAfterHead(p, token) { + p._insertFakeElement($.BODY); + p.insertionMode = IN_BODY_MODE; + p._processToken(token); +} + +// The "in body" insertion mode +//------------------------------------------------------------------ +function whitespaceCharacterInBody(p, token) { + p._reconstructActiveFormattingElements(); + p._insertCharacters(token); +} + +function characterInBody(p, token) { + p._reconstructActiveFormattingElements(); + p._insertCharacters(token); + p.framesetOk = false; +} + +function htmlStartTagInBody(p, token) { + if (p.openElements.tmplCount === 0) { + p.treeAdapter.adoptAttributes(p.openElements.items[0], token.attrs); + } +} + +function bodyStartTagInBody(p, token) { + const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement(); + + if (bodyElement && p.openElements.tmplCount === 0) { + p.framesetOk = false; + p.treeAdapter.adoptAttributes(bodyElement, token.attrs); + } +} + +function framesetStartTagInBody(p, token) { + const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement(); + + if (p.framesetOk && bodyElement) { + p.treeAdapter.detachNode(bodyElement); + p.openElements.popAllUpToHtmlElement(); + p._insertElement(token, NS.HTML); + p.insertionMode = IN_FRAMESET_MODE; + } +} + +function addressStartTagInBody(p, token) { + if (p.openElements.hasInButtonScope($.P)) { + p._closePElement(); + } + + p._insertElement(token, NS.HTML); +} + +function numberedHeaderStartTagInBody(p, token) { + if (p.openElements.hasInButtonScope($.P)) { + p._closePElement(); + } + + const tn = p.openElements.currentTagName; + + if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) { + p.openElements.pop(); + } + + p._insertElement(token, NS.HTML); +} + +function preStartTagInBody(p, token) { + if (p.openElements.hasInButtonScope($.P)) { + p._closePElement(); + } + + p._insertElement(token, NS.HTML); + //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move + //on to the next one. (Newlines at the start of pre blocks are ignored as an authoring convenience.) + p.skipNextNewLine = true; + p.framesetOk = false; +} + +function formStartTagInBody(p, token) { + const inTemplate = p.openElements.tmplCount > 0; + + if (!p.formElement || inTemplate) { + if (p.openElements.hasInButtonScope($.P)) { + p._closePElement(); + } + + p._insertElement(token, NS.HTML); + + if (!inTemplate) { + p.formElement = p.openElements.current; + } + } +} + +function listItemStartTagInBody(p, token) { + p.framesetOk = false; + + const tn = token.tagName; + + for (let i = p.openElements.stackTop; i >= 0; i--) { + const element = p.openElements.items[i]; + const elementTn = p.treeAdapter.getTagName(element); + let closeTn = null; + + if (tn === $.LI && elementTn === $.LI) { + closeTn = $.LI; + } else if ((tn === $.DD || tn === $.DT) && (elementTn === $.DD || elementTn === $.DT)) { + closeTn = elementTn; + } + + if (closeTn) { + p.openElements.generateImpliedEndTagsWithExclusion(closeTn); + p.openElements.popUntilTagNamePopped(closeTn); + break; + } + + if (elementTn !== $.ADDRESS && elementTn !== $.DIV && elementTn !== $.P && p._isSpecialElement(element)) { + break; + } + } + + if (p.openElements.hasInButtonScope($.P)) { + p._closePElement(); + } + + p._insertElement(token, NS.HTML); +} + +function plaintextStartTagInBody(p, token) { + if (p.openElements.hasInButtonScope($.P)) { + p._closePElement(); + } + + p._insertElement(token, NS.HTML); + p.tokenizer.state = Tokenizer.MODE.PLAINTEXT; +} + +function buttonStartTagInBody(p, token) { + if (p.openElements.hasInScope($.BUTTON)) { + p.openElements.generateImpliedEndTags(); + p.openElements.popUntilTagNamePopped($.BUTTON); + } + + p._reconstructActiveFormattingElements(); + p._insertElement(token, NS.HTML); + p.framesetOk = false; +} + +function aStartTagInBody(p, token) { + const activeElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName($.A); + + if (activeElementEntry) { + callAdoptionAgency(p, token); + p.openElements.remove(activeElementEntry.element); + p.activeFormattingElements.removeEntry(activeElementEntry); + } + + p._reconstructActiveFormattingElements(); + p._insertElement(token, NS.HTML); + p.activeFormattingElements.pushElement(p.openElements.current, token); +} + +function bStartTagInBody(p, token) { + p._reconstructActiveFormattingElements(); + p._insertElement(token, NS.HTML); + p.activeFormattingElements.pushElement(p.openElements.current, token); +} + +function nobrStartTagInBody(p, token) { + p._reconstructActiveFormattingElements(); + + if (p.openElements.hasInScope($.NOBR)) { + callAdoptionAgency(p, token); + p._reconstructActiveFormattingElements(); + } + + p._insertElement(token, NS.HTML); + p.activeFormattingElements.pushElement(p.openElements.current, token); +} + +function appletStartTagInBody(p, token) { + p._reconstructActiveFormattingElements(); + p._insertElement(token, NS.HTML); + p.activeFormattingElements.insertMarker(); + p.framesetOk = false; +} + +function tableStartTagInBody(p, token) { + if ( + p.treeAdapter.getDocumentMode(p.document) !== HTML.DOCUMENT_MODE.QUIRKS && + p.openElements.hasInButtonScope($.P) + ) { + p._closePElement(); + } + + p._insertElement(token, NS.HTML); + p.framesetOk = false; + p.insertionMode = IN_TABLE_MODE; +} + +function areaStartTagInBody(p, token) { + p._reconstructActiveFormattingElements(); + p._appendElement(token, NS.HTML); + p.framesetOk = false; + token.ackSelfClosing = true; +} + +function inputStartTagInBody(p, token) { + p._reconstructActiveFormattingElements(); + p._appendElement(token, NS.HTML); + + const inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE); + + if (!inputType || inputType.toLowerCase() !== HIDDEN_INPUT_TYPE) { + p.framesetOk = false; + } + + token.ackSelfClosing = true; +} + +function paramStartTagInBody(p, token) { + p._appendElement(token, NS.HTML); + token.ackSelfClosing = true; +} + +function hrStartTagInBody(p, token) { + if (p.openElements.hasInButtonScope($.P)) { + p._closePElement(); + } + + p._appendElement(token, NS.HTML); + p.framesetOk = false; + token.ackSelfClosing = true; +} + +function imageStartTagInBody(p, token) { + token.tagName = $.IMG; + areaStartTagInBody(p, token); +} + +function textareaStartTagInBody(p, token) { + p._insertElement(token, NS.HTML); + //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move + //on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.) + p.skipNextNewLine = true; + p.tokenizer.state = Tokenizer.MODE.RCDATA; + p.originalInsertionMode = p.insertionMode; + p.framesetOk = false; + p.insertionMode = TEXT_MODE; +} + +function xmpStartTagInBody(p, token) { + if (p.openElements.hasInButtonScope($.P)) { + p._closePElement(); + } + + p._reconstructActiveFormattingElements(); + p.framesetOk = false; + p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT); +} + +function iframeStartTagInBody(p, token) { + p.framesetOk = false; + p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT); +} + +//NOTE: here we assume that we always act as an user agent with enabled plugins, so we parse +//<noembed> as a rawtext. +function noembedStartTagInBody(p, token) { + p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT); +} + +function selectStartTagInBody(p, token) { + p._reconstructActiveFormattingElements(); + p._insertElement(token, NS.HTML); + p.framesetOk = false; + + if ( + p.insertionMode === IN_TABLE_MODE || + p.insertionMode === IN_CAPTION_MODE || + p.insertionMode === IN_TABLE_BODY_MODE || + p.insertionMode === IN_ROW_MODE || + p.insertionMode === IN_CELL_MODE + ) { + p.insertionMode = IN_SELECT_IN_TABLE_MODE; + } else { + p.insertionMode = IN_SELECT_MODE; + } +} + +function optgroupStartTagInBody(p, token) { + if (p.openElements.currentTagName === $.OPTION) { + p.openElements.pop(); + } + + p._reconstructActiveFormattingElements(); + p._insertElement(token, NS.HTML); +} + +function rbStartTagInBody(p, token) { + if (p.openElements.hasInScope($.RUBY)) { + p.openElements.generateImpliedEndTags(); + } + + p._insertElement(token, NS.HTML); +} + +function rtStartTagInBody(p, token) { + if (p.openElements.hasInScope($.RUBY)) { + p.openElements.generateImpliedEndTagsWithExclusion($.RTC); + } + + p._insertElement(token, NS.HTML); +} + +function menuStartTagInBody(p, token) { + if (p.openElements.hasInButtonScope($.P)) { + p._closePElement(); + } + + p._insertElement(token, NS.HTML); +} + +function mathStartTagInBody(p, token) { + p._reconstructActiveFormattingElements(); + + foreignContent.adjustTokenMathMLAttrs(token); + foreignContent.adjustTokenXMLAttrs(token); + + if (token.selfClosing) { + p._appendElement(token, NS.MATHML); + } else { + p._insertElement(token, NS.MATHML); + } + + token.ackSelfClosing = true; +} + +function svgStartTagInBody(p, token) { + p._reconstructActiveFormattingElements(); + + foreignContent.adjustTokenSVGAttrs(token); + foreignContent.adjustTokenXMLAttrs(token); + + if (token.selfClosing) { + p._appendElement(token, NS.SVG); + } else { + p._insertElement(token, NS.SVG); + } + + token.ackSelfClosing = true; +} + +function genericStartTagInBody(p, token) { + p._reconstructActiveFormattingElements(); + p._insertElement(token, NS.HTML); +} + +//OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here. +//It's faster than using dictionary. +function startTagInBody(p, token) { + const tn = token.tagName; + + switch (tn.length) { + case 1: + if (tn === $.I || tn === $.S || tn === $.B || tn === $.U) { + bStartTagInBody(p, token); + } else if (tn === $.P) { + addressStartTagInBody(p, token); + } else if (tn === $.A) { + aStartTagInBody(p, token); + } else { + genericStartTagInBody(p, token); + } + + break; + + case 2: + if (tn === $.DL || tn === $.OL || tn === $.UL) { + addressStartTagInBody(p, token); + } else if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) { + numberedHeaderStartTagInBody(p, token); + } else if (tn === $.LI || tn === $.DD || tn === $.DT) { + listItemStartTagInBody(p, token); + } else if (tn === $.EM || tn === $.TT) { + bStartTagInBody(p, token); + } else if (tn === $.BR) { + areaStartTagInBody(p, token); + } else if (tn === $.HR) { + hrStartTagInBody(p, token); + } else if (tn === $.RB) { + rbStartTagInBody(p, token); + } else if (tn === $.RT || tn === $.RP) { + rtStartTagInBody(p, token); + } else if (tn !== $.TH && tn !== $.TD && tn !== $.TR) { + genericStartTagInBody(p, token); + } + + break; + + case 3: + if (tn === $.DIV || tn === $.DIR || tn === $.NAV) { + addressStartTagInBody(p, token); + } else if (tn === $.PRE) { + preStartTagInBody(p, token); + } else if (tn === $.BIG) { + bStartTagInBody(p, token); + } else if (tn === $.IMG || tn === $.WBR) { + areaStartTagInBody(p, token); + } else if (tn === $.XMP) { + xmpStartTagInBody(p, token); + } else if (tn === $.SVG) { + svgStartTagInBody(p, token); + } else if (tn === $.RTC) { + rbStartTagInBody(p, token); + } else if (tn !== $.COL) { + genericStartTagInBody(p, token); + } + + break; + + case 4: + if (tn === $.HTML) { + htmlStartTagInBody(p, token); + } else if (tn === $.BASE || tn === $.LINK || tn === $.META) { + startTagInHead(p, token); + } else if (tn === $.BODY) { + bodyStartTagInBody(p, token); + } else if (tn === $.MAIN || tn === $.MENU) { + addressStartTagInBody(p, token); + } else if (tn === $.FORM) { + formStartTagInBody(p, token); + } else if (tn === $.CODE || tn === $.FONT) { + bStartTagInBody(p, token); + } else if (tn === $.NOBR) { + nobrStartTagInBody(p, token); + } else if (tn === $.AREA) { + areaStartTagInBody(p, token); + } else if (tn === $.MATH) { + mathStartTagInBody(p, token); + } else if (tn === $.MENU) { + menuStartTagInBody(p, token); + } else if (tn !== $.HEAD) { + genericStartTagInBody(p, token); + } + + break; + + case 5: + if (tn === $.STYLE || tn === $.TITLE) { + startTagInHead(p, token); + } else if (tn === $.ASIDE) { + addressStartTagInBody(p, token); + } else if (tn === $.SMALL) { + bStartTagInBody(p, token); + } else if (tn === $.TABLE) { + tableStartTagInBody(p, token); + } else if (tn === $.EMBED) { + areaStartTagInBody(p, token); + } else if (tn === $.INPUT) { + inputStartTagInBody(p, token); + } else if (tn === $.PARAM || tn === $.TRACK) { + paramStartTagInBody(p, token); + } else if (tn === $.IMAGE) { + imageStartTagInBody(p, token); + } else if (tn !== $.FRAME && tn !== $.TBODY && tn !== $.TFOOT && tn !== $.THEAD) { + genericStartTagInBody(p, token); + } + + break; + + case 6: + if (tn === $.SCRIPT) { + startTagInHead(p, token); + } else if ( + tn === $.CENTER || + tn === $.FIGURE || + tn === $.FOOTER || + tn === $.HEADER || + tn === $.HGROUP || + tn === $.DIALOG + ) { + addressStartTagInBody(p, token); + } else if (tn === $.BUTTON) { + buttonStartTagInBody(p, token); + } else if (tn === $.STRIKE || tn === $.STRONG) { + bStartTagInBody(p, token); + } else if (tn === $.APPLET || tn === $.OBJECT) { + appletStartTagInBody(p, token); + } else if (tn === $.KEYGEN) { + areaStartTagInBody(p, token); + } else if (tn === $.SOURCE) { + paramStartTagInBody(p, token); + } else if (tn === $.IFRAME) { + iframeStartTagInBody(p, token); + } else if (tn === $.SELECT) { + selectStartTagInBody(p, token); + } else if (tn === $.OPTION) { + optgroupStartTagInBody(p, token); + } else { + genericStartTagInBody(p, token); + } + + break; + + case 7: + if (tn === $.BGSOUND) { + startTagInHead(p, token); + } else if ( + tn === $.DETAILS || + tn === $.ADDRESS || + tn === $.ARTICLE || + tn === $.SECTION || + tn === $.SUMMARY + ) { + addressStartTagInBody(p, token); + } else if (tn === $.LISTING) { + preStartTagInBody(p, token); + } else if (tn === $.MARQUEE) { + appletStartTagInBody(p, token); + } else if (tn === $.NOEMBED) { + noembedStartTagInBody(p, token); + } else if (tn !== $.CAPTION) { + genericStartTagInBody(p, token); + } + + break; + + case 8: + if (tn === $.BASEFONT) { + startTagInHead(p, token); + } else if (tn === $.FRAMESET) { + framesetStartTagInBody(p, token); + } else if (tn === $.FIELDSET) { + addressStartTagInBody(p, token); + } else if (tn === $.TEXTAREA) { + textareaStartTagInBody(p, token); + } else if (tn === $.TEMPLATE) { + startTagInHead(p, token); + } else if (tn === $.NOSCRIPT) { + if (p.options.scriptingEnabled) { + noembedStartTagInBody(p, token); + } else { + genericStartTagInBody(p, token); + } + } else if (tn === $.OPTGROUP) { + optgroupStartTagInBody(p, token); + } else if (tn !== $.COLGROUP) { + genericStartTagInBody(p, token); + } + + break; + + case 9: + if (tn === $.PLAINTEXT) { + plaintextStartTagInBody(p, token); + } else { + genericStartTagInBody(p, token); + } + + break; + + case 10: + if (tn === $.BLOCKQUOTE || tn === $.FIGCAPTION) { + addressStartTagInBody(p, token); + } else { + genericStartTagInBody(p, token); + } + + break; + + default: + genericStartTagInBody(p, token); + } +} + +function bodyEndTagInBody(p) { + if (p.openElements.hasInScope($.BODY)) { + p.insertionMode = AFTER_BODY_MODE; + } +} + +function htmlEndTagInBody(p, token) { + if (p.openElements.hasInScope($.BODY)) { + p.insertionMode = AFTER_BODY_MODE; + p._processToken(token); + } +} + +function addressEndTagInBody(p, token) { + const tn = token.tagName; + + if (p.openElements.hasInScope(tn)) { + p.openElements.generateImpliedEndTags(); + p.openElements.popUntilTagNamePopped(tn); + } +} + +function formEndTagInBody(p) { + const inTemplate = p.openElements.tmplCount > 0; + const formElement = p.formElement; + + if (!inTemplate) { + p.formElement = null; + } + + if ((formElement || inTemplate) && p.openElements.hasInScope($.FORM)) { + p.openElements.generateImpliedEndTags(); + + if (inTemplate) { + p.openElements.popUntilTagNamePopped($.FORM); + } else { + p.openElements.remove(formElement); + } + } +} + +function pEndTagInBody(p) { + if (!p.openElements.hasInButtonScope($.P)) { + p._insertFakeElement($.P); + } + + p._closePElement(); +} + +function liEndTagInBody(p) { + if (p.openElements.hasInListItemScope($.LI)) { + p.openElements.generateImpliedEndTagsWithExclusion($.LI); + p.openElements.popUntilTagNamePopped($.LI); + } +} + +function ddEndTagInBody(p, token) { + const tn = token.tagName; + + if (p.openElements.hasInScope(tn)) { + p.openElements.generateImpliedEndTagsWithExclusion(tn); + p.openElements.popUntilTagNamePopped(tn); + } +} + +function numberedHeaderEndTagInBody(p) { + if (p.openElements.hasNumberedHeaderInScope()) { + p.openElements.generateImpliedEndTags(); + p.openElements.popUntilNumberedHeaderPopped(); + } +} + +function appletEndTagInBody(p, token) { + const tn = token.tagName; + + if (p.openElements.hasInScope(tn)) { + p.openElements.generateImpliedEndTags(); + p.openElements.popUntilTagNamePopped(tn); + p.activeFormattingElements.clearToLastMarker(); + } +} + +function brEndTagInBody(p) { + p._reconstructActiveFormattingElements(); + p._insertFakeElement($.BR); + p.openElements.pop(); + p.framesetOk = false; +} + +function genericEndTagInBody(p, token) { + const tn = token.tagName; + + for (let i = p.openElements.stackTop; i > 0; i--) { + const element = p.openElements.items[i]; + + if (p.treeAdapter.getTagName(element) === tn) { + p.openElements.generateImpliedEndTagsWithExclusion(tn); + p.openElements.popUntilElementPopped(element); + break; + } + + if (p._isSpecialElement(element)) { + break; + } + } +} + +//OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here. +//It's faster than using dictionary. +function endTagInBody(p, token) { + const tn = token.tagName; + + switch (tn.length) { + case 1: + if (tn === $.A || tn === $.B || tn === $.I || tn === $.S || tn === $.U) { + callAdoptionAgency(p, token); + } else if (tn === $.P) { + pEndTagInBody(p, token); + } else { + genericEndTagInBody(p, token); + } + + break; + + case 2: + if (tn === $.DL || tn === $.UL || tn === $.OL) { + addressEndTagInBody(p, token); + } else if (tn === $.LI) { + liEndTagInBody(p, token); + } else if (tn === $.DD || tn === $.DT) { + ddEndTagInBody(p, token); + } else if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) { + numberedHeaderEndTagInBody(p, token); + } else if (tn === $.BR) { + brEndTagInBody(p, token); + } else if (tn === $.EM || tn === $.TT) { + callAdoptionAgency(p, token); + } else { + genericEndTagInBody(p, token); + } + + break; + + case 3: + if (tn === $.BIG) { + callAdoptionAgency(p, token); + } else if (tn === $.DIR || tn === $.DIV || tn === $.NAV || tn === $.PRE) { + addressEndTagInBody(p, token); + } else { + genericEndTagInBody(p, token); + } + + break; + + case 4: + if (tn === $.BODY) { + bodyEndTagInBody(p, token); + } else if (tn === $.HTML) { + htmlEndTagInBody(p, token); + } else if (tn === $.FORM) { + formEndTagInBody(p, token); + } else if (tn === $.CODE || tn === $.FONT || tn === $.NOBR) { + callAdoptionAgency(p, token); + } else if (tn === $.MAIN || tn === $.MENU) { + addressEndTagInBody(p, token); + } else { + genericEndTagInBody(p, token); + } + + break; + + case 5: + if (tn === $.ASIDE) { + addressEndTagInBody(p, token); + } else if (tn === $.SMALL) { + callAdoptionAgency(p, token); + } else { + genericEndTagInBody(p, token); + } + + break; + + case 6: + if ( + tn === $.CENTER || + tn === $.FIGURE || + tn === $.FOOTER || + tn === $.HEADER || + tn === $.HGROUP || + tn === $.DIALOG + ) { + addressEndTagInBody(p, token); + } else if (tn === $.APPLET || tn === $.OBJECT) { + appletEndTagInBody(p, token); + } else if (tn === $.STRIKE || tn === $.STRONG) { + callAdoptionAgency(p, token); + } else { + genericEndTagInBody(p, token); + } + + break; + + case 7: + if ( + tn === $.ADDRESS || + tn === $.ARTICLE || + tn === $.DETAILS || + tn === $.SECTION || + tn === $.SUMMARY || + tn === $.LISTING + ) { + addressEndTagInBody(p, token); + } else if (tn === $.MARQUEE) { + appletEndTagInBody(p, token); + } else { + genericEndTagInBody(p, token); + } + + break; + + case 8: + if (tn === $.FIELDSET) { + addressEndTagInBody(p, token); + } else if (tn === $.TEMPLATE) { + endTagInHead(p, token); + } else { + genericEndTagInBody(p, token); + } + + break; + + case 10: + if (tn === $.BLOCKQUOTE || tn === $.FIGCAPTION) { + addressEndTagInBody(p, token); + } else { + genericEndTagInBody(p, token); + } + + break; + + default: + genericEndTagInBody(p, token); + } +} + +function eofInBody(p, token) { + if (p.tmplInsertionModeStackTop > -1) { + eofInTemplate(p, token); + } else { + p.stopped = true; + } +} + +// The "text" insertion mode +//------------------------------------------------------------------ +function endTagInText(p, token) { + if (token.tagName === $.SCRIPT) { + p.pendingScript = p.openElements.current; + } + + p.openElements.pop(); + p.insertionMode = p.originalInsertionMode; +} + +function eofInText(p, token) { + p._err(ERR.eofInElementThatCanContainOnlyText); + p.openElements.pop(); + p.insertionMode = p.originalInsertionMode; + p._processToken(token); +} + +// The "in table" insertion mode +//------------------------------------------------------------------ +function characterInTable(p, token) { + const curTn = p.openElements.currentTagName; + + if (curTn === $.TABLE || curTn === $.TBODY || curTn === $.TFOOT || curTn === $.THEAD || curTn === $.TR) { + p.pendingCharacterTokens = []; + p.hasNonWhitespacePendingCharacterToken = false; + p.originalInsertionMode = p.insertionMode; + p.insertionMode = IN_TABLE_TEXT_MODE; + p._processToken(token); + } else { + tokenInTable(p, token); + } +} + +function captionStartTagInTable(p, token) { + p.openElements.clearBackToTableContext(); + p.activeFormattingElements.insertMarker(); + p._insertElement(token, NS.HTML); + p.insertionMode = IN_CAPTION_MODE; +} + +function colgroupStartTagInTable(p, token) { + p.openElements.clearBackToTableContext(); + p._insertElement(token, NS.HTML); + p.insertionMode = IN_COLUMN_GROUP_MODE; +} + +function colStartTagInTable(p, token) { + p.openElements.clearBackToTableContext(); + p._insertFakeElement($.COLGROUP); + p.insertionMode = IN_COLUMN_GROUP_MODE; + p._processToken(token); +} + +function tbodyStartTagInTable(p, token) { + p.openElements.clearBackToTableContext(); + p._insertElement(token, NS.HTML); + p.insertionMode = IN_TABLE_BODY_MODE; +} + +function tdStartTagInTable(p, token) { + p.openElements.clearBackToTableContext(); + p._insertFakeElement($.TBODY); + p.insertionMode = IN_TABLE_BODY_MODE; + p._processToken(token); +} + +function tableStartTagInTable(p, token) { + if (p.openElements.hasInTableScope($.TABLE)) { + p.openElements.popUntilTagNamePopped($.TABLE); + p._resetInsertionMode(); + p._processToken(token); + } +} + +function inputStartTagInTable(p, token) { + const inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE); + + if (inputType && inputType.toLowerCase() === HIDDEN_INPUT_TYPE) { + p._appendElement(token, NS.HTML); + } else { + tokenInTable(p, token); + } + + token.ackSelfClosing = true; +} + +function formStartTagInTable(p, token) { + if (!p.formElement && p.openElements.tmplCount === 0) { + p._insertElement(token, NS.HTML); + p.formElement = p.openElements.current; + p.openElements.pop(); + } +} + +function startTagInTable(p, token) { + const tn = token.tagName; + + switch (tn.length) { + case 2: + if (tn === $.TD || tn === $.TH || tn === $.TR) { + tdStartTagInTable(p, token); + } else { + tokenInTable(p, token); + } + + break; + + case 3: + if (tn === $.COL) { + colStartTagInTable(p, token); + } else { + tokenInTable(p, token); + } + + break; + + case 4: + if (tn === $.FORM) { + formStartTagInTable(p, token); + } else { + tokenInTable(p, token); + } + + break; + + case 5: + if (tn === $.TABLE) { + tableStartTagInTable(p, token); + } else if (tn === $.STYLE) { + startTagInHead(p, token); + } else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) { + tbodyStartTagInTable(p, token); + } else if (tn === $.INPUT) { + inputStartTagInTable(p, token); + } else { + tokenInTable(p, token); + } + + break; + + case 6: + if (tn === $.SCRIPT) { + startTagInHead(p, token); + } else { + tokenInTable(p, token); + } + + break; + + case 7: + if (tn === $.CAPTION) { + captionStartTagInTable(p, token); + } else { + tokenInTable(p, token); + } + + break; + + case 8: + if (tn === $.COLGROUP) { + colgroupStartTagInTable(p, token); + } else if (tn === $.TEMPLATE) { + startTagInHead(p, token); + } else { + tokenInTable(p, token); + } + + break; + + default: + tokenInTable(p, token); + } +} + +function endTagInTable(p, token) { + const tn = token.tagName; + + if (tn === $.TABLE) { + if (p.openElements.hasInTableScope($.TABLE)) { + p.openElements.popUntilTagNamePopped($.TABLE); + p._resetInsertionMode(); + } + } else if (tn === $.TEMPLATE) { + endTagInHead(p, token); + } else if ( + tn !== $.BODY && + tn !== $.CAPTION && + tn !== $.COL && + tn !== $.COLGROUP && + tn !== $.HTML && + tn !== $.TBODY && + tn !== $.TD && + tn !== $.TFOOT && + tn !== $.TH && + tn !== $.THEAD && + tn !== $.TR + ) { + tokenInTable(p, token); + } +} + +function tokenInTable(p, token) { + const savedFosterParentingState = p.fosterParentingEnabled; + + p.fosterParentingEnabled = true; + p._processTokenInBodyMode(token); + p.fosterParentingEnabled = savedFosterParentingState; +} + +// The "in table text" insertion mode +//------------------------------------------------------------------ +function whitespaceCharacterInTableText(p, token) { + p.pendingCharacterTokens.push(token); +} + +function characterInTableText(p, token) { + p.pendingCharacterTokens.push(token); + p.hasNonWhitespacePendingCharacterToken = true; +} + +function tokenInTableText(p, token) { + let i = 0; + + if (p.hasNonWhitespacePendingCharacterToken) { + for (; i < p.pendingCharacterTokens.length; i++) { + tokenInTable(p, p.pendingCharacterTokens[i]); + } + } else { + for (; i < p.pendingCharacterTokens.length; i++) { + p._insertCharacters(p.pendingCharacterTokens[i]); + } + } + + p.insertionMode = p.originalInsertionMode; + p._processToken(token); +} + +// The "in caption" insertion mode +//------------------------------------------------------------------ +function startTagInCaption(p, token) { + const tn = token.tagName; + + if ( + tn === $.CAPTION || + tn === $.COL || + tn === $.COLGROUP || + tn === $.TBODY || + tn === $.TD || + tn === $.TFOOT || + tn === $.TH || + tn === $.THEAD || + tn === $.TR + ) { + if (p.openElements.hasInTableScope($.CAPTION)) { + p.openElements.generateImpliedEndTags(); + p.openElements.popUntilTagNamePopped($.CAPTION); + p.activeFormattingElements.clearToLastMarker(); + p.insertionMode = IN_TABLE_MODE; + p._processToken(token); + } + } else { + startTagInBody(p, token); + } +} + +function endTagInCaption(p, token) { + const tn = token.tagName; + + if (tn === $.CAPTION || tn === $.TABLE) { + if (p.openElements.hasInTableScope($.CAPTION)) { + p.openElements.generateImpliedEndTags(); + p.openElements.popUntilTagNamePopped($.CAPTION); + p.activeFormattingElements.clearToLastMarker(); + p.insertionMode = IN_TABLE_MODE; + + if (tn === $.TABLE) { + p._processToken(token); + } + } + } else if ( + tn !== $.BODY && + tn !== $.COL && + tn !== $.COLGROUP && + tn !== $.HTML && + tn !== $.TBODY && + tn !== $.TD && + tn !== $.TFOOT && + tn !== $.TH && + tn !== $.THEAD && + tn !== $.TR + ) { + endTagInBody(p, token); + } +} + +// The "in column group" insertion mode +//------------------------------------------------------------------ +function startTagInColumnGroup(p, token) { + const tn = token.tagName; + + if (tn === $.HTML) { + startTagInBody(p, token); + } else if (tn === $.COL) { + p._appendElement(token, NS.HTML); + token.ackSelfClosing = true; + } else if (tn === $.TEMPLATE) { + startTagInHead(p, token); + } else { + tokenInColumnGroup(p, token); + } +} + +function endTagInColumnGroup(p, token) { + const tn = token.tagName; + + if (tn === $.COLGROUP) { + if (p.openElements.currentTagName === $.COLGROUP) { + p.openElements.pop(); + p.insertionMode = IN_TABLE_MODE; + } + } else if (tn === $.TEMPLATE) { + endTagInHead(p, token); + } else if (tn !== $.COL) { + tokenInColumnGroup(p, token); + } +} + +function tokenInColumnGroup(p, token) { + if (p.openElements.currentTagName === $.COLGROUP) { + p.openElements.pop(); + p.insertionMode = IN_TABLE_MODE; + p._processToken(token); + } +} + +// The "in table body" insertion mode +//------------------------------------------------------------------ +function startTagInTableBody(p, token) { + const tn = token.tagName; + + if (tn === $.TR) { + p.openElements.clearBackToTableBodyContext(); + p._insertElement(token, NS.HTML); + p.insertionMode = IN_ROW_MODE; + } else if (tn === $.TH || tn === $.TD) { + p.openElements.clearBackToTableBodyContext(); + p._insertFakeElement($.TR); + p.insertionMode = IN_ROW_MODE; + p._processToken(token); + } else if ( + tn === $.CAPTION || + tn === $.COL || + tn === $.COLGROUP || + tn === $.TBODY || + tn === $.TFOOT || + tn === $.THEAD + ) { + if (p.openElements.hasTableBodyContextInTableScope()) { + p.openElements.clearBackToTableBodyContext(); + p.openElements.pop(); + p.insertionMode = IN_TABLE_MODE; + p._processToken(token); + } + } else { + startTagInTable(p, token); + } +} + +function endTagInTableBody(p, token) { + const tn = token.tagName; + + if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) { + if (p.openElements.hasInTableScope(tn)) { + p.openElements.clearBackToTableBodyContext(); + p.openElements.pop(); + p.insertionMode = IN_TABLE_MODE; + } + } else if (tn === $.TABLE) { + if (p.openElements.hasTableBodyContextInTableScope()) { + p.openElements.clearBackToTableBodyContext(); + p.openElements.pop(); + p.insertionMode = IN_TABLE_MODE; + p._processToken(token); + } + } else if ( + (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP) || + (tn !== $.HTML && tn !== $.TD && tn !== $.TH && tn !== $.TR) + ) { + endTagInTable(p, token); + } +} + +// The "in row" insertion mode +//------------------------------------------------------------------ +function startTagInRow(p, token) { + const tn = token.tagName; + + if (tn === $.TH || tn === $.TD) { + p.openElements.clearBackToTableRowContext(); + p._insertElement(token, NS.HTML); + p.insertionMode = IN_CELL_MODE; + p.activeFormattingElements.insertMarker(); + } else if ( + tn === $.CAPTION || + tn === $.COL || + tn === $.COLGROUP || + tn === $.TBODY || + tn === $.TFOOT || + tn === $.THEAD || + tn === $.TR + ) { + if (p.openElements.hasInTableScope($.TR)) { + p.openElements.clearBackToTableRowContext(); + p.openElements.pop(); + p.insertionMode = IN_TABLE_BODY_MODE; + p._processToken(token); + } + } else { + startTagInTable(p, token); + } +} + +function endTagInRow(p, token) { + const tn = token.tagName; + + if (tn === $.TR) { + if (p.openElements.hasInTableScope($.TR)) { + p.openElements.clearBackToTableRowContext(); + p.openElements.pop(); + p.insertionMode = IN_TABLE_BODY_MODE; + } + } else if (tn === $.TABLE) { + if (p.openElements.hasInTableScope($.TR)) { + p.openElements.clearBackToTableRowContext(); + p.openElements.pop(); + p.insertionMode = IN_TABLE_BODY_MODE; + p._processToken(token); + } + } else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) { + if (p.openElements.hasInTableScope(tn) || p.openElements.hasInTableScope($.TR)) { + p.openElements.clearBackToTableRowContext(); + p.openElements.pop(); + p.insertionMode = IN_TABLE_BODY_MODE; + p._processToken(token); + } + } else if ( + (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP) || + (tn !== $.HTML && tn !== $.TD && tn !== $.TH) + ) { + endTagInTable(p, token); + } +} + +// The "in cell" insertion mode +//------------------------------------------------------------------ +function startTagInCell(p, token) { + const tn = token.tagName; + + if ( + tn === $.CAPTION || + tn === $.COL || + tn === $.COLGROUP || + tn === $.TBODY || + tn === $.TD || + tn === $.TFOOT || + tn === $.TH || + tn === $.THEAD || + tn === $.TR + ) { + if (p.openElements.hasInTableScope($.TD) || p.openElements.hasInTableScope($.TH)) { + p._closeTableCell(); + p._processToken(token); + } + } else { + startTagInBody(p, token); + } +} + +function endTagInCell(p, token) { + const tn = token.tagName; + + if (tn === $.TD || tn === $.TH) { + if (p.openElements.hasInTableScope(tn)) { + p.openElements.generateImpliedEndTags(); + p.openElements.popUntilTagNamePopped(tn); + p.activeFormattingElements.clearToLastMarker(); + p.insertionMode = IN_ROW_MODE; + } + } else if (tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD || tn === $.TR) { + if (p.openElements.hasInTableScope(tn)) { + p._closeTableCell(); + p._processToken(token); + } + } else if (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP && tn !== $.HTML) { + endTagInBody(p, token); + } +} + +// The "in select" insertion mode +//------------------------------------------------------------------ +function startTagInSelect(p, token) { + const tn = token.tagName; + + if (tn === $.HTML) { + startTagInBody(p, token); + } else if (tn === $.OPTION) { + if (p.openElements.currentTagName === $.OPTION) { + p.openElements.pop(); + } + + p._insertElement(token, NS.HTML); + } else if (tn === $.OPTGROUP) { + if (p.openElements.currentTagName === $.OPTION) { + p.openElements.pop(); + } + + if (p.openElements.currentTagName === $.OPTGROUP) { + p.openElements.pop(); + } + + p._insertElement(token, NS.HTML); + } else if (tn === $.INPUT || tn === $.KEYGEN || tn === $.TEXTAREA || tn === $.SELECT) { + if (p.openElements.hasInSelectScope($.SELECT)) { + p.openElements.popUntilTagNamePopped($.SELECT); + p._resetInsertionMode(); + + if (tn !== $.SELECT) { + p._processToken(token); + } + } + } else if (tn === $.SCRIPT || tn === $.TEMPLATE) { + startTagInHead(p, token); + } +} + +function endTagInSelect(p, token) { + const tn = token.tagName; + + if (tn === $.OPTGROUP) { + const prevOpenElement = p.openElements.items[p.openElements.stackTop - 1]; + const prevOpenElementTn = prevOpenElement && p.treeAdapter.getTagName(prevOpenElement); + + if (p.openElements.currentTagName === $.OPTION && prevOpenElementTn === $.OPTGROUP) { + p.openElements.pop(); + } + + if (p.openElements.currentTagName === $.OPTGROUP) { + p.openElements.pop(); + } + } else if (tn === $.OPTION) { + if (p.openElements.currentTagName === $.OPTION) { + p.openElements.pop(); + } + } else if (tn === $.SELECT && p.openElements.hasInSelectScope($.SELECT)) { + p.openElements.popUntilTagNamePopped($.SELECT); + p._resetInsertionMode(); + } else if (tn === $.TEMPLATE) { + endTagInHead(p, token); + } +} + +//12.2.5.4.17 The "in select in table" insertion mode +//------------------------------------------------------------------ +function startTagInSelectInTable(p, token) { + const tn = token.tagName; + + if ( + tn === $.CAPTION || + tn === $.TABLE || + tn === $.TBODY || + tn === $.TFOOT || + tn === $.THEAD || + tn === $.TR || + tn === $.TD || + tn === $.TH + ) { + p.openElements.popUntilTagNamePopped($.SELECT); + p._resetInsertionMode(); + p._processToken(token); + } else { + startTagInSelect(p, token); + } +} + +function endTagInSelectInTable(p, token) { + const tn = token.tagName; + + if ( + tn === $.CAPTION || + tn === $.TABLE || + tn === $.TBODY || + tn === $.TFOOT || + tn === $.THEAD || + tn === $.TR || + tn === $.TD || + tn === $.TH + ) { + if (p.openElements.hasInTableScope(tn)) { + p.openElements.popUntilTagNamePopped($.SELECT); + p._resetInsertionMode(); + p._processToken(token); + } + } else { + endTagInSelect(p, token); + } +} + +// The "in template" insertion mode +//------------------------------------------------------------------ +function startTagInTemplate(p, token) { + const tn = token.tagName; + + if ( + tn === $.BASE || + tn === $.BASEFONT || + tn === $.BGSOUND || + tn === $.LINK || + tn === $.META || + tn === $.NOFRAMES || + tn === $.SCRIPT || + tn === $.STYLE || + tn === $.TEMPLATE || + tn === $.TITLE + ) { + startTagInHead(p, token); + } else { + const newInsertionMode = TEMPLATE_INSERTION_MODE_SWITCH_MAP[tn] || IN_BODY_MODE; + + p._popTmplInsertionMode(); + p._pushTmplInsertionMode(newInsertionMode); + p.insertionMode = newInsertionMode; + p._processToken(token); + } +} + +function endTagInTemplate(p, token) { + if (token.tagName === $.TEMPLATE) { + endTagInHead(p, token); + } +} + +function eofInTemplate(p, token) { + if (p.openElements.tmplCount > 0) { + p.openElements.popUntilTagNamePopped($.TEMPLATE); + p.activeFormattingElements.clearToLastMarker(); + p._popTmplInsertionMode(); + p._resetInsertionMode(); + p._processToken(token); + } else { + p.stopped = true; + } +} + +// The "after body" insertion mode +//------------------------------------------------------------------ +function startTagAfterBody(p, token) { + if (token.tagName === $.HTML) { + startTagInBody(p, token); + } else { + tokenAfterBody(p, token); + } +} + +function endTagAfterBody(p, token) { + if (token.tagName === $.HTML) { + if (!p.fragmentContext) { + p.insertionMode = AFTER_AFTER_BODY_MODE; + } + } else { + tokenAfterBody(p, token); + } +} + +function tokenAfterBody(p, token) { + p.insertionMode = IN_BODY_MODE; + p._processToken(token); +} + +// The "in frameset" insertion mode +//------------------------------------------------------------------ +function startTagInFrameset(p, token) { + const tn = token.tagName; + + if (tn === $.HTML) { + startTagInBody(p, token); + } else if (tn === $.FRAMESET) { + p._insertElement(token, NS.HTML); + } else if (tn === $.FRAME) { + p._appendElement(token, NS.HTML); + token.ackSelfClosing = true; + } else if (tn === $.NOFRAMES) { + startTagInHead(p, token); + } +} + +function endTagInFrameset(p, token) { + if (token.tagName === $.FRAMESET && !p.openElements.isRootHtmlElementCurrent()) { + p.openElements.pop(); + + if (!p.fragmentContext && p.openElements.currentTagName !== $.FRAMESET) { + p.insertionMode = AFTER_FRAMESET_MODE; + } + } +} + +// The "after frameset" insertion mode +//------------------------------------------------------------------ +function startTagAfterFrameset(p, token) { + const tn = token.tagName; + + if (tn === $.HTML) { + startTagInBody(p, token); + } else if (tn === $.NOFRAMES) { + startTagInHead(p, token); + } +} + +function endTagAfterFrameset(p, token) { + if (token.tagName === $.HTML) { + p.insertionMode = AFTER_AFTER_FRAMESET_MODE; + } +} + +// The "after after body" insertion mode +//------------------------------------------------------------------ +function startTagAfterAfterBody(p, token) { + if (token.tagName === $.HTML) { + startTagInBody(p, token); + } else { + tokenAfterAfterBody(p, token); + } +} + +function tokenAfterAfterBody(p, token) { + p.insertionMode = IN_BODY_MODE; + p._processToken(token); +} + +// The "after after frameset" insertion mode +//------------------------------------------------------------------ +function startTagAfterAfterFrameset(p, token) { + const tn = token.tagName; + + if (tn === $.HTML) { + startTagInBody(p, token); + } else if (tn === $.NOFRAMES) { + startTagInHead(p, token); + } +} + +// The rules for parsing tokens in foreign content +//------------------------------------------------------------------ +function nullCharacterInForeignContent(p, token) { + token.chars = unicode.REPLACEMENT_CHARACTER; + p._insertCharacters(token); +} + +function characterInForeignContent(p, token) { + p._insertCharacters(token); + p.framesetOk = false; +} + +function startTagInForeignContent(p, token) { + if (foreignContent.causesExit(token) && !p.fragmentContext) { + while ( + p.treeAdapter.getNamespaceURI(p.openElements.current) !== NS.HTML && + !p._isIntegrationPoint(p.openElements.current) + ) { + p.openElements.pop(); + } + + p._processToken(token); + } else { + const current = p._getAdjustedCurrentElement(); + const currentNs = p.treeAdapter.getNamespaceURI(current); + + if (currentNs === NS.MATHML) { + foreignContent.adjustTokenMathMLAttrs(token); + } else if (currentNs === NS.SVG) { + foreignContent.adjustTokenSVGTagName(token); + foreignContent.adjustTokenSVGAttrs(token); + } + + foreignContent.adjustTokenXMLAttrs(token); + + if (token.selfClosing) { + p._appendElement(token, currentNs); + } else { + p._insertElement(token, currentNs); + } + + token.ackSelfClosing = true; + } +} + +function endTagInForeignContent(p, token) { + for (let i = p.openElements.stackTop; i > 0; i--) { + const element = p.openElements.items[i]; + + if (p.treeAdapter.getNamespaceURI(element) === NS.HTML) { + p._processToken(token); + break; + } + + if (p.treeAdapter.getTagName(element).toLowerCase() === token.tagName) { + p.openElements.popUntilElementPopped(element); + break; + } + } +} diff --git a/school/node_modules/parse5/lib/parser/open-element-stack.js b/school/node_modules/parse5/lib/parser/open-element-stack.js new file mode 100644 index 0000000..c10880a --- /dev/null +++ b/school/node_modules/parse5/lib/parser/open-element-stack.js @@ -0,0 +1,482 @@ +'use strict'; + +const HTML = require('../common/html'); + +//Aliases +const $ = HTML.TAG_NAMES; +const NS = HTML.NAMESPACES; + +//Element utils + +//OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here. +//It's faster than using dictionary. +function isImpliedEndTagRequired(tn) { + switch (tn.length) { + case 1: + return tn === $.P; + + case 2: + return tn === $.RB || tn === $.RP || tn === $.RT || tn === $.DD || tn === $.DT || tn === $.LI; + + case 3: + return tn === $.RTC; + + case 6: + return tn === $.OPTION; + + case 8: + return tn === $.OPTGROUP; + } + + return false; +} + +function isImpliedEndTagRequiredThoroughly(tn) { + switch (tn.length) { + case 1: + return tn === $.P; + + case 2: + return ( + tn === $.RB || + tn === $.RP || + tn === $.RT || + tn === $.DD || + tn === $.DT || + tn === $.LI || + tn === $.TD || + tn === $.TH || + tn === $.TR + ); + + case 3: + return tn === $.RTC; + + case 5: + return tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD; + + case 6: + return tn === $.OPTION; + + case 7: + return tn === $.CAPTION; + + case 8: + return tn === $.OPTGROUP || tn === $.COLGROUP; + } + + return false; +} + +function isScopingElement(tn, ns) { + switch (tn.length) { + case 2: + if (tn === $.TD || tn === $.TH) { + return ns === NS.HTML; + } else if (tn === $.MI || tn === $.MO || tn === $.MN || tn === $.MS) { + return ns === NS.MATHML; + } + + break; + + case 4: + if (tn === $.HTML) { + return ns === NS.HTML; + } else if (tn === $.DESC) { + return ns === NS.SVG; + } + + break; + + case 5: + if (tn === $.TABLE) { + return ns === NS.HTML; + } else if (tn === $.MTEXT) { + return ns === NS.MATHML; + } else if (tn === $.TITLE) { + return ns === NS.SVG; + } + + break; + + case 6: + return (tn === $.APPLET || tn === $.OBJECT) && ns === NS.HTML; + + case 7: + return (tn === $.CAPTION || tn === $.MARQUEE) && ns === NS.HTML; + + case 8: + return tn === $.TEMPLATE && ns === NS.HTML; + + case 13: + return tn === $.FOREIGN_OBJECT && ns === NS.SVG; + + case 14: + return tn === $.ANNOTATION_XML && ns === NS.MATHML; + } + + return false; +} + +//Stack of open elements +class OpenElementStack { + constructor(document, treeAdapter) { + this.stackTop = -1; + this.items = []; + this.current = document; + this.currentTagName = null; + this.currentTmplContent = null; + this.tmplCount = 0; + this.treeAdapter = treeAdapter; + } + + //Index of element + _indexOf(element) { + let idx = -1; + + for (let i = this.stackTop; i >= 0; i--) { + if (this.items[i] === element) { + idx = i; + break; + } + } + return idx; + } + + //Update current element + _isInTemplate() { + return this.currentTagName === $.TEMPLATE && this.treeAdapter.getNamespaceURI(this.current) === NS.HTML; + } + + _updateCurrentElement() { + this.current = this.items[this.stackTop]; + this.currentTagName = this.current && this.treeAdapter.getTagName(this.current); + + this.currentTmplContent = this._isInTemplate() ? this.treeAdapter.getTemplateContent(this.current) : null; + } + + //Mutations + push(element) { + this.items[++this.stackTop] = element; + this._updateCurrentElement(); + + if (this._isInTemplate()) { + this.tmplCount++; + } + } + + pop() { + this.stackTop--; + + if (this.tmplCount > 0 && this._isInTemplate()) { + this.tmplCount--; + } + + this._updateCurrentElement(); + } + + replace(oldElement, newElement) { + const idx = this._indexOf(oldElement); + + this.items[idx] = newElement; + + if (idx === this.stackTop) { + this._updateCurrentElement(); + } + } + + insertAfter(referenceElement, newElement) { + const insertionIdx = this._indexOf(referenceElement) + 1; + + this.items.splice(insertionIdx, 0, newElement); + + if (insertionIdx === ++this.stackTop) { + this._updateCurrentElement(); + } + } + + popUntilTagNamePopped(tagName) { + while (this.stackTop > -1) { + const tn = this.currentTagName; + const ns = this.treeAdapter.getNamespaceURI(this.current); + + this.pop(); + + if (tn === tagName && ns === NS.HTML) { + break; + } + } + } + + popUntilElementPopped(element) { + while (this.stackTop > -1) { + const poppedElement = this.current; + + this.pop(); + + if (poppedElement === element) { + break; + } + } + } + + popUntilNumberedHeaderPopped() { + while (this.stackTop > -1) { + const tn = this.currentTagName; + const ns = this.treeAdapter.getNamespaceURI(this.current); + + this.pop(); + + if ( + tn === $.H1 || + tn === $.H2 || + tn === $.H3 || + tn === $.H4 || + tn === $.H5 || + (tn === $.H6 && ns === NS.HTML) + ) { + break; + } + } + } + + popUntilTableCellPopped() { + while (this.stackTop > -1) { + const tn = this.currentTagName; + const ns = this.treeAdapter.getNamespaceURI(this.current); + + this.pop(); + + if (tn === $.TD || (tn === $.TH && ns === NS.HTML)) { + break; + } + } + } + + popAllUpToHtmlElement() { + //NOTE: here we assume that root <html> element is always first in the open element stack, so + //we perform this fast stack clean up. + this.stackTop = 0; + this._updateCurrentElement(); + } + + clearBackToTableContext() { + while ( + (this.currentTagName !== $.TABLE && this.currentTagName !== $.TEMPLATE && this.currentTagName !== $.HTML) || + this.treeAdapter.getNamespaceURI(this.current) !== NS.HTML + ) { + this.pop(); + } + } + + clearBackToTableBodyContext() { + while ( + (this.currentTagName !== $.TBODY && + this.currentTagName !== $.TFOOT && + this.currentTagName !== $.THEAD && + this.currentTagName !== $.TEMPLATE && + this.currentTagName !== $.HTML) || + this.treeAdapter.getNamespaceURI(this.current) !== NS.HTML + ) { + this.pop(); + } + } + + clearBackToTableRowContext() { + while ( + (this.currentTagName !== $.TR && this.currentTagName !== $.TEMPLATE && this.currentTagName !== $.HTML) || + this.treeAdapter.getNamespaceURI(this.current) !== NS.HTML + ) { + this.pop(); + } + } + + remove(element) { + for (let i = this.stackTop; i >= 0; i--) { + if (this.items[i] === element) { + this.items.splice(i, 1); + this.stackTop--; + this._updateCurrentElement(); + break; + } + } + } + + //Search + tryPeekProperlyNestedBodyElement() { + //Properly nested <body> element (should be second element in stack). + const element = this.items[1]; + + return element && this.treeAdapter.getTagName(element) === $.BODY ? element : null; + } + + contains(element) { + return this._indexOf(element) > -1; + } + + getCommonAncestor(element) { + let elementIdx = this._indexOf(element); + + return --elementIdx >= 0 ? this.items[elementIdx] : null; + } + + isRootHtmlElementCurrent() { + return this.stackTop === 0 && this.currentTagName === $.HTML; + } + + //Element in scope + hasInScope(tagName) { + for (let i = this.stackTop; i >= 0; i--) { + const tn = this.treeAdapter.getTagName(this.items[i]); + const ns = this.treeAdapter.getNamespaceURI(this.items[i]); + + if (tn === tagName && ns === NS.HTML) { + return true; + } + + if (isScopingElement(tn, ns)) { + return false; + } + } + + return true; + } + + hasNumberedHeaderInScope() { + for (let i = this.stackTop; i >= 0; i--) { + const tn = this.treeAdapter.getTagName(this.items[i]); + const ns = this.treeAdapter.getNamespaceURI(this.items[i]); + + if ( + (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) && + ns === NS.HTML + ) { + return true; + } + + if (isScopingElement(tn, ns)) { + return false; + } + } + + return true; + } + + hasInListItemScope(tagName) { + for (let i = this.stackTop; i >= 0; i--) { + const tn = this.treeAdapter.getTagName(this.items[i]); + const ns = this.treeAdapter.getNamespaceURI(this.items[i]); + + if (tn === tagName && ns === NS.HTML) { + return true; + } + + if (((tn === $.UL || tn === $.OL) && ns === NS.HTML) || isScopingElement(tn, ns)) { + return false; + } + } + + return true; + } + + hasInButtonScope(tagName) { + for (let i = this.stackTop; i >= 0; i--) { + const tn = this.treeAdapter.getTagName(this.items[i]); + const ns = this.treeAdapter.getNamespaceURI(this.items[i]); + + if (tn === tagName && ns === NS.HTML) { + return true; + } + + if ((tn === $.BUTTON && ns === NS.HTML) || isScopingElement(tn, ns)) { + return false; + } + } + + return true; + } + + hasInTableScope(tagName) { + for (let i = this.stackTop; i >= 0; i--) { + const tn = this.treeAdapter.getTagName(this.items[i]); + const ns = this.treeAdapter.getNamespaceURI(this.items[i]); + + if (ns !== NS.HTML) { + continue; + } + + if (tn === tagName) { + return true; + } + + if (tn === $.TABLE || tn === $.TEMPLATE || tn === $.HTML) { + return false; + } + } + + return true; + } + + hasTableBodyContextInTableScope() { + for (let i = this.stackTop; i >= 0; i--) { + const tn = this.treeAdapter.getTagName(this.items[i]); + const ns = this.treeAdapter.getNamespaceURI(this.items[i]); + + if (ns !== NS.HTML) { + continue; + } + + if (tn === $.TBODY || tn === $.THEAD || tn === $.TFOOT) { + return true; + } + + if (tn === $.TABLE || tn === $.HTML) { + return false; + } + } + + return true; + } + + hasInSelectScope(tagName) { + for (let i = this.stackTop; i >= 0; i--) { + const tn = this.treeAdapter.getTagName(this.items[i]); + const ns = this.treeAdapter.getNamespaceURI(this.items[i]); + + if (ns !== NS.HTML) { + continue; + } + + if (tn === tagName) { + return true; + } + + if (tn !== $.OPTION && tn !== $.OPTGROUP) { + return false; + } + } + + return true; + } + + //Implied end tags + generateImpliedEndTags() { + while (isImpliedEndTagRequired(this.currentTagName)) { + this.pop(); + } + } + + generateImpliedEndTagsThoroughly() { + while (isImpliedEndTagRequiredThoroughly(this.currentTagName)) { + this.pop(); + } + } + + generateImpliedEndTagsWithExclusion(exclusionTagName) { + while (isImpliedEndTagRequired(this.currentTagName) && this.currentTagName !== exclusionTagName) { + this.pop(); + } + } +} + +module.exports = OpenElementStack; |