'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; | |
} | |
} | |
} |