You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2957 lines
91 KiB
2957 lines
91 KiB
'use strict';
|
|
|
|
const Tokenizer = require('../tokenizer');
|
|
const OpenElementStack = require('./open-element-stack');
|
|
const FormattingElementList = require('./formatting-element-list');
|
|
const LocationInfoParserMixin = require('../extensions/location-info/parser-mixin');
|
|
const ErrorReportingParserMixin = require('../extensions/error-reporting/parser-mixin');
|
|
const Mixin = require('../utils/mixin');
|
|
const defaultTreeAdapter = require('../tree-adapters/default');
|
|
const mergeOptions = require('../utils/merge-options');
|
|
const doctype = require('../common/doctype');
|
|
const foreignContent = require('../common/foreign-content');
|
|
const ERR = require('../common/error-codes');
|
|
const unicode = require('../common/unicode');
|
|
const HTML = require('../common/html');
|
|
|
|
//Aliases
|
|
const $ = HTML.TAG_NAMES;
|
|
const NS = HTML.NAMESPACES;
|
|
const ATTRS = HTML.ATTRS;
|
|
|
|
const DEFAULT_OPTIONS = {
|
|
scriptingEnabled: true,
|
|
sourceCodeLocationInfo: false,
|
|
onParseError: null,
|
|
treeAdapter: defaultTreeAdapter
|
|
};
|
|
|
|
//Misc constants
|
|
const HIDDEN_INPUT_TYPE = 'hidden';
|
|
|
|
//Adoption agency loops iteration count
|
|
const AA_OUTER_LOOP_ITER = 8;
|
|
const AA_INNER_LOOP_ITER = 3;
|
|
|
|
//Insertion modes
|
|
const INITIAL_MODE = 'INITIAL_MODE';
|
|
const BEFORE_HTML_MODE = 'BEFORE_HTML_MODE';
|
|
const BEFORE_HEAD_MODE = 'BEFORE_HEAD_MODE';
|
|
const IN_HEAD_MODE = 'IN_HEAD_MODE';
|
|
const IN_HEAD_NO_SCRIPT_MODE = 'IN_HEAD_NO_SCRIPT_MODE';
|
|
const AFTER_HEAD_MODE = 'AFTER_HEAD_MODE';
|
|
const IN_BODY_MODE = 'IN_BODY_MODE';
|
|
const TEXT_MODE = 'TEXT_MODE';
|
|
const IN_TABLE_MODE = 'IN_TABLE_MODE';
|
|
const IN_TABLE_TEXT_MODE = 'IN_TABLE_TEXT_MODE';
|
|
const IN_CAPTION_MODE = 'IN_CAPTION_MODE';
|
|
const IN_COLUMN_GROUP_MODE = 'IN_COLUMN_GROUP_MODE';
|
|
const IN_TABLE_BODY_MODE = 'IN_TABLE_BODY_MODE';
|
|
const IN_ROW_MODE = 'IN_ROW_MODE';
|
|
const IN_CELL_MODE = 'IN_CELL_MODE';
|
|
const IN_SELECT_MODE = 'IN_SELECT_MODE';
|
|
const IN_SELECT_IN_TABLE_MODE = 'IN_SELECT_IN_TABLE_MODE';
|
|
const IN_TEMPLATE_MODE = 'IN_TEMPLATE_MODE';
|
|
const AFTER_BODY_MODE = 'AFTER_BODY_MODE';
|
|
const IN_FRAMESET_MODE = 'IN_FRAMESET_MODE';
|
|
const AFTER_FRAMESET_MODE = 'AFTER_FRAMESET_MODE';
|
|
const AFTER_AFTER_BODY_MODE = 'AFTER_AFTER_BODY_MODE';
|
|
const AFTER_AFTER_FRAMESET_MODE = 'AFTER_AFTER_FRAMESET_MODE';
|
|
|
|
//Insertion mode reset map
|
|
const INSERTION_MODE_RESET_MAP = {
|
|
[$.TR]: IN_ROW_MODE,
|
|
[$.TBODY]: IN_TABLE_BODY_MODE,
|
|
[$.THEAD]: IN_TABLE_BODY_MODE,
|
|
[$.TFOOT]: IN_TABLE_BODY_MODE,
|
|
[$.CAPTION]: IN_CAPTION_MODE,
|
|
[$.COLGROUP]: IN_COLUMN_GROUP_MODE,
|
|
[$.TABLE]: IN_TABLE_MODE,
|
|
[$.BODY]: IN_BODY_MODE,
|
|
[$.FRAMESET]: IN_FRAMESET_MODE
|
|
};
|
|
|
|
//Template insertion mode switch map
|
|
const TEMPLATE_INSERTION_MODE_SWITCH_MAP = {
|
|
[$.CAPTION]: IN_TABLE_MODE,
|
|
[$.COLGROUP]: IN_TABLE_MODE,
|
|
[$.TBODY]: IN_TABLE_MODE,
|
|
[$.TFOOT]: IN_TABLE_MODE,
|
|
[$.THEAD]: IN_TABLE_MODE,
|
|
[$.COL]: IN_COLUMN_GROUP_MODE,
|
|
[$.TR]: IN_TABLE_BODY_MODE,
|
|
[$.TD]: IN_ROW_MODE,
|
|
[$.TH]: IN_ROW_MODE
|
|
};
|
|
|
|
//Token handlers map for insertion modes
|
|
const TOKEN_HANDLERS = {
|
|
[INITIAL_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: tokenInInitialMode,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenInInitialMode,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: doctypeInInitialMode,
|
|
[Tokenizer.START_TAG_TOKEN]: tokenInInitialMode,
|
|
[Tokenizer.END_TAG_TOKEN]: tokenInInitialMode,
|
|
[Tokenizer.EOF_TOKEN]: tokenInInitialMode
|
|
},
|
|
[BEFORE_HTML_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: tokenBeforeHtml,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHtml,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagBeforeHtml,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagBeforeHtml,
|
|
[Tokenizer.EOF_TOKEN]: tokenBeforeHtml
|
|
},
|
|
[BEFORE_HEAD_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: tokenBeforeHead,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHead,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagBeforeHead,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagBeforeHead,
|
|
[Tokenizer.EOF_TOKEN]: tokenBeforeHead
|
|
},
|
|
[IN_HEAD_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: tokenInHead,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHead,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInHead,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInHead,
|
|
[Tokenizer.EOF_TOKEN]: tokenInHead
|
|
},
|
|
[IN_HEAD_NO_SCRIPT_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: tokenInHeadNoScript,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHeadNoScript,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInHeadNoScript,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInHeadNoScript,
|
|
[Tokenizer.EOF_TOKEN]: tokenInHeadNoScript
|
|
},
|
|
[AFTER_HEAD_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: tokenAfterHead,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterHead,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagAfterHead,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagAfterHead,
|
|
[Tokenizer.EOF_TOKEN]: tokenAfterHead
|
|
},
|
|
[IN_BODY_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: characterInBody,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInBody,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInBody,
|
|
[Tokenizer.EOF_TOKEN]: eofInBody
|
|
},
|
|
[TEXT_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.COMMENT_TOKEN]: ignoreToken,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: ignoreToken,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInText,
|
|
[Tokenizer.EOF_TOKEN]: eofInText
|
|
},
|
|
[IN_TABLE_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: characterInTable,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInTable,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInTable,
|
|
[Tokenizer.EOF_TOKEN]: eofInBody
|
|
},
|
|
[IN_TABLE_TEXT_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: characterInTableText,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInTableText,
|
|
[Tokenizer.COMMENT_TOKEN]: tokenInTableText,
|
|
[Tokenizer.DOCTYPE_TOKEN]: tokenInTableText,
|
|
[Tokenizer.START_TAG_TOKEN]: tokenInTableText,
|
|
[Tokenizer.END_TAG_TOKEN]: tokenInTableText,
|
|
[Tokenizer.EOF_TOKEN]: tokenInTableText
|
|
},
|
|
[IN_CAPTION_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: characterInBody,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInCaption,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInCaption,
|
|
[Tokenizer.EOF_TOKEN]: eofInBody
|
|
},
|
|
[IN_COLUMN_GROUP_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: tokenInColumnGroup,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenInColumnGroup,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInColumnGroup,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInColumnGroup,
|
|
[Tokenizer.EOF_TOKEN]: eofInBody
|
|
},
|
|
[IN_TABLE_BODY_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: characterInTable,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInTableBody,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInTableBody,
|
|
[Tokenizer.EOF_TOKEN]: eofInBody
|
|
},
|
|
[IN_ROW_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: characterInTable,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInRow,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInRow,
|
|
[Tokenizer.EOF_TOKEN]: eofInBody
|
|
},
|
|
[IN_CELL_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: characterInBody,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInCell,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInCell,
|
|
[Tokenizer.EOF_TOKEN]: eofInBody
|
|
},
|
|
[IN_SELECT_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInSelect,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInSelect,
|
|
[Tokenizer.EOF_TOKEN]: eofInBody
|
|
},
|
|
[IN_SELECT_IN_TABLE_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInSelectInTable,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInSelectInTable,
|
|
[Tokenizer.EOF_TOKEN]: eofInBody
|
|
},
|
|
[IN_TEMPLATE_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: characterInBody,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInTemplate,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInTemplate,
|
|
[Tokenizer.EOF_TOKEN]: eofInTemplate
|
|
},
|
|
[AFTER_BODY_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: tokenAfterBody,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterBody,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
|
|
[Tokenizer.COMMENT_TOKEN]: appendCommentToRootHtmlElement,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagAfterBody,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagAfterBody,
|
|
[Tokenizer.EOF_TOKEN]: stopParsing
|
|
},
|
|
[IN_FRAMESET_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagInFrameset,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagInFrameset,
|
|
[Tokenizer.EOF_TOKEN]: stopParsing
|
|
},
|
|
[AFTER_FRAMESET_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
|
|
[Tokenizer.COMMENT_TOKEN]: appendComment,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagAfterFrameset,
|
|
[Tokenizer.END_TAG_TOKEN]: endTagAfterFrameset,
|
|
[Tokenizer.EOF_TOKEN]: stopParsing
|
|
},
|
|
[AFTER_AFTER_BODY_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: tokenAfterAfterBody,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterAfterBody,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
|
|
[Tokenizer.COMMENT_TOKEN]: appendCommentToDocument,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagAfterAfterBody,
|
|
[Tokenizer.END_TAG_TOKEN]: tokenAfterAfterBody,
|
|
[Tokenizer.EOF_TOKEN]: stopParsing
|
|
},
|
|
[AFTER_AFTER_FRAMESET_MODE]: {
|
|
[Tokenizer.CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
|
|
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
|
|
[Tokenizer.COMMENT_TOKEN]: appendCommentToDocument,
|
|
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
|
|
[Tokenizer.START_TAG_TOKEN]: startTagAfterAfterFrameset,
|
|
[Tokenizer.END_TAG_TOKEN]: ignoreToken,
|
|
[Tokenizer.EOF_TOKEN]: stopParsing
|
|
}
|
|
};
|
|
|
|
//Parser
|
|
class Parser {
|
|
constructor(options) {
|
|
this.options = mergeOptions(DEFAULT_OPTIONS, options);
|
|
|
|
this.treeAdapter = this.options.treeAdapter;
|
|
this.pendingScript = null;
|
|
|
|
if (this.options.sourceCodeLocationInfo) {
|
|
Mixin.install(this, LocationInfoParserMixin);
|
|
}
|
|
|
|
if (this.options.onParseError) {
|
|
Mixin.install(this, ErrorReportingParserMixin, { onParseError: this.options.onParseError });
|
|
}
|
|
}
|
|
|
|
// API
|
|
parse(html) {
|
|
const document = this.treeAdapter.createDocument();
|
|
|
|
this._bootstrap(document, null);
|
|
this.tokenizer.write(html, true);
|
|
this._runParsingLoop(null);
|
|
|
|
return document;
|
|
}
|
|
|
|
parseFragment(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`.
|
|
const documentMock = this.treeAdapter.createElement('documentmock', NS.HTML, []);
|
|
|
|
this._bootstrap(documentMock, fragmentContext);
|
|
|
|
if (this.treeAdapter.getTagName(fragmentContext) === $.TEMPLATE) {
|
|
this._pushTmplInsertionMode(IN_TEMPLATE_MODE);
|
|
}
|
|
|
|
this._initTokenizerForFragmentParsing();
|
|
this._insertFakeRootElement();
|
|
this._resetInsertionMode();
|
|
this._findFormInFragmentContext();
|
|
this.tokenizer.write(html, true);
|
|
this._runParsingLoop(null);
|
|
|
|
const rootElement = this.treeAdapter.getFirstChild(documentMock);
|
|
const fragment = this.treeAdapter.createDocumentFragment();
|
|
|
|
this._adoptNodes(rootElement, fragment);
|
|
|
|
return fragment;
|
|
}
|
|
|
|
//Bootstrap parser
|
|
_bootstrap(document, fragmentContext) {
|
|
this.tokenizer = new Tokenizer(this.options);
|
|
|
|
this.stopped = false;
|
|
|
|
this.insertionMode = INITIAL_MODE;
|
|
this.originalInsertionMode = '';
|
|
|
|
this.document = document;
|
|
this.fragmentContext = fragmentContext;
|
|
|
|
this.headElement = null;
|
|
this.formElement = null;
|
|
|
|
this.openElements = new OpenElementStack(this.document, this.treeAdapter);
|
|
this.activeFormattingElements = new FormattingElementList(this.treeAdapter);
|
|
|
|
this.tmplInsertionModeStack = [];
|
|
this.tmplInsertionModeStackTop = -1;
|
|
this.currentTmplInsertionMode = null;
|
|
|
|
this.pendingCharacterTokens = [];
|
|
this.hasNonWhitespacePendingCharacterToken = false;
|
|
|
|
this.framesetOk = true;
|
|
this.skipNextNewLine = false;
|
|
this.fosterParentingEnabled = false;
|
|
}
|
|
|
|
//Errors
|
|
_err() {
|
|
// NOTE: err reporting is noop by default. Enabled by mixin.
|
|
}
|
|
|
|
//Parsing loop
|
|
_runParsingLoop(scriptHandler) {
|
|
while (!this.stopped) {
|
|
this._setupTokenizerCDATAMode();
|
|
|
|
const token = this.tokenizer.getNextToken();
|
|
|
|
if (token.type === Tokenizer.HIBERNATION_TOKEN) {
|
|
break;
|
|
}
|
|
|
|
if (this.skipNextNewLine) {
|
|
this.skipNextNewLine = false;
|
|
|
|
if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN && token.chars[0] === '\n') {
|
|
if (token.chars.length === 1) {
|
|
continue;
|
|
}
|
|
|
|
token.chars = token.chars.substr(1);
|
|
}
|
|
}
|
|
|
|
this._processInputToken(token);
|
|
|
|
if (scriptHandler && this.pendingScript) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
runParsingLoopForCurrentChunk(writeCallback, scriptHandler) {
|
|
this._runParsingLoop(scriptHandler);
|
|
|
|
if (scriptHandler && this.pendingScript) {
|
|
const script = this.pendingScript;
|
|
|
|
this.pendingScript = null;
|
|
|
|
scriptHandler(script);
|
|
|
|
return;
|
|
}
|
|
|
|
if (writeCallback) {
|
|
writeCallback();
|
|
}
|
|
}
|
|
|
|
//Text parsing
|
|
_setupTokenizerCDATAMode() {
|
|
const current = this._getAdjustedCurrentElement();
|
|
|
|
this.tokenizer.allowCDATA =
|
|
current &&
|
|
current !== this.document &&
|
|
this.treeAdapter.getNamespaceURI(current) !== NS.HTML &&
|
|
!this._isIntegrationPoint(current);
|
|
}
|
|
|
|
_switchToTextParsing(currentToken, nextTokenizerState) {
|
|
this._insertElement(currentToken, NS.HTML);
|
|
this.tokenizer.state = nextTokenizerState;
|
|
this.originalInsertionMode = this.insertionMode;
|
|
this.insertionMode = TEXT_MODE;
|
|
}
|
|
|
|
switchToPlaintextParsing() {
|
|
this.insertionMode = TEXT_MODE;
|
|
this.originalInsertionMode = IN_BODY_MODE;
|
|
this.tokenizer.state = Tokenizer.MODE.PLAINTEXT;
|
|
}
|
|
|
|
//Fragment parsing
|
|
_getAdjustedCurrentElement() {
|
|
return this.openElements.stackTop === 0 && this.fragmentContext
|
|
? this.fragmentContext
|
|
: this.openElements.current;
|
|
}
|
|
|
|
_findFormInFragmentContext() {
|
|
let node = this.fragmentContext;
|
|
|
|
do {
|
|
if (this.treeAdapter.getTagName(node) === $.FORM) {
|
|
this.formElement = node;
|
|
break;
|
|
}
|
|
|
|
node = this.treeAdapter.getParentNode(node);
|
|
} while (node);
|
|
}
|
|
|
|
_initTokenizerForFragmentParsing() {
|
|
if (this.treeAdapter.getNamespaceURI(this.fragmentContext) === NS.HTML) {
|
|
const 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
|
|
_setDocumentType(token) {
|
|
const name = token.name || '';
|
|
const publicId = token.publicId || '';
|
|
const systemId = token.systemId || '';
|
|
|
|
this.treeAdapter.setDocumentType(this.document, name, publicId, systemId);
|
|
}
|
|
|
|
_attachElementToTree(element) {
|
|
if (this._shouldFosterParentOnInsertion()) {
|
|
this._fosterParentElement(element);
|
|
} else {
|
|
const parent = this.openElements.currentTmplContent || this.openElements.current;
|
|
|
|
this.treeAdapter.appendChild(parent, element);
|
|
}
|
|
}
|
|
|
|
_appendElement(token, namespaceURI) {
|
|
const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);
|
|
|
|
this._attachElementToTree(element);
|
|
}
|
|
|
|
_insertElement(token, namespaceURI) {
|
|
const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);
|
|
|
|
this._attachElementToTree(element);
|
|
this.openElements.push(element);
|
|
}
|
|
|
|
_insertFakeElement(tagName) {
|
|
const element = this.treeAdapter.createElement(tagName, NS.HTML, []);
|
|
|
|
this._attachElementToTree(element);
|
|
this.openElements.push(element);
|
|
}
|
|
|
|
_insertTemplate(token) {
|
|
const tmpl = this.treeAdapter.createElement(token.tagName, NS.HTML, token.attrs);
|
|
const content = this.treeAdapter.createDocumentFragment();
|
|
|
|
this.treeAdapter.setTemplateContent(tmpl, content);
|
|
this._attachElementToTree(tmpl);
|
|
this.openElements.push(tmpl);
|
|
}
|
|
|
|
_insertFakeRootElement() {
|
|
const element = this.treeAdapter.createElement($.HTML, NS.HTML, []);
|
|
|
|
this.treeAdapter.appendChild(this.openElements.current, element);
|
|
this.openElements.push(element);
|
|
}
|
|
|
|
_appendCommentNode(token, parent) {
|
|
const commentNode = this.treeAdapter.createCommentNode(token.data);
|
|
|
|
this.treeAdapter.appendChild(parent, commentNode);
|
|
}
|
|
|
|
_insertCharacters(token) {
|
|
if (this._shouldFosterParentOnInsertion()) {
|
|
this._fosterParentText(token.chars);
|
|
} else {
|
|
const parent = this.openElements.currentTmplContent || this.openElements.current;
|
|
|
|
this.treeAdapter.insertText(parent, token.chars);
|
|
}
|
|
}
|
|
|
|
_adoptNodes(donor, recipient) {
|
|
for (let child = this.treeAdapter.getFirstChild(donor); child; child = this.treeAdapter.getFirstChild(donor)) {
|
|
this.treeAdapter.detachNode(child);
|
|
this.treeAdapter.appendChild(recipient, child);
|
|
}
|
|
}
|
|
|
|
//Token processing
|
|
_shouldProcessTokenInForeignContent(token) {
|
|
const current = this._getAdjustedCurrentElement();
|
|
|
|
if (!current || current === this.document) {
|
|
return false;
|
|
}
|
|
|
|
const 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;
|
|
}
|
|
|
|
const isCharacterToken =
|
|
token.type === Tokenizer.CHARACTER_TOKEN ||
|
|
token.type === Tokenizer.NULL_CHARACTER_TOKEN ||
|
|
token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN;
|
|
|
|
const isMathMLTextStartTag =
|
|
token.type === Tokenizer.START_TAG_TOKEN && token.tagName !== $.MGLYPH && token.tagName !== $.MALIGNMARK;
|
|
|
|
if ((isMathMLTextStartTag || isCharacterToken) && this._isIntegrationPoint(current, NS.MATHML)) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
(token.type === Tokenizer.START_TAG_TOKEN || isCharacterToken) &&
|
|
this._isIntegrationPoint(current, NS.HTML)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return token.type !== Tokenizer.EOF_TOKEN;
|
|
}
|
|
|
|
_processToken(token) {
|
|
TOKEN_HANDLERS[this.insertionMode][token.type](this, token);
|
|
}
|
|
|
|
_processTokenInBodyMode(token) {
|
|
TOKEN_HANDLERS[IN_BODY_MODE][token.type](this, token);
|
|
}
|
|
|
|
_processTokenInForeignContent(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);
|
|
}
|
|
}
|
|
|
|
_processInputToken(token) {
|
|
if (this._shouldProcessTokenInForeignContent(token)) {
|
|
this._processTokenInForeignContent(token);
|
|
} else {
|
|
this._processToken(token);
|
|
}
|
|
|
|
if (token.type === Tokenizer.START_TAG_TOKEN && token.selfClosing && !token.ackSelfClosing) {
|
|
this._err(ERR.nonVoidHtmlElementStartTagWithTrailingSolidus);
|
|
}
|
|
}
|
|
|
|
//Integration points
|
|
_isIntegrationPoint(element, foreignNS) {
|
|
const tn = this.treeAdapter.getTagName(element);
|
|
const ns = this.treeAdapter.getNamespaceURI(element);
|
|
const attrs = this.treeAdapter.getAttrList(element);
|
|
|
|
return foreignContent.isIntegrationPoint(tn, ns, attrs, foreignNS);
|
|
}
|
|
|
|
//Active formatting elements reconstruction
|
|
_reconstructActiveFormattingElements() {
|
|
const listLength = this.activeFormattingElements.length;
|
|
|
|
if (listLength) {
|
|
let unopenIdx = listLength;
|
|
let 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 (let 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
|
|
_closeTableCell() {
|
|
this.openElements.generateImpliedEndTags();
|
|
this.openElements.popUntilTableCellPopped();
|
|
this.activeFormattingElements.clearToLastMarker();
|
|
this.insertionMode = IN_ROW_MODE;
|
|
}
|
|
|
|
_closePElement() {
|
|
this.openElements.generateImpliedEndTagsWithExclusion($.P);
|
|
this.openElements.popUntilTagNamePopped($.P);
|
|
}
|
|
|
|
//Insertion modes
|
|
_resetInsertionMode() {
|
|
for (let i = this.openElements.stackTop, last = false; i >= 0; i--) {
|
|
let element = this.openElements.items[i];
|
|
|
|
if (i === 0) {
|
|
last = true;
|
|
|
|
if (this.fragmentContext) {
|
|
element = this.fragmentContext;
|
|
}
|
|
}
|
|
|
|
const tn = this.treeAdapter.getTagName(element);
|
|
const 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
_resetInsertionModeForSelect(selectIdx) {
|
|
if (selectIdx > 0) {
|
|
for (let i = selectIdx - 1; i > 0; i--) {
|
|
const ancestor = this.openElements.items[i];
|
|
const 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;
|
|
}
|
|
|
|
_pushTmplInsertionMode(mode) {
|
|
this.tmplInsertionModeStack.push(mode);
|
|
this.tmplInsertionModeStackTop++;
|
|
this.currentTmplInsertionMode = mode;
|
|
}
|
|
|
|
_popTmplInsertionMode() {
|
|
this.tmplInsertionModeStack.pop();
|
|
this.tmplInsertionModeStackTop--;
|
|
this.currentTmplInsertionMode = this.tmplInsertionModeStack[this.tmplInsertionModeStackTop];
|
|
}
|
|
|
|
//Foster parenting
|
|
_isElementCausesFosterParenting(element) {
|
|
const tn = this.treeAdapter.getTagName(element);
|
|
|
|
return tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD || tn === $.TR;
|
|
}
|
|
|
|
_shouldFosterParentOnInsertion() {
|
|
return this.fosterParentingEnabled && this._isElementCausesFosterParenting(this.openElements.current);
|
|
}
|
|
|
|
_findFosterParentingLocation() {
|
|
const location = {
|
|
parent: null,
|
|
beforeElement: null
|
|
};
|
|
|
|
for (let i = this.openElements.stackTop; i >= 0; i--) {
|
|
const openElement = this.openElements.items[i];
|
|
const tn = this.treeAdapter.getTagName(openElement);
|
|
const ns = this.treeAdapter.getNamespaceURI(openElement);
|
|
|
|
if (tn === $.TEMPLATE && ns === NS.HTML) {
|
|
location.parent = this.treeAdapter.getTemplateContent(openElement);
|
|
break;
|
|
} else if (tn === $.TABLE) {
|
|
location.parent = this.treeAdapter.getParentNode(openElement);
|
|
|
|
if (location.parent) {
|
|
location.beforeElement = openElement;
|
|
} else {
|
|
location.parent = this.openElements.items[i - 1];
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!location.parent) {
|
|
location.parent = this.openElements.items[0];
|
|
}
|
|
|
|
return location;
|
|
}
|
|
|
|
_fosterParentElement(element) {
|
|
const location = this._findFosterParentingLocation();
|
|
|
|
if (location.beforeElement) {
|
|
this.treeAdapter.insertBefore(location.parent, element, location.beforeElement);
|
|
} else {
|
|
this.treeAdapter.appendChild(location.parent, element);
|
|
}
|
|
}
|
|
|
|
_fosterParentText(chars) {
|
|
const location = this._findFosterParentingLocation();
|
|
|
|
if (location.beforeElement) {
|
|
this.treeAdapter.insertTextBefore(location.parent, chars, location.beforeElement);
|
|
} else {
|
|
this.treeAdapter.insertText(location.parent, chars);
|
|
}
|
|
}
|
|
|
|
//Special elements
|
|
_isSpecialElement(element) {
|
|
const tn = this.treeAdapter.getTagName(element);
|
|
const ns = this.treeAdapter.getNamespaceURI(element);
|
|
|
|
return HTML.SPECIAL_ELEMENTS[ns][tn];
|
|
}
|
|
}
|
|
|
|
module.exports = Parser;
|
|
|
|
//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) {
|
|
let 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) {
|
|
let furthestBlock = null;
|
|
|
|
for (let i = p.openElements.stackTop; i >= 0; i--) {
|
|
const 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) {
|
|
let lastElement = furthestBlock;
|
|
let nextElement = p.openElements.getCommonAncestor(furthestBlock);
|
|
|
|
for (let i = 0, element = nextElement; element !== formattingElement; i++, element = nextElement) {
|
|
//NOTE: store next element for the next loop iteration (it may be deleted from the stack by step 9.5)
|
|
nextElement = p.openElements.getCommonAncestor(element);
|
|
|
|
const elementEntry = p.activeFormattingElements.getElementEntry(element);
|
|
const counterOverflow = elementEntry && i >= AA_INNER_LOOP_ITER;
|
|
const shouldRemoveFromOpenElements = !elementEntry || counterOverflow;
|
|
|
|
if (shouldRemoveFromOpenElements) {
|
|
if (counterOverflow) {
|
|
p.activeFormattingElements.removeEntry(elementEntry);
|
|
}
|
|
|
|
p.openElements.remove(element);
|
|
} else {
|
|
element = aaRecreateElementFromEntry(p, elementEntry);
|
|
|
|
if (lastElement === furthestBlock) {
|
|
p.activeFormattingElements.bookmark = elementEntry;
|
|
}
|
|
|
|
p.treeAdapter.detachNode(lastElement);
|
|
p.treeAdapter.appendChild(element, lastElement);
|
|
lastElement = element;
|
|
}
|
|
}
|
|
|
|
return lastElement;
|
|
}
|
|
|
|
//Step 13.7 of the algorithm
|
|
function aaRecreateElementFromEntry(p, elementEntry) {
|
|
const ns = p.treeAdapter.getNamespaceURI(elementEntry.element);
|
|
const 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 {
|
|
const tn = p.treeAdapter.getTagName(commonAncestor);
|
|
const ns = p.treeAdapter.getNamespaceURI(commonAncestor);
|
|
|
|
if (tn === $.TEMPLATE && ns === NS.HTML) {
|
|
commonAncestor = p.treeAdapter.getTemplateContent(commonAncestor);
|
|
}
|
|
|
|
p.treeAdapter.appendChild(commonAncestor, lastElement);
|
|
}
|
|
}
|
|
|
|
//Steps 15-19 of the algorithm
|
|
function aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry) {
|
|
const ns = p.treeAdapter.getNamespaceURI(formattingElementEntry.element);
|
|
const token = formattingElementEntry.token;
|
|
const 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) {
|
|
let formattingElementEntry;
|
|
|
|
for (let i = 0; i < AA_OUTER_LOOP_ITER; i++) {
|
|
formattingElementEntry = aaObtainFormattingElementEntry(p, token, formattingElementEntry);
|
|
|
|
if (!formattingElementEntry) {
|
|
break;
|
|
}
|
|
|
|
const furthestBlock = aaObtainFurthestBlock(p, formattingElementEntry);
|
|
|
|
if (!furthestBlock) {
|
|
break;
|
|
}
|
|
|
|
p.activeFormattingElements.bookmark = formattingElementEntry;
|
|
|
|
const lastElement = aaInnerLoop(p, furthestBlock, formattingElementEntry.element);
|
|
const commonAncestor = p.openElements.getCommonAncestor(formattingElementEntry.element);
|
|
|
|
p.treeAdapter.detachNode(lastElement);
|
|
aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement);
|
|
aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry);
|
|
}
|
|
}
|
|
|
|
//Generic token handlers
|
|
//------------------------------------------------------------------
|
|
function ignoreToken() {
|
|
//NOTE: do nothing =)
|
|
}
|
|
|
|
function misplacedDoctype(p) {
|
|
p._err(ERR.misplacedDoctype);
|
|
}
|
|
|
|
function appendComment(p, token) {
|
|
p._appendCommentNode(token, p.openElements.currentTmplContent || p.openElements.current);
|
|
}
|
|
|
|
function appendCommentToRootHtmlElement(p, token) {
|
|
p._appendCommentNode(token, p.openElements.items[0]);
|
|
}
|
|
|
|
function appendCommentToDocument(p, token) {
|
|
p._appendCommentNode(token, p.document);
|
|
}
|
|
|
|
function insertCharacters(p, token) {
|
|
p._insertCharacters(token);
|
|
}
|
|
|
|
function stopParsing(p) {
|
|
p.stopped = true;
|
|
}
|
|
|
|
// The "initial" insertion mode
|
|
//------------------------------------------------------------------
|
|
function doctypeInInitialMode(p, token) {
|
|
p._setDocumentType(token);
|
|
|
|
const mode = token.forceQuirks ? HTML.DOCUMENT_MODE.QUIRKS : doctype.getDocumentMode(token);
|
|
|
|
if (!doctype.isConforming(token)) {
|
|
p._err(ERR.nonConformingDoctype);
|
|
}
|
|
|
|
p.treeAdapter.setDocumentMode(p.document, mode);
|
|
|
|
p.insertionMode = BEFORE_HTML_MODE;
|
|
}
|
|
|
|
function tokenInInitialMode(p, token) {
|
|
p._err(ERR.missingDoctype, { beforeToken: true });
|
|
p.treeAdapter.setDocumentMode(p.document, HTML.DOCUMENT_MODE.QUIRKS);
|
|
p.insertionMode = BEFORE_HTML_MODE;
|
|
p._processToken(token);
|
|
}
|
|
|
|
// 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) {
|
|
const 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);
|
|
}
|
|
|
|
// The "before head" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagBeforeHead(p, token) {
|
|
const 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) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.HEAD || tn === $.BODY || tn === $.HTML || tn === $.BR) {
|
|
tokenBeforeHead(p, token);
|
|
} else {
|
|
p._err(ERR.endTagWithoutMatchingOpenElement);
|
|
}
|
|
}
|
|
|
|
function tokenBeforeHead(p, token) {
|
|
p._insertFakeElement($.HEAD);
|
|
p.headElement = p.openElements.current;
|
|
p.insertionMode = IN_HEAD_MODE;
|
|
p._processToken(token);
|
|
}
|
|
|
|
// The "in head" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagInHead(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.HTML) {
|
|
startTagInBody(p, token);
|
|
} else if (tn === $.BASE || tn === $.BASEFONT || tn === $.BGSOUND || tn === $.LINK || tn === $.META) {
|
|
p._appendElement(token, NS.HTML);
|
|
token.ackSelfClosing = true;
|
|
} else if (tn === $.TITLE) {
|
|
p._switchToTextParsing(token, Tokenizer.MODE.RCDATA);
|
|
} else if (tn === $.NOSCRIPT) {
|
|
if (p.options.scriptingEnabled) {
|
|
p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
|
|
} else {
|
|
p._insertElement(token, NS.HTML);
|
|
p.insertionMode = IN_HEAD_NO_SCRIPT_MODE;
|
|
}
|
|
} else if (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) {
|
|
p._err(ERR.misplacedStartTagForHeadElement);
|
|
} else {
|
|
tokenInHead(p, token);
|
|
}
|
|
}
|
|
|
|
function endTagInHead(p, token) {
|
|
const 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) {
|
|
if (p.openElements.tmplCount > 0) {
|
|
p.openElements.generateImpliedEndTagsThoroughly();
|
|
|
|
if (p.openElements.currentTagName !== $.TEMPLATE) {
|
|
p._err(ERR.closingOfElementWithOpenChildElements);
|
|
}
|
|
|
|
p.openElements.popUntilTagNamePopped($.TEMPLATE);
|
|
p.activeFormattingElements.clearToLastMarker();
|
|
p._popTmplInsertionMode();
|
|
p._resetInsertionMode();
|
|
} else {
|
|
p._err(ERR.endTagWithoutMatchingOpenElement);
|
|
}
|
|
} else {
|
|
p._err(ERR.endTagWithoutMatchingOpenElement);
|
|
}
|
|
}
|
|
|
|
function tokenInHead(p, token) {
|
|
p.openElements.pop();
|
|
p.insertionMode = AFTER_HEAD_MODE;
|
|
p._processToken(token);
|
|
}
|
|
|
|
// The "in head no script" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagInHeadNoScript(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.HTML) {
|
|
startTagInBody(p, token);
|
|
} else if (
|
|
tn === $.BASEFONT ||
|
|
tn === $.BGSOUND ||
|
|
tn === $.HEAD ||
|
|
tn === $.LINK ||
|
|
tn === $.META ||
|
|
tn === $.NOFRAMES ||
|
|
tn === $.STYLE
|
|
) {
|
|
startTagInHead(p, token);
|
|
} else if (tn === $.NOSCRIPT) {
|
|
p._err(ERR.nestedNoscriptInHead);
|
|
} else {
|
|
tokenInHeadNoScript(p, token);
|
|
}
|
|
}
|
|
|
|
function endTagInHeadNoScript(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.NOSCRIPT) {
|
|
p.openElements.pop();
|
|
p.insertionMode = IN_HEAD_MODE;
|
|
} else if (tn === $.BR) {
|
|
tokenInHeadNoScript(p, token);
|
|
} else {
|
|
p._err(ERR.endTagWithoutMatchingOpenElement);
|
|
}
|
|
}
|
|
|
|
function tokenInHeadNoScript(p, token) {
|
|
const errCode =
|
|
token.type === Tokenizer.EOF_TOKEN ? ERR.openElementsLeftAfterEof : ERR.disallowedContentInNoscriptInHead;
|
|
|
|
p._err(errCode);
|
|
p.openElements.pop();
|
|
p.insertionMode = IN_HEAD_MODE;
|
|
p._processToken(token);
|
|
}
|
|
|
|
// The "after head" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagAfterHead(p, token) {
|
|
const 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._err(ERR.abandonedHeadElementChild);
|
|
p.openElements.push(p.headElement);
|
|
startTagInHead(p, token);
|
|
p.openElements.remove(p.headElement);
|
|
} else if (tn === $.HEAD) {
|
|
p._err(ERR.misplacedStartTagForHeadElement);
|
|
} else {
|
|
tokenAfterHead(p, token);
|
|
}
|
|
}
|
|
|
|
function endTagAfterHead(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.BODY || tn === $.HTML || tn === $.BR) {
|
|
tokenAfterHead(p, token);
|
|
} else if (tn === $.TEMPLATE) {
|
|
endTagInHead(p, token);
|
|
} else {
|
|
p._err(ERR.endTagWithoutMatchingOpenElement);
|
|
}
|
|
}
|
|
|
|
function tokenAfterHead(p, token) {
|
|
p._insertFakeElement($.BODY);
|
|
p.insertionMode = IN_BODY_MODE;
|
|
p._processToken(token);
|
|
}
|
|
|
|
// 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) {
|
|
const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement();
|
|
|
|
if (bodyElement && p.openElements.tmplCount === 0) {
|
|
p.framesetOk = false;
|
|
p.treeAdapter.adoptAttributes(bodyElement, token.attrs);
|
|
}
|
|
}
|
|
|
|
function framesetStartTagInBody(p, token) {
|
|
const 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();
|
|
}
|
|
|
|
const 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) {
|
|
const 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;
|
|
|
|
const tn = token.tagName;
|
|
|
|
for (let i = p.openElements.stackTop; i >= 0; i--) {
|
|
const element = p.openElements.items[i];
|
|
const elementTn = p.treeAdapter.getTagName(element);
|
|
let closeTn = null;
|
|
|
|
if (tn === $.LI && elementTn === $.LI) {
|
|
closeTn = $.LI;
|
|
} else if ((tn === $.DD || tn === $.DT) && (elementTn === $.DD || elementTn === $.DT)) {
|
|
closeTn = elementTn;
|
|
}
|
|
|
|
if (closeTn) {
|
|
p.openElements.generateImpliedEndTagsWithExclusion(closeTn);
|
|
p.openElements.popUntilTagNamePopped(closeTn);
|
|
break;
|
|
}
|
|
|
|
if (elementTn !== $.ADDRESS && elementTn !== $.DIV && elementTn !== $.P && p._isSpecialElement(element)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (p.openElements.hasInButtonScope($.P)) {
|
|
p._closePElement();
|
|
}
|
|
|
|
p._insertElement(token, NS.HTML);
|
|
}
|
|
|
|
function plaintextStartTagInBody(p, token) {
|
|
if (p.openElements.hasInButtonScope($.P)) {
|
|
p._closePElement();
|
|
}
|
|
|
|
p._insertElement(token, NS.HTML);
|
|
p.tokenizer.state = Tokenizer.MODE.PLAINTEXT;
|
|
}
|
|
|
|
function buttonStartTagInBody(p, token) {
|
|
if (p.openElements.hasInScope($.BUTTON)) {
|
|
p.openElements.generateImpliedEndTags();
|
|
p.openElements.popUntilTagNamePopped($.BUTTON);
|
|
}
|
|
|
|
p._reconstructActiveFormattingElements();
|
|
p._insertElement(token, NS.HTML);
|
|
p.framesetOk = false;
|
|
}
|
|
|
|
function aStartTagInBody(p, token) {
|
|
const activeElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName($.A);
|
|
|
|
if (activeElementEntry) {
|
|
callAdoptionAgency(p, token);
|
|
p.openElements.remove(activeElementEntry.element);
|
|
p.activeFormattingElements.removeEntry(activeElementEntry);
|
|
}
|
|
|
|
p._reconstructActiveFormattingElements();
|
|
p._insertElement(token, NS.HTML);
|
|
p.activeFormattingElements.pushElement(p.openElements.current, token);
|
|
}
|
|
|
|
function bStartTagInBody(p, token) {
|
|
p._reconstructActiveFormattingElements();
|
|
p._insertElement(token, NS.HTML);
|
|
p.activeFormattingElements.pushElement(p.openElements.current, token);
|
|
}
|
|
|
|
function nobrStartTagInBody(p, token) {
|
|
p._reconstructActiveFormattingElements();
|
|
|
|
if (p.openElements.hasInScope($.NOBR)) {
|
|
callAdoptionAgency(p, token);
|
|
p._reconstructActiveFormattingElements();
|
|
}
|
|
|
|
p._insertElement(token, NS.HTML);
|
|
p.activeFormattingElements.pushElement(p.openElements.current, token);
|
|
}
|
|
|
|
function appletStartTagInBody(p, token) {
|
|
p._reconstructActiveFormattingElements();
|
|
p._insertElement(token, NS.HTML);
|
|
p.activeFormattingElements.insertMarker();
|
|
p.framesetOk = false;
|
|
}
|
|
|
|
function tableStartTagInBody(p, token) {
|
|
if (
|
|
p.treeAdapter.getDocumentMode(p.document) !== HTML.DOCUMENT_MODE.QUIRKS &&
|
|
p.openElements.hasInButtonScope($.P)
|
|
) {
|
|
p._closePElement();
|
|
}
|
|
|
|
p._insertElement(token, NS.HTML);
|
|
p.framesetOk = false;
|
|
p.insertionMode = IN_TABLE_MODE;
|
|
}
|
|
|
|
function areaStartTagInBody(p, token) {
|
|
p._reconstructActiveFormattingElements();
|
|
p._appendElement(token, NS.HTML);
|
|
p.framesetOk = false;
|
|
token.ackSelfClosing = true;
|
|
}
|
|
|
|
function inputStartTagInBody(p, token) {
|
|
p._reconstructActiveFormattingElements();
|
|
p._appendElement(token, NS.HTML);
|
|
|
|
const inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE);
|
|
|
|
if (!inputType || inputType.toLowerCase() !== HIDDEN_INPUT_TYPE) {
|
|
p.framesetOk = false;
|
|
}
|
|
|
|
token.ackSelfClosing = true;
|
|
}
|
|
|
|
function paramStartTagInBody(p, token) {
|
|
p._appendElement(token, NS.HTML);
|
|
token.ackSelfClosing = true;
|
|
}
|
|
|
|
function hrStartTagInBody(p, token) {
|
|
if (p.openElements.hasInButtonScope($.P)) {
|
|
p._closePElement();
|
|
}
|
|
|
|
p._appendElement(token, NS.HTML);
|
|
p.framesetOk = false;
|
|
p.ackSelfClosing = true;
|
|
}
|
|
|
|
function imageStartTagInBody(p, token) {
|
|
token.tagName = $.IMG;
|
|
areaStartTagInBody(p, token);
|
|
}
|
|
|
|
function textareaStartTagInBody(p, token) {
|
|
p._insertElement(token, NS.HTML);
|
|
//NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move
|
|
//on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.)
|
|
p.skipNextNewLine = true;
|
|
p.tokenizer.state = Tokenizer.MODE.RCDATA;
|
|
p.originalInsertionMode = p.insertionMode;
|
|
p.framesetOk = false;
|
|
p.insertionMode = TEXT_MODE;
|
|
}
|
|
|
|
function xmpStartTagInBody(p, token) {
|
|
if (p.openElements.hasInButtonScope($.P)) {
|
|
p._closePElement();
|
|
}
|
|
|
|
p._reconstructActiveFormattingElements();
|
|
p.framesetOk = false;
|
|
p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
|
|
}
|
|
|
|
function iframeStartTagInBody(p, token) {
|
|
p.framesetOk = false;
|
|
p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
|
|
}
|
|
|
|
//NOTE: here we assume that we always act as an user agent with enabled plugins, so we parse
|
|
//<noembed> as a rawtext.
|
|
function noembedStartTagInBody(p, token) {
|
|
p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
|
|
}
|
|
|
|
function selectStartTagInBody(p, token) {
|
|
p._reconstructActiveFormattingElements();
|
|
p._insertElement(token, NS.HTML);
|
|
p.framesetOk = false;
|
|
|
|
if (
|
|
p.insertionMode === IN_TABLE_MODE ||
|
|
p.insertionMode === IN_CAPTION_MODE ||
|
|
p.insertionMode === IN_TABLE_BODY_MODE ||
|
|
p.insertionMode === IN_ROW_MODE ||
|
|
p.insertionMode === IN_CELL_MODE
|
|
) {
|
|
p.insertionMode = IN_SELECT_IN_TABLE_MODE;
|
|
} else {
|
|
p.insertionMode = IN_SELECT_MODE;
|
|
}
|
|
}
|
|
|
|
function optgroupStartTagInBody(p, token) {
|
|
if (p.openElements.currentTagName === $.OPTION) {
|
|
p.openElements.pop();
|
|
}
|
|
|
|
p._reconstructActiveFormattingElements();
|
|
p._insertElement(token, NS.HTML);
|
|
}
|
|
|
|
function rbStartTagInBody(p, token) {
|
|
if (p.openElements.hasInScope($.RUBY)) {
|
|
p.openElements.generateImpliedEndTags();
|
|
}
|
|
|
|
p._insertElement(token, NS.HTML);
|
|
}
|
|
|
|
function rtStartTagInBody(p, token) {
|
|
if (p.openElements.hasInScope($.RUBY)) {
|
|
p.openElements.generateImpliedEndTagsWithExclusion($.RTC);
|
|
}
|
|
|
|
p._insertElement(token, NS.HTML);
|
|
}
|
|
|
|
function menuStartTagInBody(p, token) {
|
|
if (p.openElements.hasInButtonScope($.P)) {
|
|
p._closePElement();
|
|
}
|
|
|
|
p._insertElement(token, NS.HTML);
|
|
}
|
|
|
|
function mathStartTagInBody(p, token) {
|
|
p._reconstructActiveFormattingElements();
|
|
|
|
foreignContent.adjustTokenMathMLAttrs(token);
|
|
foreignContent.adjustTokenXMLAttrs(token);
|
|
|
|
if (token.selfClosing) {
|
|
p._appendElement(token, NS.MATHML);
|
|
} else {
|
|
p._insertElement(token, NS.MATHML);
|
|
}
|
|
|
|
token.ackSelfClosing = true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
token.ackSelfClosing = true;
|
|
}
|
|
|
|
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) {
|
|
const tn = token.tagName;
|
|
|
|
switch (tn.length) {
|
|
case 1:
|
|
if (tn === $.I || tn === $.S || tn === $.B || tn === $.U) {
|
|
bStartTagInBody(p, token);
|
|
} else if (tn === $.P) {
|
|
addressStartTagInBody(p, token);
|
|
} else if (tn === $.A) {
|
|
aStartTagInBody(p, token);
|
|
} else {
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
|
|
break;
|
|
|
|
case 2:
|
|
if (tn === $.DL || tn === $.OL || tn === $.UL) {
|
|
addressStartTagInBody(p, token);
|
|
} else if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) {
|
|
numberedHeaderStartTagInBody(p, token);
|
|
} else if (tn === $.LI || tn === $.DD || tn === $.DT) {
|
|
listItemStartTagInBody(p, token);
|
|
} else if (tn === $.EM || tn === $.TT) {
|
|
bStartTagInBody(p, token);
|
|
} else if (tn === $.BR) {
|
|
areaStartTagInBody(p, token);
|
|
} else if (tn === $.HR) {
|
|
hrStartTagInBody(p, token);
|
|
} else if (tn === $.RB) {
|
|
rbStartTagInBody(p, token);
|
|
} else if (tn === $.RT || tn === $.RP) {
|
|
rtStartTagInBody(p, token);
|
|
} else if (tn !== $.TH && tn !== $.TD && tn !== $.TR) {
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
if (tn === $.DIV || tn === $.DIR || tn === $.NAV) {
|
|
addressStartTagInBody(p, token);
|
|
} else if (tn === $.PRE) {
|
|
preStartTagInBody(p, token);
|
|
} else if (tn === $.BIG) {
|
|
bStartTagInBody(p, token);
|
|
} else if (tn === $.IMG || tn === $.WBR) {
|
|
areaStartTagInBody(p, token);
|
|
} else if (tn === $.XMP) {
|
|
xmpStartTagInBody(p, token);
|
|
} else if (tn === $.SVG) {
|
|
svgStartTagInBody(p, token);
|
|
} else if (tn === $.RTC) {
|
|
rbStartTagInBody(p, token);
|
|
} else if (tn !== $.COL) {
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
if (tn === $.HTML) {
|
|
htmlStartTagInBody(p, token);
|
|
} else if (tn === $.BASE || tn === $.LINK || tn === $.META) {
|
|
startTagInHead(p, token);
|
|
} else if (tn === $.BODY) {
|
|
bodyStartTagInBody(p, token);
|
|
} else if (tn === $.MAIN || 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 === $.MENU) {
|
|
menuStartTagInBody(p, token);
|
|
} else if (tn !== $.HEAD) {
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
|
|
break;
|
|
|
|
case 5:
|
|
if (tn === $.STYLE || tn === $.TITLE) {
|
|
startTagInHead(p, token);
|
|
} else if (tn === $.ASIDE) {
|
|
addressStartTagInBody(p, token);
|
|
} else if (tn === $.SMALL) {
|
|
bStartTagInBody(p, token);
|
|
} else if (tn === $.TABLE) {
|
|
tableStartTagInBody(p, token);
|
|
} else if (tn === $.EMBED) {
|
|
areaStartTagInBody(p, token);
|
|
} else if (tn === $.INPUT) {
|
|
inputStartTagInBody(p, token);
|
|
} else if (tn === $.PARAM || tn === $.TRACK) {
|
|
paramStartTagInBody(p, token);
|
|
} else if (tn === $.IMAGE) {
|
|
imageStartTagInBody(p, token);
|
|
} else if (tn !== $.FRAME && tn !== $.TBODY && tn !== $.TFOOT && tn !== $.THEAD) {
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
|
|
break;
|
|
|
|
case 6:
|
|
if (tn === $.SCRIPT) {
|
|
startTagInHead(p, token);
|
|
} else if (
|
|
tn === $.CENTER ||
|
|
tn === $.FIGURE ||
|
|
tn === $.FOOTER ||
|
|
tn === $.HEADER ||
|
|
tn === $.HGROUP ||
|
|
tn === $.DIALOG
|
|
) {
|
|
addressStartTagInBody(p, token);
|
|
} else if (tn === $.BUTTON) {
|
|
buttonStartTagInBody(p, token);
|
|
} else if (tn === $.STRIKE || tn === $.STRONG) {
|
|
bStartTagInBody(p, token);
|
|
} else if (tn === $.APPLET || tn === $.OBJECT) {
|
|
appletStartTagInBody(p, token);
|
|
} else if (tn === $.KEYGEN) {
|
|
areaStartTagInBody(p, token);
|
|
} else if (tn === $.SOURCE) {
|
|
paramStartTagInBody(p, token);
|
|
} else if (tn === $.IFRAME) {
|
|
iframeStartTagInBody(p, token);
|
|
} else if (tn === $.SELECT) {
|
|
selectStartTagInBody(p, token);
|
|
} else if (tn === $.OPTION) {
|
|
optgroupStartTagInBody(p, token);
|
|
} else {
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
|
|
break;
|
|
|
|
case 7:
|
|
if (tn === $.BGSOUND) {
|
|
startTagInHead(p, token);
|
|
} else if (
|
|
tn === $.DETAILS ||
|
|
tn === $.ADDRESS ||
|
|
tn === $.ARTICLE ||
|
|
tn === $.SECTION ||
|
|
tn === $.SUMMARY
|
|
) {
|
|
addressStartTagInBody(p, token);
|
|
} else if (tn === $.LISTING) {
|
|
preStartTagInBody(p, token);
|
|
} else if (tn === $.MARQUEE) {
|
|
appletStartTagInBody(p, token);
|
|
} else if (tn === $.NOEMBED) {
|
|
noembedStartTagInBody(p, token);
|
|
} else if (tn !== $.CAPTION) {
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
|
|
break;
|
|
|
|
case 8:
|
|
if (tn === $.BASEFONT) {
|
|
startTagInHead(p, token);
|
|
} else if (tn === $.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) {
|
|
if (p.options.scriptingEnabled) {
|
|
noembedStartTagInBody(p, token);
|
|
} else {
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
} else if (tn === $.OPTGROUP) {
|
|
optgroupStartTagInBody(p, token);
|
|
} else if (tn !== $.COLGROUP) {
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
|
|
break;
|
|
|
|
case 9:
|
|
if (tn === $.PLAINTEXT) {
|
|
plaintextStartTagInBody(p, token);
|
|
} else {
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
|
|
break;
|
|
|
|
case 10:
|
|
if (tn === $.BLOCKQUOTE || tn === $.FIGCAPTION) {
|
|
addressStartTagInBody(p, token);
|
|
} else {
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
genericStartTagInBody(p, token);
|
|
}
|
|
}
|
|
|
|
function bodyEndTagInBody(p) {
|
|
if (p.openElements.hasInScope($.BODY)) {
|
|
p.insertionMode = AFTER_BODY_MODE;
|
|
}
|
|
}
|
|
|
|
function htmlEndTagInBody(p, token) {
|
|
if (p.openElements.hasInScope($.BODY)) {
|
|
p.insertionMode = AFTER_BODY_MODE;
|
|
p._processToken(token);
|
|
}
|
|
}
|
|
|
|
function addressEndTagInBody(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (p.openElements.hasInScope(tn)) {
|
|
p.openElements.generateImpliedEndTags();
|
|
p.openElements.popUntilTagNamePopped(tn);
|
|
}
|
|
}
|
|
|
|
function formEndTagInBody(p) {
|
|
const inTemplate = p.openElements.tmplCount > 0;
|
|
const formElement = p.formElement;
|
|
|
|
if (!inTemplate) {
|
|
p.formElement = null;
|
|
}
|
|
|
|
if ((formElement || inTemplate) && p.openElements.hasInScope($.FORM)) {
|
|
p.openElements.generateImpliedEndTags();
|
|
|
|
if (inTemplate) {
|
|
p.openElements.popUntilTagNamePopped($.FORM);
|
|
} else {
|
|
p.openElements.remove(formElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
function pEndTagInBody(p) {
|
|
if (!p.openElements.hasInButtonScope($.P)) {
|
|
p._insertFakeElement($.P);
|
|
}
|
|
|
|
p._closePElement();
|
|
}
|
|
|
|
function liEndTagInBody(p) {
|
|
if (p.openElements.hasInListItemScope($.LI)) {
|
|
p.openElements.generateImpliedEndTagsWithExclusion($.LI);
|
|
p.openElements.popUntilTagNamePopped($.LI);
|
|
}
|
|
}
|
|
|
|
function ddEndTagInBody(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (p.openElements.hasInScope(tn)) {
|
|
p.openElements.generateImpliedEndTagsWithExclusion(tn);
|
|
p.openElements.popUntilTagNamePopped(tn);
|
|
}
|
|
}
|
|
|
|
function numberedHeaderEndTagInBody(p) {
|
|
if (p.openElements.hasNumberedHeaderInScope()) {
|
|
p.openElements.generateImpliedEndTags();
|
|
p.openElements.popUntilNumberedHeaderPopped();
|
|
}
|
|
}
|
|
|
|
function appletEndTagInBody(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (p.openElements.hasInScope(tn)) {
|
|
p.openElements.generateImpliedEndTags();
|
|
p.openElements.popUntilTagNamePopped(tn);
|
|
p.activeFormattingElements.clearToLastMarker();
|
|
}
|
|
}
|
|
|
|
function brEndTagInBody(p) {
|
|
p._reconstructActiveFormattingElements();
|
|
p._insertFakeElement($.BR);
|
|
p.openElements.pop();
|
|
p.framesetOk = false;
|
|
}
|
|
|
|
function genericEndTagInBody(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
for (let i = p.openElements.stackTop; i > 0; i--) {
|
|
const 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) {
|
|
const 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 || tn === $.PRE) {
|
|
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 ||
|
|
tn === $.DIALOG
|
|
) {
|
|
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 ||
|
|
tn === $.LISTING
|
|
) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
// The "text" insertion mode
|
|
//------------------------------------------------------------------
|
|
function endTagInText(p, token) {
|
|
if (token.tagName === $.SCRIPT) {
|
|
p.pendingScript = p.openElements.current;
|
|
}
|
|
|
|
p.openElements.pop();
|
|
p.insertionMode = p.originalInsertionMode;
|
|
}
|
|
|
|
function eofInText(p, token) {
|
|
p._err(ERR.eofInElementThatCanContainOnlyText);
|
|
p.openElements.pop();
|
|
p.insertionMode = p.originalInsertionMode;
|
|
p._processToken(token);
|
|
}
|
|
|
|
// The "in table" insertion mode
|
|
//------------------------------------------------------------------
|
|
function characterInTable(p, token) {
|
|
const curTn = p.openElements.currentTagName;
|
|
|
|
if (curTn === $.TABLE || curTn === $.TBODY || curTn === $.TFOOT || curTn === $.THEAD || curTn === $.TR) {
|
|
p.pendingCharacterTokens = [];
|
|
p.hasNonWhitespacePendingCharacterToken = false;
|
|
p.originalInsertionMode = p.insertionMode;
|
|
p.insertionMode = IN_TABLE_TEXT_MODE;
|
|
p._processToken(token);
|
|
} else {
|
|
tokenInTable(p, token);
|
|
}
|
|
}
|
|
|
|
function captionStartTagInTable(p, token) {
|
|
p.openElements.clearBackToTableContext();
|
|
p.activeFormattingElements.insertMarker();
|
|
p._insertElement(token, NS.HTML);
|
|
p.insertionMode = IN_CAPTION_MODE;
|
|
}
|
|
|
|
function colgroupStartTagInTable(p, token) {
|
|
p.openElements.clearBackToTableContext();
|
|
p._insertElement(token, NS.HTML);
|
|
p.insertionMode = IN_COLUMN_GROUP_MODE;
|
|
}
|
|
|
|
function colStartTagInTable(p, token) {
|
|
p.openElements.clearBackToTableContext();
|
|
p._insertFakeElement($.COLGROUP);
|
|
p.insertionMode = IN_COLUMN_GROUP_MODE;
|
|
p._processToken(token);
|
|
}
|
|
|
|
function tbodyStartTagInTable(p, token) {
|
|
p.openElements.clearBackToTableContext();
|
|
p._insertElement(token, NS.HTML);
|
|
p.insertionMode = IN_TABLE_BODY_MODE;
|
|
}
|
|
|
|
function tdStartTagInTable(p, token) {
|
|
p.openElements.clearBackToTableContext();
|
|
p._insertFakeElement($.TBODY);
|
|
p.insertionMode = IN_TABLE_BODY_MODE;
|
|
p._processToken(token);
|
|
}
|
|
|
|
function tableStartTagInTable(p, token) {
|
|
if (p.openElements.hasInTableScope($.TABLE)) {
|
|
p.openElements.popUntilTagNamePopped($.TABLE);
|
|
p._resetInsertionMode();
|
|
p._processToken(token);
|
|
}
|
|
}
|
|
|
|
function inputStartTagInTable(p, token) {
|
|
const inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE);
|
|
|
|
if (inputType && inputType.toLowerCase() === HIDDEN_INPUT_TYPE) {
|
|
p._appendElement(token, NS.HTML);
|
|
} else {
|
|
tokenInTable(p, token);
|
|
}
|
|
|
|
token.ackSelfClosing = true;
|
|
}
|
|
|
|
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) {
|
|
const 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) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.TABLE) {
|
|
if (p.openElements.hasInTableScope($.TABLE)) {
|
|
p.openElements.popUntilTagNamePopped($.TABLE);
|
|
p._resetInsertionMode();
|
|
}
|
|
} else if (tn === $.TEMPLATE) {
|
|
endTagInHead(p, token);
|
|
} else if (
|
|
tn !== $.BODY &&
|
|
tn !== $.CAPTION &&
|
|
tn !== $.COL &&
|
|
tn !== $.COLGROUP &&
|
|
tn !== $.HTML &&
|
|
tn !== $.TBODY &&
|
|
tn !== $.TD &&
|
|
tn !== $.TFOOT &&
|
|
tn !== $.TH &&
|
|
tn !== $.THEAD &&
|
|
tn !== $.TR
|
|
) {
|
|
tokenInTable(p, token);
|
|
}
|
|
}
|
|
|
|
function tokenInTable(p, token) {
|
|
const savedFosterParentingState = p.fosterParentingEnabled;
|
|
|
|
p.fosterParentingEnabled = true;
|
|
p._processTokenInBodyMode(token);
|
|
p.fosterParentingEnabled = savedFosterParentingState;
|
|
}
|
|
|
|
// 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) {
|
|
let i = 0;
|
|
|
|
if (p.hasNonWhitespacePendingCharacterToken) {
|
|
for (; i < p.pendingCharacterTokens.length; i++) {
|
|
tokenInTable(p, p.pendingCharacterTokens[i]);
|
|
}
|
|
} else {
|
|
for (; i < p.pendingCharacterTokens.length; i++) {
|
|
p._insertCharacters(p.pendingCharacterTokens[i]);
|
|
}
|
|
}
|
|
|
|
p.insertionMode = p.originalInsertionMode;
|
|
p._processToken(token);
|
|
}
|
|
|
|
// The "in caption" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagInCaption(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (
|
|
tn === $.CAPTION ||
|
|
tn === $.COL ||
|
|
tn === $.COLGROUP ||
|
|
tn === $.TBODY ||
|
|
tn === $.TD ||
|
|
tn === $.TFOOT ||
|
|
tn === $.TH ||
|
|
tn === $.THEAD ||
|
|
tn === $.TR
|
|
) {
|
|
if (p.openElements.hasInTableScope($.CAPTION)) {
|
|
p.openElements.generateImpliedEndTags();
|
|
p.openElements.popUntilTagNamePopped($.CAPTION);
|
|
p.activeFormattingElements.clearToLastMarker();
|
|
p.insertionMode = IN_TABLE_MODE;
|
|
p._processToken(token);
|
|
}
|
|
} else {
|
|
startTagInBody(p, token);
|
|
}
|
|
}
|
|
|
|
function endTagInCaption(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.CAPTION || tn === $.TABLE) {
|
|
if (p.openElements.hasInTableScope($.CAPTION)) {
|
|
p.openElements.generateImpliedEndTags();
|
|
p.openElements.popUntilTagNamePopped($.CAPTION);
|
|
p.activeFormattingElements.clearToLastMarker();
|
|
p.insertionMode = IN_TABLE_MODE;
|
|
|
|
if (tn === $.TABLE) {
|
|
p._processToken(token);
|
|
}
|
|
}
|
|
} else if (
|
|
tn !== $.BODY &&
|
|
tn !== $.COL &&
|
|
tn !== $.COLGROUP &&
|
|
tn !== $.HTML &&
|
|
tn !== $.TBODY &&
|
|
tn !== $.TD &&
|
|
tn !== $.TFOOT &&
|
|
tn !== $.TH &&
|
|
tn !== $.THEAD &&
|
|
tn !== $.TR
|
|
) {
|
|
endTagInBody(p, token);
|
|
}
|
|
}
|
|
|
|
// The "in column group" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagInColumnGroup(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.HTML) {
|
|
startTagInBody(p, token);
|
|
} else if (tn === $.COL) {
|
|
p._appendElement(token, NS.HTML);
|
|
token.ackSelfClosing = true;
|
|
} else if (tn === $.TEMPLATE) {
|
|
startTagInHead(p, token);
|
|
} else {
|
|
tokenInColumnGroup(p, token);
|
|
}
|
|
}
|
|
|
|
function endTagInColumnGroup(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.COLGROUP) {
|
|
if (p.openElements.currentTagName === $.COLGROUP) {
|
|
p.openElements.pop();
|
|
p.insertionMode = IN_TABLE_MODE;
|
|
}
|
|
} else if (tn === $.TEMPLATE) {
|
|
endTagInHead(p, token);
|
|
} else if (tn !== $.COL) {
|
|
tokenInColumnGroup(p, token);
|
|
}
|
|
}
|
|
|
|
function tokenInColumnGroup(p, token) {
|
|
if (p.openElements.currentTagName === $.COLGROUP) {
|
|
p.openElements.pop();
|
|
p.insertionMode = IN_TABLE_MODE;
|
|
p._processToken(token);
|
|
}
|
|
}
|
|
|
|
// The "in table body" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagInTableBody(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.TR) {
|
|
p.openElements.clearBackToTableBodyContext();
|
|
p._insertElement(token, NS.HTML);
|
|
p.insertionMode = IN_ROW_MODE;
|
|
} else if (tn === $.TH || tn === $.TD) {
|
|
p.openElements.clearBackToTableBodyContext();
|
|
p._insertFakeElement($.TR);
|
|
p.insertionMode = IN_ROW_MODE;
|
|
p._processToken(token);
|
|
} else if (
|
|
tn === $.CAPTION ||
|
|
tn === $.COL ||
|
|
tn === $.COLGROUP ||
|
|
tn === $.TBODY ||
|
|
tn === $.TFOOT ||
|
|
tn === $.THEAD
|
|
) {
|
|
if (p.openElements.hasTableBodyContextInTableScope()) {
|
|
p.openElements.clearBackToTableBodyContext();
|
|
p.openElements.pop();
|
|
p.insertionMode = IN_TABLE_MODE;
|
|
p._processToken(token);
|
|
}
|
|
} else {
|
|
startTagInTable(p, token);
|
|
}
|
|
}
|
|
|
|
function endTagInTableBody(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
|
|
if (p.openElements.hasInTableScope(tn)) {
|
|
p.openElements.clearBackToTableBodyContext();
|
|
p.openElements.pop();
|
|
p.insertionMode = IN_TABLE_MODE;
|
|
}
|
|
} else if (tn === $.TABLE) {
|
|
if (p.openElements.hasTableBodyContextInTableScope()) {
|
|
p.openElements.clearBackToTableBodyContext();
|
|
p.openElements.pop();
|
|
p.insertionMode = IN_TABLE_MODE;
|
|
p._processToken(token);
|
|
}
|
|
} else if (
|
|
(tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP) ||
|
|
(tn !== $.HTML && tn !== $.TD && tn !== $.TH && tn !== $.TR)
|
|
) {
|
|
endTagInTable(p, token);
|
|
}
|
|
}
|
|
|
|
// The "in row" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagInRow(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.TH || tn === $.TD) {
|
|
p.openElements.clearBackToTableRowContext();
|
|
p._insertElement(token, NS.HTML);
|
|
p.insertionMode = IN_CELL_MODE;
|
|
p.activeFormattingElements.insertMarker();
|
|
} else if (
|
|
tn === $.CAPTION ||
|
|
tn === $.COL ||
|
|
tn === $.COLGROUP ||
|
|
tn === $.TBODY ||
|
|
tn === $.TFOOT ||
|
|
tn === $.THEAD ||
|
|
tn === $.TR
|
|
) {
|
|
if (p.openElements.hasInTableScope($.TR)) {
|
|
p.openElements.clearBackToTableRowContext();
|
|
p.openElements.pop();
|
|
p.insertionMode = IN_TABLE_BODY_MODE;
|
|
p._processToken(token);
|
|
}
|
|
} else {
|
|
startTagInTable(p, token);
|
|
}
|
|
}
|
|
|
|
function endTagInRow(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.TR) {
|
|
if (p.openElements.hasInTableScope($.TR)) {
|
|
p.openElements.clearBackToTableRowContext();
|
|
p.openElements.pop();
|
|
p.insertionMode = IN_TABLE_BODY_MODE;
|
|
}
|
|
} else if (tn === $.TABLE) {
|
|
if (p.openElements.hasInTableScope($.TR)) {
|
|
p.openElements.clearBackToTableRowContext();
|
|
p.openElements.pop();
|
|
p.insertionMode = IN_TABLE_BODY_MODE;
|
|
p._processToken(token);
|
|
}
|
|
} else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
|
|
if (p.openElements.hasInTableScope(tn) || p.openElements.hasInTableScope($.TR)) {
|
|
p.openElements.clearBackToTableRowContext();
|
|
p.openElements.pop();
|
|
p.insertionMode = IN_TABLE_BODY_MODE;
|
|
p._processToken(token);
|
|
}
|
|
} else if (
|
|
(tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP) ||
|
|
(tn !== $.HTML && tn !== $.TD && tn !== $.TH)
|
|
) {
|
|
endTagInTable(p, token);
|
|
}
|
|
}
|
|
|
|
// The "in cell" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagInCell(p, token) {
|
|
const 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) {
|
|
const 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);
|
|
}
|
|
}
|
|
|
|
// The "in select" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagInSelect(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.HTML) {
|
|
startTagInBody(p, token);
|
|
} else if (tn === $.OPTION) {
|
|
if (p.openElements.currentTagName === $.OPTION) {
|
|
p.openElements.pop();
|
|
}
|
|
|
|
p._insertElement(token, NS.HTML);
|
|
} else if (tn === $.OPTGROUP) {
|
|
if (p.openElements.currentTagName === $.OPTION) {
|
|
p.openElements.pop();
|
|
}
|
|
|
|
if (p.openElements.currentTagName === $.OPTGROUP) {
|
|
p.openElements.pop();
|
|
}
|
|
|
|
p._insertElement(token, NS.HTML);
|
|
} else if (tn === $.INPUT || tn === $.KEYGEN || tn === $.TEXTAREA || tn === $.SELECT) {
|
|
if (p.openElements.hasInSelectScope($.SELECT)) {
|
|
p.openElements.popUntilTagNamePopped($.SELECT);
|
|
p._resetInsertionMode();
|
|
|
|
if (tn !== $.SELECT) {
|
|
p._processToken(token);
|
|
}
|
|
}
|
|
} else if (tn === $.SCRIPT || tn === $.TEMPLATE) {
|
|
startTagInHead(p, token);
|
|
}
|
|
}
|
|
|
|
function endTagInSelect(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.OPTGROUP) {
|
|
const prevOpenElement = p.openElements.items[p.openElements.stackTop - 1];
|
|
const prevOpenElementTn = prevOpenElement && p.treeAdapter.getTagName(prevOpenElement);
|
|
|
|
if (p.openElements.currentTagName === $.OPTION && prevOpenElementTn === $.OPTGROUP) {
|
|
p.openElements.pop();
|
|
}
|
|
|
|
if (p.openElements.currentTagName === $.OPTGROUP) {
|
|
p.openElements.pop();
|
|
}
|
|
} else if (tn === $.OPTION) {
|
|
if (p.openElements.currentTagName === $.OPTION) {
|
|
p.openElements.pop();
|
|
}
|
|
} else if (tn === $.SELECT && p.openElements.hasInSelectScope($.SELECT)) {
|
|
p.openElements.popUntilTagNamePopped($.SELECT);
|
|
p._resetInsertionMode();
|
|
} else if (tn === $.TEMPLATE) {
|
|
endTagInHead(p, token);
|
|
}
|
|
}
|
|
|
|
//12.2.5.4.17 The "in select in table" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagInSelectInTable(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (
|
|
tn === $.CAPTION ||
|
|
tn === $.TABLE ||
|
|
tn === $.TBODY ||
|
|
tn === $.TFOOT ||
|
|
tn === $.THEAD ||
|
|
tn === $.TR ||
|
|
tn === $.TD ||
|
|
tn === $.TH
|
|
) {
|
|
p.openElements.popUntilTagNamePopped($.SELECT);
|
|
p._resetInsertionMode();
|
|
p._processToken(token);
|
|
} else {
|
|
startTagInSelect(p, token);
|
|
}
|
|
}
|
|
|
|
function endTagInSelectInTable(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (
|
|
tn === $.CAPTION ||
|
|
tn === $.TABLE ||
|
|
tn === $.TBODY ||
|
|
tn === $.TFOOT ||
|
|
tn === $.THEAD ||
|
|
tn === $.TR ||
|
|
tn === $.TD ||
|
|
tn === $.TH
|
|
) {
|
|
if (p.openElements.hasInTableScope(tn)) {
|
|
p.openElements.popUntilTagNamePopped($.SELECT);
|
|
p._resetInsertionMode();
|
|
p._processToken(token);
|
|
}
|
|
} else {
|
|
endTagInSelect(p, token);
|
|
}
|
|
}
|
|
|
|
// The "in template" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagInTemplate(p, token) {
|
|
const 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 {
|
|
const newInsertionMode = TEMPLATE_INSERTION_MODE_SWITCH_MAP[tn] || IN_BODY_MODE;
|
|
|
|
p._popTmplInsertionMode();
|
|
p._pushTmplInsertionMode(newInsertionMode);
|
|
p.insertionMode = newInsertionMode;
|
|
p._processToken(token);
|
|
}
|
|
}
|
|
|
|
function endTagInTemplate(p, token) {
|
|
if (token.tagName === $.TEMPLATE) {
|
|
endTagInHead(p, token);
|
|
}
|
|
}
|
|
|
|
function eofInTemplate(p, token) {
|
|
if (p.openElements.tmplCount > 0) {
|
|
p.openElements.popUntilTagNamePopped($.TEMPLATE);
|
|
p.activeFormattingElements.clearToLastMarker();
|
|
p._popTmplInsertionMode();
|
|
p._resetInsertionMode();
|
|
p._processToken(token);
|
|
} else {
|
|
p.stopped = true;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// The "in frameset" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagInFrameset(p, token) {
|
|
const 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);
|
|
token.ackSelfClosing = true;
|
|
} 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The "after frameset" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagAfterFrameset(p, token) {
|
|
const 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;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// The "after after frameset" insertion mode
|
|
//------------------------------------------------------------------
|
|
function startTagAfterAfterFrameset(p, token) {
|
|
const tn = token.tagName;
|
|
|
|
if (tn === $.HTML) {
|
|
startTagInBody(p, token);
|
|
} else if (tn === $.NOFRAMES) {
|
|
startTagInHead(p, token);
|
|
}
|
|
}
|
|
|
|
// The rules for parsing tokens in foreign content
|
|
//------------------------------------------------------------------
|
|
function nullCharacterInForeignContent(p, token) {
|
|
token.chars = unicode.REPLACEMENT_CHARACTER;
|
|
p._insertCharacters(token);
|
|
}
|
|
|
|
function characterInForeignContent(p, token) {
|
|
p._insertCharacters(token);
|
|
p.framesetOk = false;
|
|
}
|
|
|
|
function startTagInForeignContent(p, token) {
|
|
if (foreignContent.causesExit(token) && !p.fragmentContext) {
|
|
while (
|
|
p.treeAdapter.getNamespaceURI(p.openElements.current) !== NS.HTML &&
|
|
!p._isIntegrationPoint(p.openElements.current)
|
|
) {
|
|
p.openElements.pop();
|
|
}
|
|
|
|
p._processToken(token);
|
|
} else {
|
|
const current = p._getAdjustedCurrentElement();
|
|
const 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);
|
|
}
|
|
|
|
token.ackSelfClosing = true;
|
|
}
|
|
}
|
|
|
|
function endTagInForeignContent(p, token) {
|
|
for (let i = p.openElements.stackTop; i > 0; i--) {
|
|
const 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;
|
|
}
|
|
}
|
|
}
|