'use strict'; var Tokenizer = require('../tokenizer'), OpenElementStack = require('./open_element_stack'), FormattingElementList = require('./formatting_element_list'), LocationInfoParserMixin = require('../extensions/location_info/parser_mixin'), defaultTreeAdapter = require('../tree_adapters/default'), mergeOptions = require('../utils/merge_options'), doctype = require('../common/doctype'), foreignContent = require('../common/foreign_content'), UNICODE = require('../common/unicode'), HTML = require('../common/html'); //Aliases var $ = HTML.TAG_NAMES, NS = HTML.NAMESPACES, ATTRS = HTML.ATTRS; var DEFAULT_OPTIONS = { locationInfo: false, treeAdapter: defaultTreeAdapter }; //Misc constants var HIDDEN_INPUT_TYPE = 'hidden'; //Adoption agency loops iteration count var AA_OUTER_LOOP_ITER = 8, AA_INNER_LOOP_ITER = 3; //Insertion modes var INITIAL_MODE = 'INITIAL_MODE', BEFORE_HTML_MODE = 'BEFORE_HTML_MODE', BEFORE_HEAD_MODE = 'BEFORE_HEAD_MODE', IN_HEAD_MODE = 'IN_HEAD_MODE', AFTER_HEAD_MODE = 'AFTER_HEAD_MODE', IN_BODY_MODE = 'IN_BODY_MODE', TEXT_MODE = 'TEXT_MODE', IN_TABLE_MODE = 'IN_TABLE_MODE', IN_TABLE_TEXT_MODE = 'IN_TABLE_TEXT_MODE', IN_CAPTION_MODE = 'IN_CAPTION_MODE', IN_COLUMN_GROUP_MODE = 'IN_COLUMN_GROUP_MODE', IN_TABLE_BODY_MODE = 'IN_TABLE_BODY_MODE', IN_ROW_MODE = 'IN_ROW_MODE', IN_CELL_MODE = 'IN_CELL_MODE', IN_SELECT_MODE = 'IN_SELECT_MODE', IN_SELECT_IN_TABLE_MODE = 'IN_SELECT_IN_TABLE_MODE', IN_TEMPLATE_MODE = 'IN_TEMPLATE_MODE', AFTER_BODY_MODE = 'AFTER_BODY_MODE', IN_FRAMESET_MODE = 'IN_FRAMESET_MODE', AFTER_FRAMESET_MODE = 'AFTER_FRAMESET_MODE', AFTER_AFTER_BODY_MODE = 'AFTER_AFTER_BODY_MODE', AFTER_AFTER_FRAMESET_MODE = 'AFTER_AFTER_FRAMESET_MODE'; //Insertion mode reset map var INSERTION_MODE_RESET_MAP = Object.create(null); INSERTION_MODE_RESET_MAP[$.TR] = IN_ROW_MODE; INSERTION_MODE_RESET_MAP[$.TBODY] = INSERTION_MODE_RESET_MAP[$.THEAD] = INSERTION_MODE_RESET_MAP[$.TFOOT] = IN_TABLE_BODY_MODE; INSERTION_MODE_RESET_MAP[$.CAPTION] = IN_CAPTION_MODE; INSERTION_MODE_RESET_MAP[$.COLGROUP] = IN_COLUMN_GROUP_MODE; INSERTION_MODE_RESET_MAP[$.TABLE] = IN_TABLE_MODE; INSERTION_MODE_RESET_MAP[$.BODY] = IN_BODY_MODE; INSERTION_MODE_RESET_MAP[$.FRAMESET] = IN_FRAMESET_MODE; //Template insertion mode switch map var TEMPLATE_INSERTION_MODE_SWITCH_MAP = Object.create(null); TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.CAPTION] = TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.COLGROUP] = TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.TBODY] = TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.TFOOT] = TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.THEAD] = IN_TABLE_MODE; TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.COL] = IN_COLUMN_GROUP_MODE; TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.TR] = IN_TABLE_BODY_MODE; TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.TD] = TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.TH] = IN_ROW_MODE; //Token handlers map for insertion modes var _ = Object.create(null); _[INITIAL_MODE] = Object.create(null); _[INITIAL_MODE][Tokenizer.CHARACTER_TOKEN] = _[INITIAL_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenInInitialMode; _[INITIAL_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = ignoreToken; _[INITIAL_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[INITIAL_MODE][Tokenizer.DOCTYPE_TOKEN] = doctypeInInitialMode; _[INITIAL_MODE][Tokenizer.START_TAG_TOKEN] = _[INITIAL_MODE][Tokenizer.END_TAG_TOKEN] = _[INITIAL_MODE][Tokenizer.EOF_TOKEN] = tokenInInitialMode; _[BEFORE_HTML_MODE] = Object.create(null); _[BEFORE_HTML_MODE][Tokenizer.CHARACTER_TOKEN] = _[BEFORE_HTML_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenBeforeHtml; _[BEFORE_HTML_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = ignoreToken; _[BEFORE_HTML_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[BEFORE_HTML_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[BEFORE_HTML_MODE][Tokenizer.START_TAG_TOKEN] = startTagBeforeHtml; _[BEFORE_HTML_MODE][Tokenizer.END_TAG_TOKEN] = endTagBeforeHtml; _[BEFORE_HTML_MODE][Tokenizer.EOF_TOKEN] = tokenBeforeHtml; _[BEFORE_HEAD_MODE] = Object.create(null); _[BEFORE_HEAD_MODE][Tokenizer.CHARACTER_TOKEN] = _[BEFORE_HEAD_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenBeforeHead; _[BEFORE_HEAD_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = ignoreToken; _[BEFORE_HEAD_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[BEFORE_HEAD_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[BEFORE_HEAD_MODE][Tokenizer.START_TAG_TOKEN] = startTagBeforeHead; _[BEFORE_HEAD_MODE][Tokenizer.END_TAG_TOKEN] = endTagBeforeHead; _[BEFORE_HEAD_MODE][Tokenizer.EOF_TOKEN] = tokenBeforeHead; _[IN_HEAD_MODE] = Object.create(null); _[IN_HEAD_MODE][Tokenizer.CHARACTER_TOKEN] = _[IN_HEAD_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenInHead; _[IN_HEAD_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[IN_HEAD_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_HEAD_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_HEAD_MODE][Tokenizer.START_TAG_TOKEN] = startTagInHead; _[IN_HEAD_MODE][Tokenizer.END_TAG_TOKEN] = endTagInHead; _[IN_HEAD_MODE][Tokenizer.EOF_TOKEN] = tokenInHead; _[AFTER_HEAD_MODE] = Object.create(null); _[AFTER_HEAD_MODE][Tokenizer.CHARACTER_TOKEN] = _[AFTER_HEAD_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenAfterHead; _[AFTER_HEAD_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[AFTER_HEAD_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[AFTER_HEAD_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[AFTER_HEAD_MODE][Tokenizer.START_TAG_TOKEN] = startTagAfterHead; _[AFTER_HEAD_MODE][Tokenizer.END_TAG_TOKEN] = endTagAfterHead; _[AFTER_HEAD_MODE][Tokenizer.EOF_TOKEN] = tokenAfterHead; _[IN_BODY_MODE] = Object.create(null); _[IN_BODY_MODE][Tokenizer.CHARACTER_TOKEN] = characterInBody; _[IN_BODY_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[IN_BODY_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[IN_BODY_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_BODY_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_BODY_MODE][Tokenizer.START_TAG_TOKEN] = startTagInBody; _[IN_BODY_MODE][Tokenizer.END_TAG_TOKEN] = endTagInBody; _[IN_BODY_MODE][Tokenizer.EOF_TOKEN] = eofInBody; _[TEXT_MODE] = Object.create(null); _[TEXT_MODE][Tokenizer.CHARACTER_TOKEN] = _[TEXT_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = _[TEXT_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[TEXT_MODE][Tokenizer.COMMENT_TOKEN] = _[TEXT_MODE][Tokenizer.DOCTYPE_TOKEN] = _[TEXT_MODE][Tokenizer.START_TAG_TOKEN] = ignoreToken; _[TEXT_MODE][Tokenizer.END_TAG_TOKEN] = endTagInText; _[TEXT_MODE][Tokenizer.EOF_TOKEN] = eofInText; _[IN_TABLE_MODE] = Object.create(null); _[IN_TABLE_MODE][Tokenizer.CHARACTER_TOKEN] = _[IN_TABLE_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = _[IN_TABLE_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = characterInTable; _[IN_TABLE_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_TABLE_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_TABLE_MODE][Tokenizer.START_TAG_TOKEN] = startTagInTable; _[IN_TABLE_MODE][Tokenizer.END_TAG_TOKEN] = endTagInTable; _[IN_TABLE_MODE][Tokenizer.EOF_TOKEN] = eofInBody; _[IN_TABLE_TEXT_MODE] = Object.create(null); _[IN_TABLE_TEXT_MODE][Tokenizer.CHARACTER_TOKEN] = characterInTableText; _[IN_TABLE_TEXT_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[IN_TABLE_TEXT_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInTableText; _[IN_TABLE_TEXT_MODE][Tokenizer.COMMENT_TOKEN] = _[IN_TABLE_TEXT_MODE][Tokenizer.DOCTYPE_TOKEN] = _[IN_TABLE_TEXT_MODE][Tokenizer.START_TAG_TOKEN] = _[IN_TABLE_TEXT_MODE][Tokenizer.END_TAG_TOKEN] = _[IN_TABLE_TEXT_MODE][Tokenizer.EOF_TOKEN] = tokenInTableText; _[IN_CAPTION_MODE] = Object.create(null); _[IN_CAPTION_MODE][Tokenizer.CHARACTER_TOKEN] = characterInBody; _[IN_CAPTION_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[IN_CAPTION_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[IN_CAPTION_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_CAPTION_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_CAPTION_MODE][Tokenizer.START_TAG_TOKEN] = startTagInCaption; _[IN_CAPTION_MODE][Tokenizer.END_TAG_TOKEN] = endTagInCaption; _[IN_CAPTION_MODE][Tokenizer.EOF_TOKEN] = eofInBody; _[IN_COLUMN_GROUP_MODE] = Object.create(null); _[IN_COLUMN_GROUP_MODE][Tokenizer.CHARACTER_TOKEN] = _[IN_COLUMN_GROUP_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenInColumnGroup; _[IN_COLUMN_GROUP_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[IN_COLUMN_GROUP_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_COLUMN_GROUP_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_COLUMN_GROUP_MODE][Tokenizer.START_TAG_TOKEN] = startTagInColumnGroup; _[IN_COLUMN_GROUP_MODE][Tokenizer.END_TAG_TOKEN] = endTagInColumnGroup; _[IN_COLUMN_GROUP_MODE][Tokenizer.EOF_TOKEN] = eofInBody; _[IN_TABLE_BODY_MODE] = Object.create(null); _[IN_TABLE_BODY_MODE][Tokenizer.CHARACTER_TOKEN] = _[IN_TABLE_BODY_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = _[IN_TABLE_BODY_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = characterInTable; _[IN_TABLE_BODY_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_TABLE_BODY_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_TABLE_BODY_MODE][Tokenizer.START_TAG_TOKEN] = startTagInTableBody; _[IN_TABLE_BODY_MODE][Tokenizer.END_TAG_TOKEN] = endTagInTableBody; _[IN_TABLE_BODY_MODE][Tokenizer.EOF_TOKEN] = eofInBody; _[IN_ROW_MODE] = Object.create(null); _[IN_ROW_MODE][Tokenizer.CHARACTER_TOKEN] = _[IN_ROW_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = _[IN_ROW_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = characterInTable; _[IN_ROW_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_ROW_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_ROW_MODE][Tokenizer.START_TAG_TOKEN] = startTagInRow; _[IN_ROW_MODE][Tokenizer.END_TAG_TOKEN] = endTagInRow; _[IN_ROW_MODE][Tokenizer.EOF_TOKEN] = eofInBody; _[IN_CELL_MODE] = Object.create(null); _[IN_CELL_MODE][Tokenizer.CHARACTER_TOKEN] = characterInBody; _[IN_CELL_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[IN_CELL_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[IN_CELL_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_CELL_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_CELL_MODE][Tokenizer.START_TAG_TOKEN] = startTagInCell; _[IN_CELL_MODE][Tokenizer.END_TAG_TOKEN] = endTagInCell; _[IN_CELL_MODE][Tokenizer.EOF_TOKEN] = eofInBody; _[IN_SELECT_MODE] = Object.create(null); _[IN_SELECT_MODE][Tokenizer.CHARACTER_TOKEN] = insertCharacters; _[IN_SELECT_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[IN_SELECT_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[IN_SELECT_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_SELECT_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_SELECT_MODE][Tokenizer.START_TAG_TOKEN] = startTagInSelect; _[IN_SELECT_MODE][Tokenizer.END_TAG_TOKEN] = endTagInSelect; _[IN_SELECT_MODE][Tokenizer.EOF_TOKEN] = eofInBody; _[IN_SELECT_IN_TABLE_MODE] = Object.create(null); _[IN_SELECT_IN_TABLE_MODE][Tokenizer.CHARACTER_TOKEN] = insertCharacters; _[IN_SELECT_IN_TABLE_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[IN_SELECT_IN_TABLE_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[IN_SELECT_IN_TABLE_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_SELECT_IN_TABLE_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_SELECT_IN_TABLE_MODE][Tokenizer.START_TAG_TOKEN] = startTagInSelectInTable; _[IN_SELECT_IN_TABLE_MODE][Tokenizer.END_TAG_TOKEN] = endTagInSelectInTable; _[IN_SELECT_IN_TABLE_MODE][Tokenizer.EOF_TOKEN] = eofInBody; _[IN_TEMPLATE_MODE] = Object.create(null); _[IN_TEMPLATE_MODE][Tokenizer.CHARACTER_TOKEN] = characterInBody; _[IN_TEMPLATE_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[IN_TEMPLATE_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[IN_TEMPLATE_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_TEMPLATE_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_TEMPLATE_MODE][Tokenizer.START_TAG_TOKEN] = startTagInTemplate; _[IN_TEMPLATE_MODE][Tokenizer.END_TAG_TOKEN] = endTagInTemplate; _[IN_TEMPLATE_MODE][Tokenizer.EOF_TOKEN] = eofInTemplate; _[AFTER_BODY_MODE] = Object.create(null); _[AFTER_BODY_MODE][Tokenizer.CHARACTER_TOKEN] = _[AFTER_BODY_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenAfterBody; _[AFTER_BODY_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[AFTER_BODY_MODE][Tokenizer.COMMENT_TOKEN] = appendCommentToRootHtmlElement; _[AFTER_BODY_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[AFTER_BODY_MODE][Tokenizer.START_TAG_TOKEN] = startTagAfterBody; _[AFTER_BODY_MODE][Tokenizer.END_TAG_TOKEN] = endTagAfterBody; _[AFTER_BODY_MODE][Tokenizer.EOF_TOKEN] = stopParsing; _[IN_FRAMESET_MODE] = Object.create(null); _[IN_FRAMESET_MODE][Tokenizer.CHARACTER_TOKEN] = _[IN_FRAMESET_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[IN_FRAMESET_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[IN_FRAMESET_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[IN_FRAMESET_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[IN_FRAMESET_MODE][Tokenizer.START_TAG_TOKEN] = startTagInFrameset; _[IN_FRAMESET_MODE][Tokenizer.END_TAG_TOKEN] = endTagInFrameset; _[IN_FRAMESET_MODE][Tokenizer.EOF_TOKEN] = stopParsing; _[AFTER_FRAMESET_MODE] = Object.create(null); _[AFTER_FRAMESET_MODE][Tokenizer.CHARACTER_TOKEN] = _[AFTER_FRAMESET_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[AFTER_FRAMESET_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[AFTER_FRAMESET_MODE][Tokenizer.COMMENT_TOKEN] = appendComment; _[AFTER_FRAMESET_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[AFTER_FRAMESET_MODE][Tokenizer.START_TAG_TOKEN] = startTagAfterFrameset; _[AFTER_FRAMESET_MODE][Tokenizer.END_TAG_TOKEN] = endTagAfterFrameset; _[AFTER_FRAMESET_MODE][Tokenizer.EOF_TOKEN] = stopParsing; _[AFTER_AFTER_BODY_MODE] = Object.create(null); _[AFTER_AFTER_BODY_MODE][Tokenizer.CHARACTER_TOKEN] = tokenAfterAfterBody; _[AFTER_AFTER_BODY_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenAfterAfterBody; _[AFTER_AFTER_BODY_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[AFTER_AFTER_BODY_MODE][Tokenizer.COMMENT_TOKEN] = appendCommentToDocument; _[AFTER_AFTER_BODY_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[AFTER_AFTER_BODY_MODE][Tokenizer.START_TAG_TOKEN] = startTagAfterAfterBody; _[AFTER_AFTER_BODY_MODE][Tokenizer.END_TAG_TOKEN] = tokenAfterAfterBody; _[AFTER_AFTER_BODY_MODE][Tokenizer.EOF_TOKEN] = stopParsing; _[AFTER_AFTER_FRAMESET_MODE] = Object.create(null); _[AFTER_AFTER_FRAMESET_MODE][Tokenizer.CHARACTER_TOKEN] = _[AFTER_AFTER_FRAMESET_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[AFTER_AFTER_FRAMESET_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[AFTER_AFTER_FRAMESET_MODE][Tokenizer.COMMENT_TOKEN] = appendCommentToDocument; _[AFTER_AFTER_FRAMESET_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[AFTER_AFTER_FRAMESET_MODE][Tokenizer.START_TAG_TOKEN] = startTagAfterAfterFrameset; _[AFTER_AFTER_FRAMESET_MODE][Tokenizer.END_TAG_TOKEN] = ignoreToken; _[AFTER_AFTER_FRAMESET_MODE][Tokenizer.EOF_TOKEN] = stopParsing; //Parser var Parser = module.exports = function (options) { this.options = mergeOptions(DEFAULT_OPTIONS, options); this.treeAdapter = this.options.treeAdapter; this.pendingScript = null; if (this.options.locationInfo) new LocationInfoParserMixin(this); }; // API Parser.prototype.parse = function (html) { var document = this.treeAdapter.createDocument(); this._bootstrap(document, null); this.tokenizer.write(html, true); this._runParsingLoop(null); return document; }; Parser.prototype.parseFragment = function (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`. var 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); var rootElement = this.treeAdapter.getFirstChild(documentMock), fragment = this.treeAdapter.createDocumentFragment(); this._adoptNodes(rootElement, fragment); return fragment; }; //Bootstrap parser Parser.prototype._bootstrap = function (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; }; //Parsing loop Parser.prototype._runParsingLoop = function (scriptHandler) { while (!this.stopped) { this._setupTokenizerCDATAMode(); var 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; } }; Parser.prototype.runParsingLoopForCurrentChunk = function (writeCallback, scriptHandler) { this._runParsingLoop(scriptHandler); if (scriptHandler && this.pendingScript) { var script = this.pendingScript; this.pendingScript = null; scriptHandler(script); return; } if (writeCallback) writeCallback(); }; //Text parsing Parser.prototype._setupTokenizerCDATAMode = function () { var current = this._getAdjustedCurrentElement(); this.tokenizer.allowCDATA = current && current !== this.document && this.treeAdapter.getNamespaceURI(current) !== NS.HTML && !this._isIntegrationPoint(current); }; Parser.prototype._switchToTextParsing = function (currentToken, nextTokenizerState) { this._insertElement(currentToken, NS.HTML); this.tokenizer.state = nextTokenizerState; this.originalInsertionMode = this.insertionMode; this.insertionMode = TEXT_MODE; }; Parser.prototype.switchToPlaintextParsing = function () { this.insertionMode = TEXT_MODE; this.originalInsertionMode = IN_BODY_MODE; this.tokenizer.state = Tokenizer.MODE.PLAINTEXT; }; //Fragment parsing Parser.prototype._getAdjustedCurrentElement = function () { return this.openElements.stackTop === 0 && this.fragmentContext ? this.fragmentContext : this.openElements.current; }; Parser.prototype._findFormInFragmentContext = function () { var node = this.fragmentContext; do { if (this.treeAdapter.getTagName(node) === $.FORM) { this.formElement = node; break; } node = this.treeAdapter.getParentNode(node); } while (node); }; Parser.prototype._initTokenizerForFragmentParsing = function () { if (this.treeAdapter.getNamespaceURI(this.fragmentContext) === NS.HTML) { var 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 Parser.prototype._setDocumentType = function (token) { this.treeAdapter.setDocumentType(this.document, token.name, token.publicId, token.systemId); }; Parser.prototype._attachElementToTree = function (element) { if (this._shouldFosterParentOnInsertion()) this._fosterParentElement(element); else { var parent = this.openElements.currentTmplContent || this.openElements.current; this.treeAdapter.appendChild(parent, element); } }; Parser.prototype._appendElement = function (token, namespaceURI) { var element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs); this._attachElementToTree(element); }; Parser.prototype._insertElement = function (token, namespaceURI) { var element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs); this._attachElementToTree(element); this.openElements.push(element); }; Parser.prototype._insertFakeElement = function (tagName) { var element = this.treeAdapter.createElement(tagName, NS.HTML, []); this._attachElementToTree(element); this.openElements.push(element); }; Parser.prototype._insertTemplate = function (token) { var tmpl = this.treeAdapter.createElement(token.tagName, NS.HTML, token.attrs), content = this.treeAdapter.createDocumentFragment(); this.treeAdapter.setTemplateContent(tmpl, content); this._attachElementToTree(tmpl); this.openElements.push(tmpl); }; Parser.prototype._insertFakeRootElement = function () { var element = this.treeAdapter.createElement($.HTML, NS.HTML, []); this.treeAdapter.appendChild(this.openElements.current, element); this.openElements.push(element); }; Parser.prototype._appendCommentNode = function (token, parent) { var commentNode = this.treeAdapter.createCommentNode(token.data); this.treeAdapter.appendChild(parent, commentNode); }; Parser.prototype._insertCharacters = function (token) { if (this._shouldFosterParentOnInsertion()) this._fosterParentText(token.chars); else { var parent = this.openElements.currentTmplContent || this.openElements.current; this.treeAdapter.insertText(parent, token.chars); } }; Parser.prototype._adoptNodes = function (donor, recipient) { while (true) { var child = this.treeAdapter.getFirstChild(donor); if (!child) break; this.treeAdapter.detachNode(child); this.treeAdapter.appendChild(recipient, child); } }; //Token processing Parser.prototype._shouldProcessTokenInForeignContent = function (token) { var current = this._getAdjustedCurrentElement(); if (!current || current === this.document) return false; var 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; var isCharacterToken = token.type === Tokenizer.CHARACTER_TOKEN || token.type === Tokenizer.NULL_CHARACTER_TOKEN || token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN, 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; }; Parser.prototype._processToken = function (token) { _[this.insertionMode][token.type](this, token); }; Parser.prototype._processTokenInBodyMode = function (token) { _[IN_BODY_MODE][token.type](this, token); }; Parser.prototype._processTokenInForeignContent = function (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); }; Parser.prototype._processInputToken = function (token) { if (this._shouldProcessTokenInForeignContent(token)) this._processTokenInForeignContent(token); else this._processToken(token); }; //Integration points Parser.prototype._isIntegrationPoint = function (element, foreignNS) { var tn = this.treeAdapter.getTagName(element), ns = this.treeAdapter.getNamespaceURI(element), attrs = this.treeAdapter.getAttrList(element); return foreignContent.isIntegrationPoint(tn, ns, attrs, foreignNS); }; //Active formatting elements reconstruction Parser.prototype._reconstructActiveFormattingElements = function () { var listLength = this.activeFormattingElements.length; if (listLength) { var unopenIdx = listLength, 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 (var 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 Parser.prototype._closeTableCell = function () { this.openElements.generateImpliedEndTags(); this.openElements.popUntilTableCellPopped(); this.activeFormattingElements.clearToLastMarker(); this.insertionMode = IN_ROW_MODE; }; Parser.prototype._closePElement = function () { this.openElements.generateImpliedEndTagsWithExclusion($.P); this.openElements.popUntilTagNamePopped($.P); }; //Insertion modes Parser.prototype._resetInsertionMode = function () { for (var i = this.openElements.stackTop, last = false; i >= 0; i--) { var element = this.openElements.items[i]; if (i === 0) { last = true; if (this.fragmentContext) element = this.fragmentContext; } var tn = this.treeAdapter.getTagName(element), 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; } } }; Parser.prototype._resetInsertionModeForSelect = function (selectIdx) { if (selectIdx > 0) { for (var i = selectIdx - 1; i > 0; i--) { var ancestor = this.openElements.items[i], 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; }; Parser.prototype._pushTmplInsertionMode = function (mode) { this.tmplInsertionModeStack.push(mode); this.tmplInsertionModeStackTop++; this.currentTmplInsertionMode = mode; }; Parser.prototype._popTmplInsertionMode = function () { this.tmplInsertionModeStack.pop(); this.tmplInsertionModeStackTop--; this.currentTmplInsertionMode = this.tmplInsertionModeStack[this.tmplInsertionModeStackTop]; }; //Foster parenting Parser.prototype._isElementCausesFosterParenting = function (element) { var tn = this.treeAdapter.getTagName(element); return tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD || tn === $.TR; }; Parser.prototype._shouldFosterParentOnInsertion = function () { return this.fosterParentingEnabled && this._isElementCausesFosterParenting(this.openElements.current); }; Parser.prototype._findFosterParentingLocation = function () { var location = { parent: null, beforeElement: null }; for (var i = this.openElements.stackTop; i >= 0; i--) { var openElement = this.openElements.items[i], tn = this.treeAdapter.getTagName(openElement), 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; }; Parser.prototype._fosterParentElement = function (element) { var location = this._findFosterParentingLocation(); if (location.beforeElement) this.treeAdapter.insertBefore(location.parent, element, location.beforeElement); else this.treeAdapter.appendChild(location.parent, element); }; Parser.prototype._fosterParentText = function (chars) { var location = this._findFosterParentingLocation(); if (location.beforeElement) this.treeAdapter.insertTextBefore(location.parent, chars, location.beforeElement); else this.treeAdapter.insertText(location.parent, chars); }; //Special elements Parser.prototype._isSpecialElement = function (element) { var tn = this.treeAdapter.getTagName(element), ns = this.treeAdapter.getNamespaceURI(element); return HTML.SPECIAL_ELEMENTS[ns][tn]; }; //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) { var 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) { var furthestBlock = null; for (var i = p.openElements.stackTop; i >= 0; i--) { var 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) { var lastElement = furthestBlock, nextElement = p.openElements.getCommonAncestor(furthestBlock); for (var 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); var elementEntry = p.activeFormattingElements.getElementEntry(element), counterOverflow = elementEntry && i >= AA_INNER_LOOP_ITER, 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) { var ns = p.treeAdapter.getNamespaceURI(elementEntry.element), 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 { var tn = p.treeAdapter.getTagName(commonAncestor), 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) { var ns = p.treeAdapter.getNamespaceURI(formattingElementEntry.element), token = formattingElementEntry.token, 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) { var formattingElementEntry; for (var i = 0; i < AA_OUTER_LOOP_ITER; i++) { formattingElementEntry = aaObtainFormattingElementEntry(p, token, formattingElementEntry); if (!formattingElementEntry) break; var furthestBlock = aaObtainFurthestBlock(p, formattingElementEntry); if (!furthestBlock) break; p.activeFormattingElements.bookmark = formattingElementEntry; var lastElement = aaInnerLoop(p, furthestBlock, formattingElementEntry.element), 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 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; } //12.2.5.4.1 The "initial" insertion mode //------------------------------------------------------------------ function doctypeInInitialMode(p, token) { p._setDocumentType(token); var mode = token.forceQuirks ? HTML.DOCUMENT_MODE.QUIRKS : doctype.getDocumentMode(token.name, token.publicId, token.systemId); p.treeAdapter.setDocumentMode(p.document, mode); p.insertionMode = BEFORE_HTML_MODE; } function tokenInInitialMode(p, token) { p.treeAdapter.setDocumentMode(p.document, HTML.DOCUMENT_MODE.QUIRKS); p.insertionMode = BEFORE_HTML_MODE; p._processToken(token); } //12.2.5.4.2 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) { var 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); } //12.2.5.4.3 The "before head" insertion mode //------------------------------------------------------------------ function startTagBeforeHead(p, token) { var 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) { var tn = token.tagName; if (tn === $.HEAD || tn === $.BODY || tn === $.HTML || tn === $.BR) tokenBeforeHead(p, token); } function tokenBeforeHead(p, token) { p._insertFakeElement($.HEAD); p.headElement = p.openElements.current; p.insertionMode = IN_HEAD_MODE; p._processToken(token); } //12.2.5.4.4 The "in head" insertion mode //------------------------------------------------------------------ function startTagInHead(p, token) { var 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); else if (tn === $.TITLE) p._switchToTextParsing(token, Tokenizer.MODE.RCDATA); //NOTE: here we assume that we always act as an interactive user agent with enabled scripting, so we parse //<noscript> as a rawtext. else if (tn === $.NOSCRIPT || 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) tokenInHead(p, token); } function endTagInHead(p, token) { var 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 && p.openElements.tmplCount > 0) { p.openElements.generateImpliedEndTags(); p.openElements.popUntilTagNamePopped($.TEMPLATE); p.activeFormattingElements.clearToLastMarker(); p._popTmplInsertionMode(); p._resetInsertionMode(); } } function tokenInHead(p, token) { p.openElements.pop(); p.insertionMode = AFTER_HEAD_MODE; p._processToken(token); } //12.2.5.4.6 The "after head" insertion mode //------------------------------------------------------------------ function startTagAfterHead(p, token) { var 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.openElements.push(p.headElement); startTagInHead(p, token); p.openElements.remove(p.headElement); } else if (tn !== $.HEAD) tokenAfterHead(p, token); } function endTagAfterHead(p, token) { var tn = token.tagName; if (tn === $.BODY || tn === $.HTML || tn === $.BR) tokenAfterHead(p, token); else if (tn === $.TEMPLATE) endTagInHead(p, token); } function tokenAfterHead(p, token) { p._insertFakeElement($.BODY); p.insertionMode = IN_BODY_MODE; p._processToken(token); } //12.2.5.4.7 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) { var bodyElement = p.openElements.tryPeekProperlyNestedBodyElement(); if (bodyElement && p.openElements.tmplCount === 0) { p.framesetOk = false; p.treeAdapter.adoptAttributes(bodyElement, token.attrs); } } function framesetStartTagInBody(p, token) { var 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(); var 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) { var 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; var tn = token.tagName; for (var i = p.openElements.stackTop; i >= 0; i--) { var element = p.openElements.items[i], elementTn = p.treeAdapter.getTagName(element), 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) { var 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; } function inputStartTagInBody(p, token) { p._reconstructActiveFormattingElements(); p._appendElement(token, NS.HTML); var inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE); if (!inputType || inputType.toLowerCase() !== HIDDEN_INPUT_TYPE) p.framesetOk = false; } function paramStartTagInBody(p, token) { p._appendElement(token, NS.HTML); } function hrStartTagInBody(p, token) { if (p.openElements.hasInButtonScope($.P)) p._closePElement(); if (p.openElements.currentTagName === $.MENUITEM) p.openElements.pop(); p._appendElement(token, NS.HTML); p.framesetOk = false; } 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 menuitemStartTagInBody(p, token) { if (p.openElements.currentTagName === $.MENUITEM) p.openElements.pop(); // TODO needs clarification, see https://github.com/whatwg/html/pull/907/files#r73505877 p._reconstructActiveFormattingElements(); p._insertElement(token, NS.HTML); } function menuStartTagInBody(p, token) { if (p.openElements.hasInButtonScope($.P)) p._closePElement(); if (p.openElements.currentTagName === $.MENUITEM) p.openElements.pop(); 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); } 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); } 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) { var 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) 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) 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 === $.MENUITEM) menuitemStartTagInBody(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) noembedStartTagInBody(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) { var tn = token.tagName; if (p.openElements.hasInScope(tn)) { p.openElements.generateImpliedEndTags(); p.openElements.popUntilTagNamePopped(tn); } } function formEndTagInBody(p) { var inTemplate = p.openElements.tmplCount > 0, 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) { var 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) { var 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) { var tn = token.tagName; for (var i = p.openElements.stackTop; i > 0; i--) { var 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) { var 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) 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) 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) 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; } //12.2.5.4.8 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.openElements.pop(); p.insertionMode = p.originalInsertionMode; p._processToken(token); } //12.2.5.4.9 The "in table" insertion mode //------------------------------------------------------------------ function characterInTable(p, token) { var 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) { var inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE); if (inputType && inputType.toLowerCase() === HIDDEN_INPUT_TYPE) p._appendElement(token, NS.HTML); else tokenInTable(p, token); } 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) { var 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) { var 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) { var savedFosterParentingState = p.fosterParentingEnabled; p.fosterParentingEnabled = true; p._processTokenInBodyMode(token); p.fosterParentingEnabled = savedFosterParentingState; } //12.2.5.4.10 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) { var 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); } //12.2.5.4.11 The "in caption" insertion mode //------------------------------------------------------------------ function startTagInCaption(p, token) { var 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) { var 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); } //12.2.5.4.12 The "in column group" insertion mode //------------------------------------------------------------------ function startTagInColumnGroup(p, token) { var tn = token.tagName; if (tn === $.HTML) startTagInBody(p, token); else if (tn === $.COL) p._appendElement(token, NS.HTML); else if (tn === $.TEMPLATE) startTagInHead(p, token); else tokenInColumnGroup(p, token); } function endTagInColumnGroup(p, token) { var 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); } } //12.2.5.4.13 The "in table body" insertion mode //------------------------------------------------------------------ function startTagInTableBody(p, token) { var 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) { var 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); } //12.2.5.4.14 The "in row" insertion mode //------------------------------------------------------------------ function startTagInRow(p, token) { var 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) { var 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); } //12.2.5.4.15 The "in cell" insertion mode //------------------------------------------------------------------ function startTagInCell(p, token) { var 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) { var 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); } //12.2.5.4.16 The "in select" insertion mode //------------------------------------------------------------------ function startTagInSelect(p, token) { var 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) { var tn = token.tagName; if (tn === $.OPTGROUP) { var prevOpenElement = p.openElements.items[p.openElements.stackTop - 1], 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) { var 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) { var 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); } //12.2.5.4.18 The "in template" insertion mode //------------------------------------------------------------------ function startTagInTemplate(p, token) { var 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 { var 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; } //12.2.5.4.19 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); } //12.2.5.4.20 The "in frameset" insertion mode //------------------------------------------------------------------ function startTagInFrameset(p, token) { var 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); 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; } } //12.2.5.4.21 The "after frameset" insertion mode //------------------------------------------------------------------ function startTagAfterFrameset(p, token) { var 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; } //12.2.5.4.22 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); } //12.2.5.4.23 The "after after frameset" insertion mode //------------------------------------------------------------------ function startTagAfterAfterFrameset(p, token) { var tn = token.tagName; if (tn === $.HTML) startTagInBody(p, token); else if (tn === $.NOFRAMES) startTagInHead(p, token); } //12.2.5.5 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 { var current = p._getAdjustedCurrentElement(), 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); } } function endTagInForeignContent(p, token) { for (var i = p.openElements.stackTop; i > 0; i--) { var 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; } } }