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