parse5-ns
Version:
My non-standard fork of parse5, the HTML parser and serializer.
1,533 lines (1,277 loc) • 99.5 kB
JavaScript
import { Tokenizer } from '../tokenizer/index.js';
import { OpenElementStack } from './open-element-stack.js';
import { FormattingElementList } from './formatting-element-list.js';
import { LocationInfoParserMixin } from '../extensions/location-info/parser-mixin.js';
import { ErrorReportingParserMixin } from '../extensions/error-reporting/parser-mixin.js';
import { Mixin } from '../utils/mixin.js';
import * as defaultTreeAdapter from '../tree-adapters/default.js';
import { mergeOptions } from '../utils/merge-options.js';
import * as doctype from '../common/doctype.js';
import * as foreignContent from '../common/foreign-content.js';
import { ERR } from '../common/error-codes.js';
import * as unicode from '../common/unicode.js';
import * as HTML from '../common/html.js';
//Aliases
const $ = HTML.TAG_NAMES;
const NS = HTML.NAMESPACES;
const { ATTRS } = HTML;
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
export default 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);
switch (tn) {
case $.TITLE:
case $.TEXTAREA: {
this.tokenizer.state = Tokenizer.MODE.RCDATA;
break;
}
case $.STYLE:
case $.XMP:
case $.IFRAME:
case $.NOEMBED:
case $.NOFRAMES:
case $.NOSCRIPT: {
this.tokenizer.state = Tokenizer.MODE.RAWTEXT;
break;
}
case $.SCRIPT: {
this.tokenizer.state = Tokenizer.MODE.SCRIPT_DATA;
break;
}
case $.PLAINTEXT: {
this.tokenizer.state = Tokenizer.MODE.PLAINTEXT;
break;
}
default:
// Do nothing
}
}
}
//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) {
switch (token.type) {
case Tokenizer.CHARACTER_TOKEN: {
characterInForeignContent(this, token);
break;
}
case Tokenizer.NULL_CHARACTER_TOKEN: {
nullCharacterInForeignContent(this, token);
break;
}
case Tokenizer.WHITESPACE_CHARACTER_TOKEN: {
insertCharacters(this, token);
break;
}
case Tokenizer.COMMENT_TOKEN: {
appendComment(this, token);
break;
}
case Tokenizer.START_TAG_TOKEN: {
startTagInForeignContent(this, token);
break;
}
case Tokenizer.END_TAG_TOKEN: {
endTagInForeignContent(this, token);
break;
}
default:
// Do nothing
}
}
_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;
// eslint-disable-next-line unicorn/prefer-switch
} 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];
}
}
//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;
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;
switch (tn) {
case $.HTML: {
startTagInBody(p, token);
break;
}
case $.BASE:
case $.BASEFONT:
case $.BGSOUND:
case $.LINK:
case $.META: {
p._appendElement(token, NS.HTML);
token.ackSelfClosing = true;
break;
}
case $.TITLE: {
p._switchToTextParsing(token, Tokenizer.MODE.RCDATA);
break;
}
case $.NOSCRIPT: {
if (p.options.scriptingEnabled) {
p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
} else {
p._insertElement(token, NS.HTML);
p.insertionMode = IN_HEAD_NO_SCRIPT_MODE;
}
break;
}
case $.NOFRAMES:
case $.STYLE: {
p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
break;
}
case $.SCRIPT: {
p._switchToTextParsing(token, Tokenizer.MODE.SCRIPT_DATA);
break;
}
case $.TEMPLATE: {
p._insertTemplate(token, NS.HTML);
p.activeFormattingElements.insertMarker();
p.framesetOk = false;
p.insertionMode = IN_TEMPLATE_MODE;
p._pushTmplInsertionMode(IN_TEMPLATE_MODE);
break;
}
case $.HEAD: {
p._err(ERR.misplacedStartTagForHeadElement);
break;
}
default: {
tokenInHead(p, token);
}
}
}
function endTagInHead(p, token) {
const tn = token.tagName;
switch (tn) {
case $.HEAD: {
p.openElements.pop();
p.insertionMode = AFTER_HEAD_MODE;
break;
}
case $.BODY:
case $.BR:
case $.HTML: {
tokenInHead(p, token);
break;
}
case $.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);
}
break;
}
default: {
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;
switch (tn) {
case $.HTML: {
startTagInBody(p, token);
break;
}
case $.BASEFONT:
case $.BGSOUND:
case $.HEAD:
case $.LINK:
case $.META:
case $.NOFRAMES:
case $.STYLE: {
startTagInHead(p, token);
break;
}
case $.NOSCRIPT: {
p._err(ERR.nestedNoscriptInHead);
break;
}
default: {
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;
switch (tn) {
case $.HTML: {
startTagInBody(p, token);
break;
}
case $.BODY: {
p._insertElement(token, NS.HTML);
p.framesetOk = false;
p.insertionMode = IN_BODY_MODE;
break;
}
case $.FRAMESET: {
p._insertElement(token, NS.HTML);
p.insertionMode = IN_FRAMESET_MODE;
break;
}
case $.BASE:
case $.BASEFONT:
case $.BGSOUND:
case $.LINK:
case $.META:
case $.NOFRAMES:
case $.SCRIPT:
case $.STYLE:
case $.TEMPLATE:
case $.TITLE: {
p._err(ERR.abandonedHeadElementChild);
p.openElements.push(p.headElement);
startTagInHead(p, token);
p.openElements.remove(p.headElement);
break;
}
case $.HEAD: {
p._err(ERR.misplacedStartTagForHeadElement);
break;
}
default: {
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 &