'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[$.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;



//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: 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 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;

        }

    }

}