'use strict';

var Tokenizer = require('../tokenization/tokenizer'),

OpenElementStack = require('./open_element_stack'),
FormattingElementList = require('./formatting_element_list'),
LocationInfoMixin = require('./location_info_mixin'),
DefaultTreeAdapter = require('../tree_adapters/default'),
Doctype = require('../common/doctype'),
ForeignContent = require('../common/foreign_content'),
Utils = require('../common/utils'),
UNICODE = require('../common/unicode'),
HTML = require('../common/html');

//Aliases var $ = HTML.TAG_NAMES,

NS = HTML.NAMESPACES,
ATTRS = HTML.ATTRS;

//Default options var DEFAULT_OPTIONS = {

decodeHtmlEntities: true,
locationInfo: false

};

//Misc constants var SEARCHABLE_INDEX_DEFAULT_PROMPT = 'This is a searchable index. Enter search keywords: ',

SEARCHABLE_INDEX_INPUT_NAME = 'isindex',
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 = IN_ROW_MODE; INSERTION_MODE_RESET_MAP = INSERTION_MODE_RESET_MAP = INSERTION_MODE_RESET_MAP = IN_TABLE_BODY_MODE; INSERTION_MODE_RESET_MAP = IN_CAPTION_MODE; INSERTION_MODE_RESET_MAP = IN_COLUMN_GROUP_MODE; INSERTION_MODE_RESET_MAP = IN_TABLE_MODE; INSERTION_MODE_RESET_MAP = IN_BODY_MODE; INSERTION_MODE_RESET_MAP = IN_FRAMESET_MODE;

//Template insertion mode switch map var TEMPLATE_INSERTION_MODE_SWITCH_MAP = {};

TEMPLATE_INSERTION_MODE_SWITCH_MAP = TEMPLATE_INSERTION_MODE_SWITCH_MAP = TEMPLATE_INSERTION_MODE_SWITCH_MAP = TEMPLATE_INSERTION_MODE_SWITCH_MAP = TEMPLATE_INSERTION_MODE_SWITCH_MAP = IN_TABLE_MODE; TEMPLATE_INSERTION_MODE_SWITCH_MAP = IN_COLUMN_GROUP_MODE; TEMPLATE_INSERTION_MODE_SWITCH_MAP = IN_TABLE_BODY_MODE; TEMPLATE_INSERTION_MODE_SWITCH_MAP = TEMPLATE_INSERTION_MODE_SWITCH_MAP = IN_ROW_MODE;

