diff options
Diffstat (limited to 'tools/eslint/node_modules/parse5/lib/parser/index.js')
-rw-r--r-- | tools/eslint/node_modules/parse5/lib/parser/index.js | 2817 |
1 files changed, 2817 insertions, 0 deletions
diff --git a/tools/eslint/node_modules/parse5/lib/parser/index.js b/tools/eslint/node_modules/parse5/lib/parser/index.js new file mode 100644 index 0000000000..bdf192050d --- /dev/null +++ b/tools/eslint/node_modules/parse5/lib/parser/index.js @@ -0,0 +1,2817 @@ +'use strict'; + +var Tokenizer = require('../tokenizer'), + OpenElementStack = require('./open_element_stack'), + FormattingElementList = require('./formatting_element_list'), + locationInfoMixin = require('../location_info/parser_mixin'), + defaultTreeAdapter = require('../tree_adapters/default'), + doctype = require('../common/doctype'), + foreignContent = require('../common/foreign_content'), + mergeOptions = require('../common/merge_options'), + UNICODE = require('../common/unicode'), + HTML = require('../common/html'); + +//Aliases +var $ = HTML.TAG_NAMES, + NS = HTML.NAMESPACES, + ATTRS = HTML.ATTRS; + +/** + * @typedef {Object} ParserOptions + * + * @property {Boolean} [locationInfo=false] - Enables source code location information for the nodes. + * When enabled, each node (except root node) has the `__location` property. In case the node is not an empty element, + * `__location` will be {@link ElementLocationInfo} object, otherwise it's {@link LocationInfo}. + * If the element was implicitly created by the parser it's `__location` property will be `null`. + * + * @property {TreeAdapter} [treeAdapter=parse5.treeAdapters.default] - Specifies the resulting tree format. + */ +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 = {}; + +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 = {}; + +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 _ = {}; + +_[INITIAL_MODE] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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] = {}; +_[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) + locationInfoMixin.assign(this); +}; + +// API +Parser.prototype.parse = function (html) { + var document = this.treeAdapter.createDocument(); + + this._bootstrap(document, null); + this.tokenizer.write(html, true); + this._runParsingLoop(null, 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, 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 (writeCallback, 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; + } + + 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; +}; + +//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); + + if (token.forceQuirks || doctype.isQuirks(token.name, token.publicId, token.systemId)) + p.treeAdapter.setQuirksMode(p.document); + + p.insertionMode = BEFORE_HTML_MODE; +} + +function tokenInInitialMode(p, token) { + p.treeAdapter.setQuirksMode(p.document); + 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.isQuirksMode(p.document) && 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; + } + } +} |