//Token handlers map for insertion modes var _ = {};

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = tokenInInitialMode; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = doctypeInInitialMode; _[Tokenizer.START_TAG_TOKEN] = _[Tokenizer.END_TAG_TOKEN] = _[Tokenizer.EOF_TOKEN] = tokenInInitialMode;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = tokenBeforeHtml; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagBeforeHtml; _[Tokenizer.END_TAG_TOKEN] = endTagBeforeHtml; _[Tokenizer.EOF_TOKEN] = tokenBeforeHtml;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = tokenBeforeHead; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagBeforeHead; _[Tokenizer.END_TAG_TOKEN] = endTagBeforeHead; _[Tokenizer.EOF_TOKEN] = tokenBeforeHead;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = tokenInHead; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInHead; _[Tokenizer.END_TAG_TOKEN] = endTagInHead; _[Tokenizer.EOF_TOKEN] = tokenInHead;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = tokenAfterHead; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagAfterHead; _[Tokenizer.END_TAG_TOKEN] = endTagAfterHead; _[Tokenizer.EOF_TOKEN] = tokenAfterHead;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = characterInBody; _[Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInBody; _[Tokenizer.END_TAG_TOKEN] = endTagInBody; _[Tokenizer.EOF_TOKEN] = eofInBody;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[Tokenizer.COMMENT_TOKEN] = _[Tokenizer.DOCTYPE_TOKEN] = _[Tokenizer.START_TAG_TOKEN] = ignoreToken; _[Tokenizer.END_TAG_TOKEN] = endTagInText; _[Tokenizer.EOF_TOKEN] = eofInText;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = characterInTable; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInTable; _[Tokenizer.END_TAG_TOKEN] = endTagInTable; _[Tokenizer.EOF_TOKEN] = eofInBody;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = characterInTableText; _[Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInTableText; _[Tokenizer.COMMENT_TOKEN] = _[Tokenizer.DOCTYPE_TOKEN] = _[Tokenizer.START_TAG_TOKEN] = _[Tokenizer.END_TAG_TOKEN] = _[Tokenizer.EOF_TOKEN] = tokenInTableText;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = characterInBody; _[Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInCaption; _[Tokenizer.END_TAG_TOKEN] = endTagInCaption; _[Tokenizer.EOF_TOKEN] = eofInBody;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = tokenInColumnGroup; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInColumnGroup; _[Tokenizer.END_TAG_TOKEN] = endTagInColumnGroup; _[Tokenizer.EOF_TOKEN] = eofInBody;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = characterInTable; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInTableBody; _[Tokenizer.END_TAG_TOKEN] = endTagInTableBody; _[Tokenizer.EOF_TOKEN] = eofInBody;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = characterInTable; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInRow; _[Tokenizer.END_TAG_TOKEN] = endTagInRow; _[Tokenizer.EOF_TOKEN] = eofInBody;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = characterInBody; _[Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInCell; _[Tokenizer.END_TAG_TOKEN] = endTagInCell; _[Tokenizer.EOF_TOKEN] = eofInBody;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = insertCharacters; _[Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInSelect; _[Tokenizer.END_TAG_TOKEN] = endTagInSelect; _[Tokenizer.EOF_TOKEN] = eofInBody;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = insertCharacters; _[Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInSelectInTable; _[Tokenizer.END_TAG_TOKEN] = endTagInSelectInTable; _[Tokenizer.EOF_TOKEN] = eofInBody;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = characterInBody; _[Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInTemplate; _[Tokenizer.END_TAG_TOKEN] = endTagInTemplate; _[Tokenizer.EOF_TOKEN] = eofInTemplate;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = tokenAfterBody; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[Tokenizer.COMMENT_TOKEN] = appendCommentToRootHtmlElement; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagAfterBody; _[Tokenizer.END_TAG_TOKEN] = endTagAfterBody; _[Tokenizer.EOF_TOKEN] = stopParsing;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagInFrameset; _[Tokenizer.END_TAG_TOKEN] = endTagInFrameset; _[Tokenizer.EOF_TOKEN] = stopParsing;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters; _[Tokenizer.COMMENT_TOKEN] = appendComment; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagAfterFrameset; _[Tokenizer.END_TAG_TOKEN] = endTagAfterFrameset; _[Tokenizer.EOF_TOKEN] = stopParsing;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = tokenAfterAfterBody; _[Tokenizer.NULL_CHARACTER_TOKEN] = tokenAfterAfterBody; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[Tokenizer.COMMENT_TOKEN] = appendCommentToDocument; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagAfterAfterBody; _[Tokenizer.END_TAG_TOKEN] = tokenAfterAfterBody; _[Tokenizer.EOF_TOKEN] = stopParsing;

_ = {}; _[Tokenizer.CHARACTER_TOKEN] = _[Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken; _[Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody; _[Tokenizer.COMMENT_TOKEN] = appendCommentToDocument; _[Tokenizer.DOCTYPE_TOKEN] = ignoreToken; _[Tokenizer.START_TAG_TOKEN] = startTagAfterAfterFrameset; _[Tokenizer.END_TAG_TOKEN] = ignoreToken; _[Tokenizer.EOF_TOKEN] = stopParsing;

//Searchable index building utils (<isindex> tag) function getSearchableIndexFormAttrs(isindexStartTagToken) {

var indexAction = Tokenizer.getTokenAttr(isindexStartTagToken, ATTRS.ACTION),
    attrs = [];
if (indexAction !== null) {
    attrs.push({
        name: ATTRS.ACTION,
        value: indexAction
    });
}
return attrs;

}

function getSearchableIndexLabelText(isindexStartTagToken) {

var indexPrompt = Tokenizer.getTokenAttr(isindexStartTagToken, ATTRS.PROMPT);
return indexPrompt === null ? SEARCHABLE_INDEX_DEFAULT_PROMPT : indexPrompt;

}

function getSearchableIndexInputAttrs(isindexStartTagToken) {

var isindexAttrs = isindexStartTagToken.attrs,
    inputAttrs = [];
for (var i = 0; i < isindexAttrs.length; i++) {
    var name = isindexAttrs[i].name;
    if (name !== ATTRS.NAME && name !== ATTRS.ACTION && name !== ATTRS.PROMPT)
        inputAttrs.push(isindexAttrs[i]);
}
inputAttrs.push({
    name: ATTRS.NAME,
    value: SEARCHABLE_INDEX_INPUT_NAME
});
return inputAttrs;

}

//Parser var Parser = module.exports = function (treeAdapter, options) {

this.treeAdapter = treeAdapter || DefaultTreeAdapter;
this.options = Utils.mergeOptions(DEFAULT_OPTIONS, options);
this.scriptHandler = null;
if (this.options.locationInfo)
    LocationInfoMixin.assign(this);

};

//API Parser.prototype.parse = function (html) {

var document = this.treeAdapter.createDocument();
this._reset(html, document, null);
this._runParsingLoop();
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._reset(html, documentMock, fragmentContext);
if (this.treeAdapter.getTagName(fragmentContext) === $.TEMPLATE)
    this._pushTmplInsertionMode(IN_TEMPLATE_MODE);
this._initTokenizerForFragmentParsing();
this._insertFakeRootElement();
this._resetInsertionMode();
this._findFormInFragmentContext();
this._runParsingLoop();
var rootElement = this.treeAdapter.getFirstChild(documentMock),
    fragment = this.treeAdapter.createDocumentFragment();
this._adoptNodes(rootElement, fragment);
return fragment;

};

//Reset state Parser.prototype._reset = function (html, document, fragmentContext) {

this.tokenizer = new Tokenizer(html, 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._iterateParsingLoop = function () {

this._setupTokenizerCDATAMode();
var token = this.tokenizer.getNextToken();
if (this.skipNextNewLine) {
    this.skipNextNewLine = false;
    if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN && token.chars[0] === '\n') {
        if (token.chars.length === 1)
            return;
        token.chars = token.chars.substr(1);
    }
}
if (this._shouldProcessTokenInForeignContent(token))
    this._processTokenInForeignContent(token);
else
    this._processToken(token);

};

Parser.prototype._runParsingLoop = function () {

while (!this.stopped)
    this._iterateParsingLoop();

};

//Text parsing Parser.prototype._setupTokenizerCDATAMode = function () {

var current = this._getAdjustedCurrentElement();
this.tokenizer.allowCDATA = current && current !== this.document &&
                            this.treeAdapter.getNamespaceURI(current) !== NS.HTML &&
                            (!this._isHtmlIntegrationPoint(current)) &&
                            (!this._isMathMLTextIntegrationPoint(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 () {

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._insertTemplate = function (token) {

var tmpl = this.treeAdapter.createElement(token.tagName, NS.HTML, token.attrs),
    content = this.treeAdapter.createDocumentFragment();
this.treeAdapter.appendChild(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._isMathMLTextIntegrationPoint(current))
    return false;
if ((token.type === Tokenizer.START_TAG_TOKEN || isCharacterToken) && this._isHtmlIntegrationPoint(current))
    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._processFakeStartTagWithAttrs = function (tagName, attrs) {

var fakeToken = this.tokenizer.buildStartTagToken(tagName);
fakeToken.attrs = attrs;
this._processToken(fakeToken);

};

Parser.prototype._processFakeStartTag = function (tagName) {

var fakeToken = this.tokenizer.buildStartTagToken(tagName);
this._processToken(fakeToken);
return fakeToken;

};

Parser.prototype._processFakeEndTag = function (tagName) {

var fakeToken = this.tokenizer.buildEndTagToken(tagName);
this._processToken(fakeToken);
return fakeToken;

};

//Integration points Parser.prototype._isMathMLTextIntegrationPoint = function (element) {

var tn = this.treeAdapter.getTagName(element),
    ns = this.treeAdapter.getNamespaceURI(element);
return ForeignContent.isMathMLTextIntegrationPoint(tn, ns);

};

Parser.prototype._isHtmlIntegrationPoint = function (element) {

var tn = this.treeAdapter.getTagName(element),
    ns = this.treeAdapter.getNamespaceURI(element),
    attrs = this.treeAdapter.getAttrList(element);
return ForeignContent.isHtmlIntegrationPoint(tn, ns, attrs);

};

//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 () {

if (this.openElements.hasInTableScope($.TD))
    this._processFakeEndTag($.TD);
else
    this._processFakeEndTag($.TH);

};

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.getChildNodes(openElement)[0];
        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: 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 element = null,
    lastElement = furthestBlock,
    nextElement = p.openElements.getCommonAncestor(furthestBlock);
for (var i = 0; i < AA_INNER_LOOP_ITER; 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);
    if (!elementEntry) {
        p.openElements.remove(element);
        continue;
    }
    if (element === formattingElement)
        break;
    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.getChildNodes(commonAncestor)[0];
    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) {

for (var i = 0; i < AA_OUTER_LOOP_ITER; i++) {
    var 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(p, token) {

//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, token) {

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._processFakeStartTag($.HEAD);
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 === $.COMMAND || 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.popUntilTemplatePopped();
    p.activeFormattingElements.clearToLastMarker();
    p._popTmplInsertionMode();
    p._resetInsertionMode();
}

}

function tokenInHead(p, token) {

p._processFakeEndTag($.HEAD);
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._processFakeStartTag($.BODY);
p.framesetOk = true;
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;
for (var i = p.openElements.stackTop; i >= 0; i--) {
    var element = p.openElements.items[i],
        tn = p.treeAdapter.getTagName(element);
    if ((token.tagName === $.LI && tn === $.LI) ||
        ((token.tagName === $.DD || token.tagName === $.DT) && (tn === $.DD || tn == $.DT))) {
        p._processFakeEndTag(tn);
        break;
    }
    if (tn !== $.ADDRESS && tn !== $.DIV && tn !== $.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._processFakeEndTag($.BUTTON);
    buttonStartTagInBody(p, token);
}
else {
    p._reconstructActiveFormattingElements();
    p._insertElement(token, NS.HTML);
    p.framesetOk = false;
}

}

function aStartTagInBody(p, token) {

var activeElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName($.A);
if (activeElementEntry) {
    p._processFakeEndTag($.A);
    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)) {
    p._processFakeEndTag($.NOBR);
    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();
p._appendElement(token, NS.HTML);
p.framesetOk = false;

}

function imageStartTagInBody(p, token) {

token.tagName = $.IMG;
areaStartTagInBody(p, token);

}

function isindexStartTagInBody(p, token) {

if (!p.formElement || p.openElements.tmplCount > 0) {
    p._processFakeStartTagWithAttrs($.FORM, getSearchableIndexFormAttrs(token));
    p._processFakeStartTag($.HR);
    p._processFakeStartTag($.LABEL);
    p.treeAdapter.insertText(p.openElements.current, getSearchableIndexLabelText(token));
    p._processFakeStartTagWithAttrs($.INPUT, getSearchableIndexInputAttrs(token));
    p._processFakeEndTag($.LABEL);
    p._processFakeStartTag($.HR);
    p._processFakeEndTag($.FORM);
}

}

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._processFakeEndTag($.OPTION);
p._reconstructActiveFormattingElements();
p._insertElement(token, NS.HTML);

}

function rpStartTagInBody(p, token) {

if (p.openElements.hasInScope($.RUBY))
    p.openElements.generateImpliedEndTags();
p._insertElement(token, NS.HTML);

}

function menuitemStartTagInBody(p, token) {

p._appendElement(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 === $.RP || tn === $.RT)
            rpStartTagInBody(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 !== $.COL)
            genericStartTagInBody(p, token);
        break;
    case 4:
        if (tn === $.HTML)
            htmlStartTagInBody(p, token);
        else if (tn === $.BASE || tn === $.LINK || tn === $.META)
            startTagInHead(p, token);
        else if (tn === $.BODY)
            bodyStartTagInBody(p, token);
        else if (tn === $.MAIN || tn === $.MENU)
            addressStartTagInBody(p, token);
        else if (tn === $.FORM)
            formStartTagInBody(p, token);
        else if (tn === $.CODE || tn === $.FONT)
            bStartTagInBody(p, token);
        else if (tn === $.NOBR)
            nobrStartTagInBody(p, token);
        else if (tn === $.AREA)
            areaStartTagInBody(p, token);
        else if (tn === $.MATH)
            mathStartTagInBody(p, token);
        else if (tn !== $.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 || tn === $.COMMAND)
            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 === $.ISINDEX)
            isindexStartTagInBody(p, token);
        else if (tn === $.NOEMBED)
            noembedStartTagInBody(p, token);
        else if (tn !== $.CAPTION)
            genericStartTagInBody(p, token);
        break;
    case 8:
        if (tn === $.BASEFONT || 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, token) {

if (p.openElements.hasInScope($.BODY))
    p.insertionMode = AFTER_BODY_MODE;
else
    token.ignored = true;

}

function htmlEndTagInBody(p, token) {

var fakeToken = p._processFakeEndTag($.BODY);
if (!fakeToken.ignored)
    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, token) {

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, token) {

if (p.openElements.hasInButtonScope($.P)) {
    p.openElements.generateImpliedEndTagsWithExclusion($.P);
    p.openElements.popUntilTagNamePopped($.P);
}
else {
    p._processFakeStartTag($.P);
    p._processToken(token);
}

}

function liEndTagInBody(p, token) {

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, token) {

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, token) {

p._processFakeStartTag($.BR);

}

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 (!p.fragmentContext && p.scriptHandler && token.tagName === $.SCRIPT)
    p.scriptHandler(p.document, 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._processFakeStartTag($.COLGROUP);
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._processFakeStartTag($.TBODY);
p._processToken(token);

}

function tableStartTagInTable(p, token) {

var fakeToken = p._processFakeEndTag($.TABLE);
//NOTE: The fake end tag token here can only be ignored in the fragment case.
if (!fakeToken.ignored)
    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
        token.ignored = true;
}
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) {

if (p.hasNonWhitespacePendingCharacterToken) {
    for (var i = 0; i < p.pendingCharacterTokens.length; i++)
        tokenInTable(p, p.pendingCharacterTokens[i]);
}
else {
    for (var i = 0; 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) {
    var fakeToken = p._processFakeEndTag($.CAPTION);
    //NOTE: The fake end tag token here can only be ignored in the fragment case.
    if (!fakeToken.ignored)
        p._processToken(token);
}
else
    startTagInBody(p, token);

}

function endTagInCaption(p, token) {

var tn = token.tagName;
if (tn === $.CAPTION) {
    if (p.openElements.hasInTableScope($.CAPTION)) {
        p.openElements.generateImpliedEndTags();
        p.openElements.popUntilTagNamePopped($.CAPTION);
        p.activeFormattingElements.clearToLastMarker();
        p.insertionMode = IN_TABLE_MODE;
    }
    else
        token.ignored = true;
}
else if (tn === $.TABLE) {
    var fakeToken = p._processFakeEndTag($.CAPTION);
    //NOTE: The fake end tag token here can only be ignored in the fragment case.
    if (!fakeToken.ignored)
        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)
        token.ignored = true;
    else {
        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) {

var fakeToken = p._processFakeEndTag($.COLGROUP);
//NOTE: The fake end tag token here can only be ignored in the fragment case.
if (!fakeToken.ignored)
    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._processFakeStartTag($.TR);
    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._processFakeEndTag(p.openElements.currentTagName);
        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._processFakeEndTag(p.openElements.currentTagName);
        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) {
    var fakeToken = p._processFakeEndTag($.TR);
    //NOTE: The fake end tag token here can only be ignored in the fragment case.
    if (!fakeToken.ignored)
        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
        token.ignored = true;
}
else if (tn === $.TABLE) {
    var fakeToken = p._processFakeEndTag($.TR);
    //NOTE: The fake end tag token here can only be ignored in the fragment case.
    if (!fakeToken.ignored)
        p._processToken(token);
}
else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
    if (p.openElements.hasInTableScope(tn)) {
        p._processFakeEndTag($.TR);
        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._processFakeEndTag($.OPTION);
    p._insertElement(token, NS.HTML);
}
else if (tn === $.OPTGROUP) {
    if (p.openElements.currentTagName === $.OPTION)
        p._processFakeEndTag($.OPTION);
    if (p.openElements.currentTagName === $.OPTGROUP)
        p._processFakeEndTag($.OPTGROUP);
    p._insertElement(token, NS.HTML);
}
else if (tn === $.SELECT)
    p._processFakeEndTag($.SELECT);
else if (tn === $.INPUT || tn === $.KEYGEN || tn === $.TEXTAREA) {
    if (p.openElements.hasInSelectScope($.SELECT)) {
        p._processFakeEndTag($.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._processFakeEndTag($.OPTION);
    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._processFakeEndTag($.SELECT);
    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._processFakeEndTag($.SELECT);
        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.popUntilTemplatePopped();
    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._isMathMLTextIntegrationPoint(p.openElements.current)) &&
           (!p._isHtmlIntegrationPoint(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;
    }
}

}