You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3168 lines
103 KiB
3168 lines
103 KiB
import { Tokenizer, TokenizerMode } from '../tokenizer/index.js'; |
|
import { OpenElementStack } from './open-element-stack.js'; |
|
import { FormattingElementList, EntryType } from './formatting-element-list.js'; |
|
import { defaultTreeAdapter } from '../tree-adapters/default.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 { TAG_ID as $, TAG_NAMES as TN, NS, ATTRS, SPECIAL_ELEMENTS, DOCUMENT_MODE, isNumberedHeader, getTagID, } from '../common/html.js'; |
|
import { TokenType, getTokenAttr, } from '../common/token.js'; |
|
//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 |
|
var InsertionMode; |
|
(function (InsertionMode) { |
|
InsertionMode[InsertionMode["INITIAL"] = 0] = "INITIAL"; |
|
InsertionMode[InsertionMode["BEFORE_HTML"] = 1] = "BEFORE_HTML"; |
|
InsertionMode[InsertionMode["BEFORE_HEAD"] = 2] = "BEFORE_HEAD"; |
|
InsertionMode[InsertionMode["IN_HEAD"] = 3] = "IN_HEAD"; |
|
InsertionMode[InsertionMode["IN_HEAD_NO_SCRIPT"] = 4] = "IN_HEAD_NO_SCRIPT"; |
|
InsertionMode[InsertionMode["AFTER_HEAD"] = 5] = "AFTER_HEAD"; |
|
InsertionMode[InsertionMode["IN_BODY"] = 6] = "IN_BODY"; |
|
InsertionMode[InsertionMode["TEXT"] = 7] = "TEXT"; |
|
InsertionMode[InsertionMode["IN_TABLE"] = 8] = "IN_TABLE"; |
|
InsertionMode[InsertionMode["IN_TABLE_TEXT"] = 9] = "IN_TABLE_TEXT"; |
|
InsertionMode[InsertionMode["IN_CAPTION"] = 10] = "IN_CAPTION"; |
|
InsertionMode[InsertionMode["IN_COLUMN_GROUP"] = 11] = "IN_COLUMN_GROUP"; |
|
InsertionMode[InsertionMode["IN_TABLE_BODY"] = 12] = "IN_TABLE_BODY"; |
|
InsertionMode[InsertionMode["IN_ROW"] = 13] = "IN_ROW"; |
|
InsertionMode[InsertionMode["IN_CELL"] = 14] = "IN_CELL"; |
|
InsertionMode[InsertionMode["IN_SELECT"] = 15] = "IN_SELECT"; |
|
InsertionMode[InsertionMode["IN_SELECT_IN_TABLE"] = 16] = "IN_SELECT_IN_TABLE"; |
|
InsertionMode[InsertionMode["IN_TEMPLATE"] = 17] = "IN_TEMPLATE"; |
|
InsertionMode[InsertionMode["AFTER_BODY"] = 18] = "AFTER_BODY"; |
|
InsertionMode[InsertionMode["IN_FRAMESET"] = 19] = "IN_FRAMESET"; |
|
InsertionMode[InsertionMode["AFTER_FRAMESET"] = 20] = "AFTER_FRAMESET"; |
|
InsertionMode[InsertionMode["AFTER_AFTER_BODY"] = 21] = "AFTER_AFTER_BODY"; |
|
InsertionMode[InsertionMode["AFTER_AFTER_FRAMESET"] = 22] = "AFTER_AFTER_FRAMESET"; |
|
})(InsertionMode || (InsertionMode = {})); |
|
const BASE_LOC = { |
|
startLine: -1, |
|
startCol: -1, |
|
startOffset: -1, |
|
endLine: -1, |
|
endCol: -1, |
|
endOffset: -1, |
|
}; |
|
const TABLE_STRUCTURE_TAGS = new Set([$.TABLE, $.TBODY, $.TFOOT, $.THEAD, $.TR]); |
|
const defaultParserOptions = { |
|
scriptingEnabled: true, |
|
sourceCodeLocationInfo: false, |
|
treeAdapter: defaultTreeAdapter, |
|
onParseError: null, |
|
}; |
|
//Parser |
|
export class Parser { |
|
constructor(options, document, fragmentContext = null, scriptHandler = null) { |
|
this.fragmentContext = fragmentContext; |
|
this.scriptHandler = scriptHandler; |
|
this.currentToken = null; |
|
this.stopped = false; |
|
this.insertionMode = InsertionMode.INITIAL; |
|
this.originalInsertionMode = InsertionMode.INITIAL; |
|
this.headElement = null; |
|
this.formElement = null; |
|
/** Indicates that the current node is not an element in the HTML namespace */ |
|
this.currentNotInHTML = false; |
|
/** |
|
* The template insertion mode stack is maintained from the left. |
|
* Ie. the topmost element will always have index 0. |
|
*/ |
|
this.tmplInsertionModeStack = []; |
|
this.pendingCharacterTokens = []; |
|
this.hasNonWhitespacePendingCharacterToken = false; |
|
this.framesetOk = true; |
|
this.skipNextNewLine = false; |
|
this.fosterParentingEnabled = false; |
|
this.options = { |
|
...defaultParserOptions, |
|
...options, |
|
}; |
|
this.treeAdapter = this.options.treeAdapter; |
|
this.onParseError = this.options.onParseError; |
|
// Always enable location info if we report parse errors. |
|
if (this.onParseError) { |
|
this.options.sourceCodeLocationInfo = true; |
|
} |
|
this.document = document !== null && document !== void 0 ? document : this.treeAdapter.createDocument(); |
|
this.tokenizer = new Tokenizer(this.options, this); |
|
this.activeFormattingElements = new FormattingElementList(this.treeAdapter); |
|
this.fragmentContextID = fragmentContext ? getTagID(this.treeAdapter.getTagName(fragmentContext)) : $.UNKNOWN; |
|
this._setContextModes(fragmentContext !== null && fragmentContext !== void 0 ? fragmentContext : this.document, this.fragmentContextID); |
|
this.openElements = new OpenElementStack(this.document, this.treeAdapter, this); |
|
} |
|
// API |
|
static parse(html, options) { |
|
const parser = new this(options); |
|
parser.tokenizer.write(html, true); |
|
return parser.document; |
|
} |
|
static getFragmentParser(fragmentContext, options) { |
|
const opts = { |
|
...defaultParserOptions, |
|
...options, |
|
}; |
|
//NOTE: use a <template> element as the fragment context if no context element was provided, |
|
//so we will parse in a "forgiving" manner |
|
fragmentContext !== null && fragmentContext !== void 0 ? fragmentContext : (fragmentContext = opts.treeAdapter.createElement(TN.TEMPLATE, NS.HTML, [])); |
|
//NOTE: create a fake element which will be used as the `document` for fragment parsing. |
|
//This is important for jsdom, where a new `document` cannot be created. This led to |
|
//fragment parsing messing with the main `document`. |
|
const documentMock = opts.treeAdapter.createElement('documentmock', NS.HTML, []); |
|
const parser = new this(opts, documentMock, fragmentContext); |
|
if (parser.fragmentContextID === $.TEMPLATE) { |
|
parser.tmplInsertionModeStack.unshift(InsertionMode.IN_TEMPLATE); |
|
} |
|
parser._initTokenizerForFragmentParsing(); |
|
parser._insertFakeRootElement(); |
|
parser._resetInsertionMode(); |
|
parser._findFormInFragmentContext(); |
|
return parser; |
|
} |
|
getFragment() { |
|
const rootElement = this.treeAdapter.getFirstChild(this.document); |
|
const fragment = this.treeAdapter.createDocumentFragment(); |
|
this._adoptNodes(rootElement, fragment); |
|
return fragment; |
|
} |
|
//Errors |
|
_err(token, code, beforeToken) { |
|
var _a; |
|
if (!this.onParseError) |
|
return; |
|
const loc = (_a = token.location) !== null && _a !== void 0 ? _a : BASE_LOC; |
|
const err = { |
|
code, |
|
startLine: loc.startLine, |
|
startCol: loc.startCol, |
|
startOffset: loc.startOffset, |
|
endLine: beforeToken ? loc.startLine : loc.endLine, |
|
endCol: beforeToken ? loc.startCol : loc.endCol, |
|
endOffset: beforeToken ? loc.startOffset : loc.endOffset, |
|
}; |
|
this.onParseError(err); |
|
} |
|
//Stack events |
|
onItemPush(node, tid, isTop) { |
|
var _a, _b; |
|
(_b = (_a = this.treeAdapter).onItemPush) === null || _b === void 0 ? void 0 : _b.call(_a, node); |
|
if (isTop && this.openElements.stackTop > 0) |
|
this._setContextModes(node, tid); |
|
} |
|
onItemPop(node, isTop) { |
|
var _a, _b; |
|
if (this.options.sourceCodeLocationInfo) { |
|
this._setEndLocation(node, this.currentToken); |
|
} |
|
(_b = (_a = this.treeAdapter).onItemPop) === null || _b === void 0 ? void 0 : _b.call(_a, node, this.openElements.current); |
|
if (isTop) { |
|
let current; |
|
let currentTagId; |
|
if (this.openElements.stackTop === 0 && this.fragmentContext) { |
|
current = this.fragmentContext; |
|
currentTagId = this.fragmentContextID; |
|
} |
|
else { |
|
({ current, currentTagId } = this.openElements); |
|
} |
|
this._setContextModes(current, currentTagId); |
|
} |
|
} |
|
_setContextModes(current, tid) { |
|
const isHTML = current === this.document || this.treeAdapter.getNamespaceURI(current) === NS.HTML; |
|
this.currentNotInHTML = !isHTML; |
|
this.tokenizer.inForeignNode = !isHTML && !this._isIntegrationPoint(tid, current); |
|
} |
|
_switchToTextParsing(currentToken, nextTokenizerState) { |
|
this._insertElement(currentToken, NS.HTML); |
|
this.tokenizer.state = nextTokenizerState; |
|
this.originalInsertionMode = this.insertionMode; |
|
this.insertionMode = InsertionMode.TEXT; |
|
} |
|
switchToPlaintextParsing() { |
|
this.insertionMode = InsertionMode.TEXT; |
|
this.originalInsertionMode = InsertionMode.IN_BODY; |
|
this.tokenizer.state = TokenizerMode.PLAINTEXT; |
|
} |
|
//Fragment parsing |
|
_getAdjustedCurrentElement() { |
|
return this.openElements.stackTop === 0 && this.fragmentContext |
|
? this.fragmentContext |
|
: this.openElements.current; |
|
} |
|
_findFormInFragmentContext() { |
|
let node = this.fragmentContext; |
|
while (node) { |
|
if (this.treeAdapter.getTagName(node) === TN.FORM) { |
|
this.formElement = node; |
|
break; |
|
} |
|
node = this.treeAdapter.getParentNode(node); |
|
} |
|
} |
|
_initTokenizerForFragmentParsing() { |
|
if (!this.fragmentContext || this.treeAdapter.getNamespaceURI(this.fragmentContext) !== NS.HTML) { |
|
return; |
|
} |
|
switch (this.fragmentContextID) { |
|
case $.TITLE: |
|
case $.TEXTAREA: { |
|
this.tokenizer.state = TokenizerMode.RCDATA; |
|
break; |
|
} |
|
case $.STYLE: |
|
case $.XMP: |
|
case $.IFRAME: |
|
case $.NOEMBED: |
|
case $.NOFRAMES: |
|
case $.NOSCRIPT: { |
|
this.tokenizer.state = TokenizerMode.RAWTEXT; |
|
break; |
|
} |
|
case $.SCRIPT: { |
|
this.tokenizer.state = TokenizerMode.SCRIPT_DATA; |
|
break; |
|
} |
|
case $.PLAINTEXT: { |
|
this.tokenizer.state = TokenizerMode.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); |
|
if (token.location) { |
|
const documentChildren = this.treeAdapter.getChildNodes(this.document); |
|
const docTypeNode = documentChildren.find((node) => this.treeAdapter.isDocumentTypeNode(node)); |
|
if (docTypeNode) { |
|
this.treeAdapter.setNodeSourceCodeLocation(docTypeNode, token.location); |
|
} |
|
} |
|
} |
|
_attachElementToTree(element, location) { |
|
if (this.options.sourceCodeLocationInfo) { |
|
const loc = location && { |
|
...location, |
|
startTag: location, |
|
}; |
|
this.treeAdapter.setNodeSourceCodeLocation(element, loc); |
|
} |
|
if (this._shouldFosterParentOnInsertion()) { |
|
this._fosterParentElement(element); |
|
} |
|
else { |
|
const parent = this.openElements.currentTmplContentOrNode; |
|
this.treeAdapter.appendChild(parent, element); |
|
} |
|
} |
|
_appendElement(token, namespaceURI) { |
|
const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs); |
|
this._attachElementToTree(element, token.location); |
|
} |
|
_insertElement(token, namespaceURI) { |
|
const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs); |
|
this._attachElementToTree(element, token.location); |
|
this.openElements.push(element, token.tagID); |
|
} |
|
_insertFakeElement(tagName, tagID) { |
|
const element = this.treeAdapter.createElement(tagName, NS.HTML, []); |
|
this._attachElementToTree(element, null); |
|
this.openElements.push(element, tagID); |
|
} |
|
_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, token.location); |
|
this.openElements.push(tmpl, token.tagID); |
|
if (this.options.sourceCodeLocationInfo) |
|
this.treeAdapter.setNodeSourceCodeLocation(content, null); |
|
} |
|
_insertFakeRootElement() { |
|
const element = this.treeAdapter.createElement(TN.HTML, NS.HTML, []); |
|
if (this.options.sourceCodeLocationInfo) |
|
this.treeAdapter.setNodeSourceCodeLocation(element, null); |
|
this.treeAdapter.appendChild(this.openElements.current, element); |
|
this.openElements.push(element, $.HTML); |
|
} |
|
_appendCommentNode(token, parent) { |
|
const commentNode = this.treeAdapter.createCommentNode(token.data); |
|
this.treeAdapter.appendChild(parent, commentNode); |
|
if (this.options.sourceCodeLocationInfo) { |
|
this.treeAdapter.setNodeSourceCodeLocation(commentNode, token.location); |
|
} |
|
} |
|
_insertCharacters(token) { |
|
let parent; |
|
let beforeElement; |
|
if (this._shouldFosterParentOnInsertion()) { |
|
({ parent, beforeElement } = this._findFosterParentingLocation()); |
|
if (beforeElement) { |
|
this.treeAdapter.insertTextBefore(parent, token.chars, beforeElement); |
|
} |
|
else { |
|
this.treeAdapter.insertText(parent, token.chars); |
|
} |
|
} |
|
else { |
|
parent = this.openElements.currentTmplContentOrNode; |
|
this.treeAdapter.insertText(parent, token.chars); |
|
} |
|
if (!token.location) |
|
return; |
|
const siblings = this.treeAdapter.getChildNodes(parent); |
|
const textNodeIdx = beforeElement ? siblings.lastIndexOf(beforeElement) : siblings.length; |
|
const textNode = siblings[textNodeIdx - 1]; |
|
//NOTE: if we have a location assigned by another token, then just update the end position |
|
const tnLoc = this.treeAdapter.getNodeSourceCodeLocation(textNode); |
|
if (tnLoc) { |
|
const { endLine, endCol, endOffset } = token.location; |
|
this.treeAdapter.updateNodeSourceCodeLocation(textNode, { endLine, endCol, endOffset }); |
|
} |
|
else if (this.options.sourceCodeLocationInfo) { |
|
this.treeAdapter.setNodeSourceCodeLocation(textNode, token.location); |
|
} |
|
} |
|
_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); |
|
} |
|
} |
|
_setEndLocation(element, closingToken) { |
|
if (this.treeAdapter.getNodeSourceCodeLocation(element) && closingToken.location) { |
|
const ctLoc = closingToken.location; |
|
const tn = this.treeAdapter.getTagName(element); |
|
const endLoc = |
|
// NOTE: For cases like <p> <p> </p> - First 'p' closes without a closing |
|
// tag and for cases like <td> <p> </td> - 'p' closes without a closing tag. |
|
closingToken.type === TokenType.END_TAG && tn === closingToken.tagName |
|
? { |
|
endTag: { ...ctLoc }, |
|
endLine: ctLoc.endLine, |
|
endCol: ctLoc.endCol, |
|
endOffset: ctLoc.endOffset, |
|
} |
|
: { |
|
endLine: ctLoc.startLine, |
|
endCol: ctLoc.startCol, |
|
endOffset: ctLoc.startOffset, |
|
}; |
|
this.treeAdapter.updateNodeSourceCodeLocation(element, endLoc); |
|
} |
|
} |
|
//Token processing |
|
shouldProcessStartTagTokenInForeignContent(token) { |
|
// Check that neither current === document, or ns === NS.HTML |
|
if (!this.currentNotInHTML) |
|
return false; |
|
let current; |
|
let currentTagId; |
|
if (this.openElements.stackTop === 0 && this.fragmentContext) { |
|
current = this.fragmentContext; |
|
currentTagId = this.fragmentContextID; |
|
} |
|
else { |
|
({ current, currentTagId } = this.openElements); |
|
} |
|
if (token.tagID === $.SVG && |
|
this.treeAdapter.getTagName(current) === TN.ANNOTATION_XML && |
|
this.treeAdapter.getNamespaceURI(current) === NS.MATHML) { |
|
return false; |
|
} |
|
return ( |
|
// Check that `current` is not an integration point for HTML or MathML elements. |
|
this.tokenizer.inForeignNode || |
|
// If it _is_ an integration point, then we might have to check that it is not an HTML |
|
// integration point. |
|
((token.tagID === $.MGLYPH || token.tagID === $.MALIGNMARK) && |
|
!this._isIntegrationPoint(currentTagId, current, NS.HTML))); |
|
} |
|
_processToken(token) { |
|
switch (token.type) { |
|
case TokenType.CHARACTER: { |
|
this.onCharacter(token); |
|
break; |
|
} |
|
case TokenType.NULL_CHARACTER: { |
|
this.onNullCharacter(token); |
|
break; |
|
} |
|
case TokenType.COMMENT: { |
|
this.onComment(token); |
|
break; |
|
} |
|
case TokenType.DOCTYPE: { |
|
this.onDoctype(token); |
|
break; |
|
} |
|
case TokenType.START_TAG: { |
|
this._processStartTag(token); |
|
break; |
|
} |
|
case TokenType.END_TAG: { |
|
this.onEndTag(token); |
|
break; |
|
} |
|
case TokenType.EOF: { |
|
this.onEof(token); |
|
break; |
|
} |
|
case TokenType.WHITESPACE_CHARACTER: { |
|
this.onWhitespaceCharacter(token); |
|
break; |
|
} |
|
} |
|
} |
|
//Integration points |
|
_isIntegrationPoint(tid, element, foreignNS) { |
|
const ns = this.treeAdapter.getNamespaceURI(element); |
|
const attrs = this.treeAdapter.getAttrList(element); |
|
return foreignContent.isIntegrationPoint(tid, ns, attrs, foreignNS); |
|
} |
|
//Active formatting elements reconstruction |
|
_reconstructActiveFormattingElements() { |
|
const listLength = this.activeFormattingElements.entries.length; |
|
if (listLength) { |
|
const endIndex = this.activeFormattingElements.entries.findIndex((entry) => entry.type === EntryType.Marker || this.openElements.contains(entry.element)); |
|
const unopenIdx = endIndex < 0 ? listLength - 1 : endIndex - 1; |
|
for (let i = unopenIdx; i >= 0; i--) { |
|
const 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 = InsertionMode.IN_ROW; |
|
} |
|
_closePElement() { |
|
this.openElements.generateImpliedEndTagsWithExclusion($.P); |
|
this.openElements.popUntilTagNamePopped($.P); |
|
} |
|
//Insertion modes |
|
_resetInsertionMode() { |
|
for (let i = this.openElements.stackTop; i >= 0; i--) { |
|
//Insertion mode reset map |
|
switch (i === 0 && this.fragmentContext ? this.fragmentContextID : this.openElements.tagIDs[i]) { |
|
case $.TR: { |
|
this.insertionMode = InsertionMode.IN_ROW; |
|
return; |
|
} |
|
case $.TBODY: |
|
case $.THEAD: |
|
case $.TFOOT: { |
|
this.insertionMode = InsertionMode.IN_TABLE_BODY; |
|
return; |
|
} |
|
case $.CAPTION: { |
|
this.insertionMode = InsertionMode.IN_CAPTION; |
|
return; |
|
} |
|
case $.COLGROUP: { |
|
this.insertionMode = InsertionMode.IN_COLUMN_GROUP; |
|
return; |
|
} |
|
case $.TABLE: { |
|
this.insertionMode = InsertionMode.IN_TABLE; |
|
return; |
|
} |
|
case $.BODY: { |
|
this.insertionMode = InsertionMode.IN_BODY; |
|
return; |
|
} |
|
case $.FRAMESET: { |
|
this.insertionMode = InsertionMode.IN_FRAMESET; |
|
return; |
|
} |
|
case $.SELECT: { |
|
this._resetInsertionModeForSelect(i); |
|
return; |
|
} |
|
case $.TEMPLATE: { |
|
this.insertionMode = this.tmplInsertionModeStack[0]; |
|
return; |
|
} |
|
case $.HTML: { |
|
this.insertionMode = this.headElement ? InsertionMode.AFTER_HEAD : InsertionMode.BEFORE_HEAD; |
|
return; |
|
} |
|
case $.TD: |
|
case $.TH: { |
|
if (i > 0) { |
|
this.insertionMode = InsertionMode.IN_CELL; |
|
return; |
|
} |
|
break; |
|
} |
|
case $.HEAD: { |
|
if (i > 0) { |
|
this.insertionMode = InsertionMode.IN_HEAD; |
|
return; |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
this.insertionMode = InsertionMode.IN_BODY; |
|
} |
|
_resetInsertionModeForSelect(selectIdx) { |
|
if (selectIdx > 0) { |
|
for (let i = selectIdx - 1; i > 0; i--) { |
|
const tn = this.openElements.tagIDs[i]; |
|
if (tn === $.TEMPLATE) { |
|
break; |
|
} |
|
else if (tn === $.TABLE) { |
|
this.insertionMode = InsertionMode.IN_SELECT_IN_TABLE; |
|
return; |
|
} |
|
} |
|
} |
|
this.insertionMode = InsertionMode.IN_SELECT; |
|
} |
|
//Foster parenting |
|
_isElementCausesFosterParenting(tn) { |
|
return TABLE_STRUCTURE_TAGS.has(tn); |
|
} |
|
_shouldFosterParentOnInsertion() { |
|
return this.fosterParentingEnabled && this._isElementCausesFosterParenting(this.openElements.currentTagId); |
|
} |
|
_findFosterParentingLocation() { |
|
for (let i = this.openElements.stackTop; i >= 0; i--) { |
|
const openElement = this.openElements.items[i]; |
|
switch (this.openElements.tagIDs[i]) { |
|
case $.TEMPLATE: { |
|
if (this.treeAdapter.getNamespaceURI(openElement) === NS.HTML) { |
|
return { parent: this.treeAdapter.getTemplateContent(openElement), beforeElement: null }; |
|
} |
|
break; |
|
} |
|
case $.TABLE: { |
|
const parent = this.treeAdapter.getParentNode(openElement); |
|
if (parent) { |
|
return { parent, beforeElement: openElement }; |
|
} |
|
return { parent: this.openElements.items[i - 1], beforeElement: null }; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
return { parent: this.openElements.items[0], beforeElement: null }; |
|
} |
|
_fosterParentElement(element) { |
|
const location = this._findFosterParentingLocation(); |
|
if (location.beforeElement) { |
|
this.treeAdapter.insertBefore(location.parent, element, location.beforeElement); |
|
} |
|
else { |
|
this.treeAdapter.appendChild(location.parent, element); |
|
} |
|
} |
|
//Special elements |
|
_isSpecialElement(element, id) { |
|
const ns = this.treeAdapter.getNamespaceURI(element); |
|
return SPECIAL_ELEMENTS[ns].has(id); |
|
} |
|
onCharacter(token) { |
|
this.skipNextNewLine = false; |
|
if (this.tokenizer.inForeignNode) { |
|
characterInForeignContent(this, token); |
|
return; |
|
} |
|
switch (this.insertionMode) { |
|
case InsertionMode.INITIAL: { |
|
tokenInInitialMode(this, token); |
|
break; |
|
} |
|
case InsertionMode.BEFORE_HTML: { |
|
tokenBeforeHtml(this, token); |
|
break; |
|
} |
|
case InsertionMode.BEFORE_HEAD: { |
|
tokenBeforeHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_HEAD: { |
|
tokenInHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_HEAD_NO_SCRIPT: { |
|
tokenInHeadNoScript(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_HEAD: { |
|
tokenAfterHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_BODY: |
|
case InsertionMode.IN_CAPTION: |
|
case InsertionMode.IN_CELL: |
|
case InsertionMode.IN_TEMPLATE: { |
|
characterInBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.TEXT: |
|
case InsertionMode.IN_SELECT: |
|
case InsertionMode.IN_SELECT_IN_TABLE: { |
|
this._insertCharacters(token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE: |
|
case InsertionMode.IN_TABLE_BODY: |
|
case InsertionMode.IN_ROW: { |
|
characterInTable(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE_TEXT: { |
|
characterInTableText(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_COLUMN_GROUP: { |
|
tokenInColumnGroup(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_BODY: { |
|
tokenAfterBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_AFTER_BODY: { |
|
tokenAfterAfterBody(this, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
onNullCharacter(token) { |
|
this.skipNextNewLine = false; |
|
if (this.tokenizer.inForeignNode) { |
|
nullCharacterInForeignContent(this, token); |
|
return; |
|
} |
|
switch (this.insertionMode) { |
|
case InsertionMode.INITIAL: { |
|
tokenInInitialMode(this, token); |
|
break; |
|
} |
|
case InsertionMode.BEFORE_HTML: { |
|
tokenBeforeHtml(this, token); |
|
break; |
|
} |
|
case InsertionMode.BEFORE_HEAD: { |
|
tokenBeforeHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_HEAD: { |
|
tokenInHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_HEAD_NO_SCRIPT: { |
|
tokenInHeadNoScript(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_HEAD: { |
|
tokenAfterHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.TEXT: { |
|
this._insertCharacters(token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE: |
|
case InsertionMode.IN_TABLE_BODY: |
|
case InsertionMode.IN_ROW: { |
|
characterInTable(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_COLUMN_GROUP: { |
|
tokenInColumnGroup(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_BODY: { |
|
tokenAfterBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_AFTER_BODY: { |
|
tokenAfterAfterBody(this, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
onComment(token) { |
|
this.skipNextNewLine = false; |
|
if (this.currentNotInHTML) { |
|
appendComment(this, token); |
|
return; |
|
} |
|
switch (this.insertionMode) { |
|
case InsertionMode.INITIAL: |
|
case InsertionMode.BEFORE_HTML: |
|
case InsertionMode.BEFORE_HEAD: |
|
case InsertionMode.IN_HEAD: |
|
case InsertionMode.IN_HEAD_NO_SCRIPT: |
|
case InsertionMode.AFTER_HEAD: |
|
case InsertionMode.IN_BODY: |
|
case InsertionMode.IN_TABLE: |
|
case InsertionMode.IN_CAPTION: |
|
case InsertionMode.IN_COLUMN_GROUP: |
|
case InsertionMode.IN_TABLE_BODY: |
|
case InsertionMode.IN_ROW: |
|
case InsertionMode.IN_CELL: |
|
case InsertionMode.IN_SELECT: |
|
case InsertionMode.IN_SELECT_IN_TABLE: |
|
case InsertionMode.IN_TEMPLATE: |
|
case InsertionMode.IN_FRAMESET: |
|
case InsertionMode.AFTER_FRAMESET: { |
|
appendComment(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE_TEXT: { |
|
tokenInTableText(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_BODY: { |
|
appendCommentToRootHtmlElement(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_AFTER_BODY: |
|
case InsertionMode.AFTER_AFTER_FRAMESET: { |
|
appendCommentToDocument(this, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
onDoctype(token) { |
|
this.skipNextNewLine = false; |
|
switch (this.insertionMode) { |
|
case InsertionMode.INITIAL: { |
|
doctypeInInitialMode(this, token); |
|
break; |
|
} |
|
case InsertionMode.BEFORE_HEAD: |
|
case InsertionMode.IN_HEAD: |
|
case InsertionMode.IN_HEAD_NO_SCRIPT: |
|
case InsertionMode.AFTER_HEAD: { |
|
this._err(token, ERR.misplacedDoctype); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE_TEXT: { |
|
tokenInTableText(this, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
onStartTag(token) { |
|
this.skipNextNewLine = false; |
|
this.currentToken = token; |
|
this._processStartTag(token); |
|
if (token.selfClosing && !token.ackSelfClosing) { |
|
this._err(token, ERR.nonVoidHtmlElementStartTagWithTrailingSolidus); |
|
} |
|
} |
|
/** |
|
* Processes a given start tag. |
|
* |
|
* `onStartTag` checks if a self-closing tag was recognized. When a token |
|
* is moved inbetween multiple insertion modes, this check for self-closing |
|
* could lead to false positives. To avoid this, `_processStartTag` is used |
|
* for nested calls. |
|
* |
|
* @param token The token to process. |
|
*/ |
|
_processStartTag(token) { |
|
if (this.shouldProcessStartTagTokenInForeignContent(token)) { |
|
startTagInForeignContent(this, token); |
|
} |
|
else { |
|
this._startTagOutsideForeignContent(token); |
|
} |
|
} |
|
_startTagOutsideForeignContent(token) { |
|
switch (this.insertionMode) { |
|
case InsertionMode.INITIAL: { |
|
tokenInInitialMode(this, token); |
|
break; |
|
} |
|
case InsertionMode.BEFORE_HTML: { |
|
startTagBeforeHtml(this, token); |
|
break; |
|
} |
|
case InsertionMode.BEFORE_HEAD: { |
|
startTagBeforeHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_HEAD: { |
|
startTagInHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_HEAD_NO_SCRIPT: { |
|
startTagInHeadNoScript(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_HEAD: { |
|
startTagAfterHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_BODY: { |
|
startTagInBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE: { |
|
startTagInTable(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE_TEXT: { |
|
tokenInTableText(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_CAPTION: { |
|
startTagInCaption(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_COLUMN_GROUP: { |
|
startTagInColumnGroup(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE_BODY: { |
|
startTagInTableBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_ROW: { |
|
startTagInRow(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_CELL: { |
|
startTagInCell(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_SELECT: { |
|
startTagInSelect(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_SELECT_IN_TABLE: { |
|
startTagInSelectInTable(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TEMPLATE: { |
|
startTagInTemplate(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_BODY: { |
|
startTagAfterBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_FRAMESET: { |
|
startTagInFrameset(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_FRAMESET: { |
|
startTagAfterFrameset(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_AFTER_BODY: { |
|
startTagAfterAfterBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_AFTER_FRAMESET: { |
|
startTagAfterAfterFrameset(this, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
onEndTag(token) { |
|
this.skipNextNewLine = false; |
|
this.currentToken = token; |
|
if (this.currentNotInHTML) { |
|
endTagInForeignContent(this, token); |
|
} |
|
else { |
|
this._endTagOutsideForeignContent(token); |
|
} |
|
} |
|
_endTagOutsideForeignContent(token) { |
|
switch (this.insertionMode) { |
|
case InsertionMode.INITIAL: { |
|
tokenInInitialMode(this, token); |
|
break; |
|
} |
|
case InsertionMode.BEFORE_HTML: { |
|
endTagBeforeHtml(this, token); |
|
break; |
|
} |
|
case InsertionMode.BEFORE_HEAD: { |
|
endTagBeforeHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_HEAD: { |
|
endTagInHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_HEAD_NO_SCRIPT: { |
|
endTagInHeadNoScript(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_HEAD: { |
|
endTagAfterHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_BODY: { |
|
endTagInBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.TEXT: { |
|
endTagInText(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE: { |
|
endTagInTable(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE_TEXT: { |
|
tokenInTableText(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_CAPTION: { |
|
endTagInCaption(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_COLUMN_GROUP: { |
|
endTagInColumnGroup(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE_BODY: { |
|
endTagInTableBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_ROW: { |
|
endTagInRow(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_CELL: { |
|
endTagInCell(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_SELECT: { |
|
endTagInSelect(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_SELECT_IN_TABLE: { |
|
endTagInSelectInTable(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TEMPLATE: { |
|
endTagInTemplate(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_BODY: { |
|
endTagAfterBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_FRAMESET: { |
|
endTagInFrameset(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_FRAMESET: { |
|
endTagAfterFrameset(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_AFTER_BODY: { |
|
tokenAfterAfterBody(this, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
onEof(token) { |
|
switch (this.insertionMode) { |
|
case InsertionMode.INITIAL: { |
|
tokenInInitialMode(this, token); |
|
break; |
|
} |
|
case InsertionMode.BEFORE_HTML: { |
|
tokenBeforeHtml(this, token); |
|
break; |
|
} |
|
case InsertionMode.BEFORE_HEAD: { |
|
tokenBeforeHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_HEAD: { |
|
tokenInHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_HEAD_NO_SCRIPT: { |
|
tokenInHeadNoScript(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_HEAD: { |
|
tokenAfterHead(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_BODY: |
|
case InsertionMode.IN_TABLE: |
|
case InsertionMode.IN_CAPTION: |
|
case InsertionMode.IN_COLUMN_GROUP: |
|
case InsertionMode.IN_TABLE_BODY: |
|
case InsertionMode.IN_ROW: |
|
case InsertionMode.IN_CELL: |
|
case InsertionMode.IN_SELECT: |
|
case InsertionMode.IN_SELECT_IN_TABLE: { |
|
eofInBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.TEXT: { |
|
eofInText(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE_TEXT: { |
|
tokenInTableText(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TEMPLATE: { |
|
eofInTemplate(this, token); |
|
break; |
|
} |
|
case InsertionMode.AFTER_BODY: |
|
case InsertionMode.IN_FRAMESET: |
|
case InsertionMode.AFTER_FRAMESET: |
|
case InsertionMode.AFTER_AFTER_BODY: |
|
case InsertionMode.AFTER_AFTER_FRAMESET: { |
|
stopParsing(this, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
onWhitespaceCharacter(token) { |
|
if (this.skipNextNewLine) { |
|
this.skipNextNewLine = false; |
|
if (token.chars.charCodeAt(0) === unicode.CODE_POINTS.LINE_FEED) { |
|
if (token.chars.length === 1) { |
|
return; |
|
} |
|
token.chars = token.chars.substr(1); |
|
} |
|
} |
|
if (this.tokenizer.inForeignNode) { |
|
this._insertCharacters(token); |
|
return; |
|
} |
|
switch (this.insertionMode) { |
|
case InsertionMode.IN_HEAD: |
|
case InsertionMode.IN_HEAD_NO_SCRIPT: |
|
case InsertionMode.AFTER_HEAD: |
|
case InsertionMode.TEXT: |
|
case InsertionMode.IN_COLUMN_GROUP: |
|
case InsertionMode.IN_SELECT: |
|
case InsertionMode.IN_SELECT_IN_TABLE: |
|
case InsertionMode.IN_FRAMESET: |
|
case InsertionMode.AFTER_FRAMESET: { |
|
this._insertCharacters(token); |
|
break; |
|
} |
|
case InsertionMode.IN_BODY: |
|
case InsertionMode.IN_CAPTION: |
|
case InsertionMode.IN_CELL: |
|
case InsertionMode.IN_TEMPLATE: |
|
case InsertionMode.AFTER_BODY: |
|
case InsertionMode.AFTER_AFTER_BODY: |
|
case InsertionMode.AFTER_AFTER_FRAMESET: { |
|
whitespaceCharacterInBody(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE: |
|
case InsertionMode.IN_TABLE_BODY: |
|
case InsertionMode.IN_ROW: { |
|
characterInTable(this, token); |
|
break; |
|
} |
|
case InsertionMode.IN_TABLE_TEXT: { |
|
whitespaceCharacterInTableText(this, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
} |
|
//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.tagID)) { |
|
formattingElementEntry = null; |
|
} |
|
} |
|
else { |
|
genericEndTagInBody(p, token); |
|
} |
|
return formattingElementEntry; |
|
} |
|
//Steps 9 and 10 of the algorithm |
|
function aaObtainFurthestBlock(p, formattingElementEntry) { |
|
let furthestBlock = null; |
|
let idx = p.openElements.stackTop; |
|
for (; idx >= 0; idx--) { |
|
const element = p.openElements.items[idx]; |
|
if (element === formattingElementEntry.element) { |
|
break; |
|
} |
|
if (p._isSpecialElement(element, p.openElements.tagIDs[idx])) { |
|
furthestBlock = element; |
|
} |
|
} |
|
if (!furthestBlock) { |
|
p.openElements.shortenToLength(idx < 0 ? 0 : idx); |
|
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 the 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) { |
|
const tn = p.treeAdapter.getTagName(commonAncestor); |
|
const tid = getTagID(tn); |
|
if (p._isElementCausesFosterParenting(tid)) { |
|
p._fosterParentElement(lastElement); |
|
} |
|
else { |
|
const ns = p.treeAdapter.getNamespaceURI(commonAncestor); |
|
if (tid === $.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, token); |
|
p.activeFormattingElements.removeEntry(formattingElementEntry); |
|
p.openElements.remove(formattingElementEntry.element); |
|
p.openElements.insertAfter(furthestBlock, newElement, token.tagID); |
|
} |
|
//Algorithm entry point |
|
function callAdoptionAgency(p, token) { |
|
for (let i = 0; i < AA_OUTER_LOOP_ITER; i++) { |
|
const formattingElementEntry = aaObtainFormattingElementEntry(p, token); |
|
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); |
|
if (commonAncestor) |
|
aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement); |
|
aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry); |
|
} |
|
} |
|
//Generic token handlers |
|
//------------------------------------------------------------------ |
|
function appendComment(p, token) { |
|
p._appendCommentNode(token, p.openElements.currentTmplContentOrNode); |
|
} |
|
function appendCommentToRootHtmlElement(p, token) { |
|
p._appendCommentNode(token, p.openElements.items[0]); |
|
} |
|
function appendCommentToDocument(p, token) { |
|
p._appendCommentNode(token, p.document); |
|
} |
|
function stopParsing(p, token) { |
|
p.stopped = true; |
|
// NOTE: Set end locations for elements that remain on the open element stack. |
|
if (token.location) { |
|
// NOTE: If we are not in a fragment, `html` and `body` will stay on the stack. |
|
// This is a problem, as we might overwrite their end position here. |
|
const target = p.fragmentContext ? 0 : 2; |
|
for (let i = p.openElements.stackTop; i >= target; i--) { |
|
p._setEndLocation(p.openElements.items[i], token); |
|
} |
|
// Handle `html` and `body` |
|
if (!p.fragmentContext && p.openElements.stackTop >= 0) { |
|
const htmlElement = p.openElements.items[0]; |
|
const htmlLocation = p.treeAdapter.getNodeSourceCodeLocation(htmlElement); |
|
if (htmlLocation && !htmlLocation.endTag) { |
|
p._setEndLocation(htmlElement, token); |
|
if (p.openElements.stackTop >= 1) { |
|
const bodyElement = p.openElements.items[1]; |
|
const bodyLocation = p.treeAdapter.getNodeSourceCodeLocation(bodyElement); |
|
if (bodyLocation && !bodyLocation.endTag) { |
|
p._setEndLocation(bodyElement, token); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
// The "initial" insertion mode |
|
//------------------------------------------------------------------ |
|
function doctypeInInitialMode(p, token) { |
|
p._setDocumentType(token); |
|
const mode = token.forceQuirks ? DOCUMENT_MODE.QUIRKS : doctype.getDocumentMode(token); |
|
if (!doctype.isConforming(token)) { |
|
p._err(token, ERR.nonConformingDoctype); |
|
} |
|
p.treeAdapter.setDocumentMode(p.document, mode); |
|
p.insertionMode = InsertionMode.BEFORE_HTML; |
|
} |
|
function tokenInInitialMode(p, token) { |
|
p._err(token, ERR.missingDoctype, true); |
|
p.treeAdapter.setDocumentMode(p.document, DOCUMENT_MODE.QUIRKS); |
|
p.insertionMode = InsertionMode.BEFORE_HTML; |
|
p._processToken(token); |
|
} |
|
// The "before html" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagBeforeHtml(p, token) { |
|
if (token.tagID === $.HTML) { |
|
p._insertElement(token, NS.HTML); |
|
p.insertionMode = InsertionMode.BEFORE_HEAD; |
|
} |
|
else { |
|
tokenBeforeHtml(p, token); |
|
} |
|
} |
|
function endTagBeforeHtml(p, token) { |
|
const tn = token.tagID; |
|
if (tn === $.HTML || tn === $.HEAD || tn === $.BODY || tn === $.BR) { |
|
tokenBeforeHtml(p, token); |
|
} |
|
} |
|
function tokenBeforeHtml(p, token) { |
|
p._insertFakeRootElement(); |
|
p.insertionMode = InsertionMode.BEFORE_HEAD; |
|
p._processToken(token); |
|
} |
|
// The "before head" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagBeforeHead(p, token) { |
|
switch (token.tagID) { |
|
case $.HTML: { |
|
startTagInBody(p, token); |
|
break; |
|
} |
|
case $.HEAD: { |
|
p._insertElement(token, NS.HTML); |
|
p.headElement = p.openElements.current; |
|
p.insertionMode = InsertionMode.IN_HEAD; |
|
break; |
|
} |
|
default: { |
|
tokenBeforeHead(p, token); |
|
} |
|
} |
|
} |
|
function endTagBeforeHead(p, token) { |
|
const tn = token.tagID; |
|
if (tn === $.HEAD || tn === $.BODY || tn === $.HTML || tn === $.BR) { |
|
tokenBeforeHead(p, token); |
|
} |
|
else { |
|
p._err(token, ERR.endTagWithoutMatchingOpenElement); |
|
} |
|
} |
|
function tokenBeforeHead(p, token) { |
|
p._insertFakeElement(TN.HEAD, $.HEAD); |
|
p.headElement = p.openElements.current; |
|
p.insertionMode = InsertionMode.IN_HEAD; |
|
p._processToken(token); |
|
} |
|
// The "in head" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagInHead(p, token) { |
|
switch (token.tagID) { |
|
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, TokenizerMode.RCDATA); |
|
break; |
|
} |
|
case $.NOSCRIPT: { |
|
if (p.options.scriptingEnabled) { |
|
p._switchToTextParsing(token, TokenizerMode.RAWTEXT); |
|
} |
|
else { |
|
p._insertElement(token, NS.HTML); |
|
p.insertionMode = InsertionMode.IN_HEAD_NO_SCRIPT; |
|
} |
|
break; |
|
} |
|
case $.NOFRAMES: |
|
case $.STYLE: { |
|
p._switchToTextParsing(token, TokenizerMode.RAWTEXT); |
|
break; |
|
} |
|
case $.SCRIPT: { |
|
p._switchToTextParsing(token, TokenizerMode.SCRIPT_DATA); |
|
break; |
|
} |
|
case $.TEMPLATE: { |
|
p._insertTemplate(token); |
|
p.activeFormattingElements.insertMarker(); |
|
p.framesetOk = false; |
|
p.insertionMode = InsertionMode.IN_TEMPLATE; |
|
p.tmplInsertionModeStack.unshift(InsertionMode.IN_TEMPLATE); |
|
break; |
|
} |
|
case $.HEAD: { |
|
p._err(token, ERR.misplacedStartTagForHeadElement); |
|
break; |
|
} |
|
default: { |
|
tokenInHead(p, token); |
|
} |
|
} |
|
} |
|
function endTagInHead(p, token) { |
|
switch (token.tagID) { |
|
case $.HEAD: { |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.AFTER_HEAD; |
|
break; |
|
} |
|
case $.BODY: |
|
case $.BR: |
|
case $.HTML: { |
|
tokenInHead(p, token); |
|
break; |
|
} |
|
case $.TEMPLATE: { |
|
templateEndTagInHead(p, token); |
|
break; |
|
} |
|
default: { |
|
p._err(token, ERR.endTagWithoutMatchingOpenElement); |
|
} |
|
} |
|
} |
|
function templateEndTagInHead(p, token) { |
|
if (p.openElements.tmplCount > 0) { |
|
p.openElements.generateImpliedEndTagsThoroughly(); |
|
if (p.openElements.currentTagId !== $.TEMPLATE) { |
|
p._err(token, ERR.closingOfElementWithOpenChildElements); |
|
} |
|
p.openElements.popUntilTagNamePopped($.TEMPLATE); |
|
p.activeFormattingElements.clearToLastMarker(); |
|
p.tmplInsertionModeStack.shift(); |
|
p._resetInsertionMode(); |
|
} |
|
else { |
|
p._err(token, ERR.endTagWithoutMatchingOpenElement); |
|
} |
|
} |
|
function tokenInHead(p, token) { |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.AFTER_HEAD; |
|
p._processToken(token); |
|
} |
|
// The "in head no script" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagInHeadNoScript(p, token) { |
|
switch (token.tagID) { |
|
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(token, ERR.nestedNoscriptInHead); |
|
break; |
|
} |
|
default: { |
|
tokenInHeadNoScript(p, token); |
|
} |
|
} |
|
} |
|
function endTagInHeadNoScript(p, token) { |
|
switch (token.tagID) { |
|
case $.NOSCRIPT: { |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.IN_HEAD; |
|
break; |
|
} |
|
case $.BR: { |
|
tokenInHeadNoScript(p, token); |
|
break; |
|
} |
|
default: { |
|
p._err(token, ERR.endTagWithoutMatchingOpenElement); |
|
} |
|
} |
|
} |
|
function tokenInHeadNoScript(p, token) { |
|
const errCode = token.type === TokenType.EOF ? ERR.openElementsLeftAfterEof : ERR.disallowedContentInNoscriptInHead; |
|
p._err(token, errCode); |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.IN_HEAD; |
|
p._processToken(token); |
|
} |
|
// The "after head" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagAfterHead(p, token) { |
|
switch (token.tagID) { |
|
case $.HTML: { |
|
startTagInBody(p, token); |
|
break; |
|
} |
|
case $.BODY: { |
|
p._insertElement(token, NS.HTML); |
|
p.framesetOk = false; |
|
p.insertionMode = InsertionMode.IN_BODY; |
|
break; |
|
} |
|
case $.FRAMESET: { |
|
p._insertElement(token, NS.HTML); |
|
p.insertionMode = InsertionMode.IN_FRAMESET; |
|
break; |
|
} |
|
case $.BASE: |
|
case $.BASEFONT: |
|
case $.BGSOUND: |
|
case $.LINK: |
|
case $.META: |
|
case $.NOFRAMES: |
|
case $.SCRIPT: |
|
case $.STYLE: |
|
case $.TEMPLATE: |
|
case $.TITLE: { |
|
p._err(token, ERR.abandonedHeadElementChild); |
|
p.openElements.push(p.headElement, $.HEAD); |
|
startTagInHead(p, token); |
|
p.openElements.remove(p.headElement); |
|
break; |
|
} |
|
case $.HEAD: { |
|
p._err(token, ERR.misplacedStartTagForHeadElement); |
|
break; |
|
} |
|
default: { |
|
tokenAfterHead(p, token); |
|
} |
|
} |
|
} |
|
function endTagAfterHead(p, token) { |
|
switch (token.tagID) { |
|
case $.BODY: |
|
case $.HTML: |
|
case $.BR: { |
|
tokenAfterHead(p, token); |
|
break; |
|
} |
|
case $.TEMPLATE: { |
|
templateEndTagInHead(p, token); |
|
break; |
|
} |
|
default: { |
|
p._err(token, ERR.endTagWithoutMatchingOpenElement); |
|
} |
|
} |
|
} |
|
function tokenAfterHead(p, token) { |
|
p._insertFakeElement(TN.BODY, $.BODY); |
|
p.insertionMode = InsertionMode.IN_BODY; |
|
modeInBody(p, token); |
|
} |
|
// The "in body" insertion mode |
|
//------------------------------------------------------------------ |
|
function modeInBody(p, token) { |
|
switch (token.type) { |
|
case TokenType.CHARACTER: { |
|
characterInBody(p, token); |
|
break; |
|
} |
|
case TokenType.WHITESPACE_CHARACTER: { |
|
whitespaceCharacterInBody(p, token); |
|
break; |
|
} |
|
case TokenType.COMMENT: { |
|
appendComment(p, token); |
|
break; |
|
} |
|
case TokenType.START_TAG: { |
|
startTagInBody(p, token); |
|
break; |
|
} |
|
case TokenType.END_TAG: { |
|
endTagInBody(p, token); |
|
break; |
|
} |
|
case TokenType.EOF: { |
|
eofInBody(p, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
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 = InsertionMode.IN_FRAMESET; |
|
} |
|
} |
|
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(); |
|
} |
|
if (isNumberedHeader(p.openElements.currentTagId)) { |
|
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.tagID; |
|
for (let i = p.openElements.stackTop; i >= 0; i--) { |
|
const elementId = p.openElements.tagIDs[i]; |
|
if ((tn === $.LI && elementId === $.LI) || |
|
((tn === $.DD || tn === $.DT) && (elementId === $.DD || elementId === $.DT))) { |
|
p.openElements.generateImpliedEndTagsWithExclusion(elementId); |
|
p.openElements.popUntilTagNamePopped(elementId); |
|
break; |
|
} |
|
if (elementId !== $.ADDRESS && |
|
elementId !== $.DIV && |
|
elementId !== $.P && |
|
p._isSpecialElement(p.openElements.items[i], elementId)) { |
|
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 = TokenizerMode.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(TN.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) !== DOCUMENT_MODE.QUIRKS && p.openElements.hasInButtonScope($.P)) { |
|
p._closePElement(); |
|
} |
|
p._insertElement(token, NS.HTML); |
|
p.framesetOk = false; |
|
p.insertionMode = InsertionMode.IN_TABLE; |
|
} |
|
function areaStartTagInBody(p, token) { |
|
p._reconstructActiveFormattingElements(); |
|
p._appendElement(token, NS.HTML); |
|
p.framesetOk = false; |
|
token.ackSelfClosing = true; |
|
} |
|
function isHiddenInput(token) { |
|
const inputType = getTokenAttr(token, ATTRS.TYPE); |
|
return inputType != null && inputType.toLowerCase() === HIDDEN_INPUT_TYPE; |
|
} |
|
function inputStartTagInBody(p, token) { |
|
p._reconstructActiveFormattingElements(); |
|
p._appendElement(token, NS.HTML); |
|
if (!isHiddenInput(token)) { |
|
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 = TN.IMG; |
|
token.tagID = $.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 = TokenizerMode.RCDATA; |
|
p.originalInsertionMode = p.insertionMode; |
|
p.framesetOk = false; |
|
p.insertionMode = InsertionMode.TEXT; |
|
} |
|
function xmpStartTagInBody(p, token) { |
|
if (p.openElements.hasInButtonScope($.P)) { |
|
p._closePElement(); |
|
} |
|
p._reconstructActiveFormattingElements(); |
|
p.framesetOk = false; |
|
p._switchToTextParsing(token, TokenizerMode.RAWTEXT); |
|
} |
|
function iframeStartTagInBody(p, token) { |
|
p.framesetOk = false; |
|
p._switchToTextParsing(token, TokenizerMode.RAWTEXT); |
|
} |
|
//NOTE: here we assume that we always act as an user agent with enabled plugins, so we parse |
|
//<noembed> as rawtext. |
|
function noembedStartTagInBody(p, token) { |
|
p._switchToTextParsing(token, TokenizerMode.RAWTEXT); |
|
} |
|
function selectStartTagInBody(p, token) { |
|
p._reconstructActiveFormattingElements(); |
|
p._insertElement(token, NS.HTML); |
|
p.framesetOk = false; |
|
p.insertionMode = |
|
p.insertionMode === InsertionMode.IN_TABLE || |
|
p.insertionMode === InsertionMode.IN_CAPTION || |
|
p.insertionMode === InsertionMode.IN_TABLE_BODY || |
|
p.insertionMode === InsertionMode.IN_ROW || |
|
p.insertionMode === InsertionMode.IN_CELL |
|
? InsertionMode.IN_SELECT_IN_TABLE |
|
: InsertionMode.IN_SELECT; |
|
} |
|
function optgroupStartTagInBody(p, token) { |
|
if (p.openElements.currentTagId === $.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 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); |
|
} |
|
function startTagInBody(p, token) { |
|
switch (token.tagID) { |
|
case $.I: |
|
case $.S: |
|
case $.B: |
|
case $.U: |
|
case $.EM: |
|
case $.TT: |
|
case $.BIG: |
|
case $.CODE: |
|
case $.FONT: |
|
case $.SMALL: |
|
case $.STRIKE: |
|
case $.STRONG: { |
|
bStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.A: { |
|
aStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.H1: |
|
case $.H2: |
|
case $.H3: |
|
case $.H4: |
|
case $.H5: |
|
case $.H6: { |
|
numberedHeaderStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.P: |
|
case $.DL: |
|
case $.OL: |
|
case $.UL: |
|
case $.DIV: |
|
case $.DIR: |
|
case $.NAV: |
|
case $.MAIN: |
|
case $.MENU: |
|
case $.ASIDE: |
|
case $.CENTER: |
|
case $.FIGURE: |
|
case $.FOOTER: |
|
case $.HEADER: |
|
case $.HGROUP: |
|
case $.DIALOG: |
|
case $.DETAILS: |
|
case $.ADDRESS: |
|
case $.ARTICLE: |
|
case $.SECTION: |
|
case $.SUMMARY: |
|
case $.FIELDSET: |
|
case $.BLOCKQUOTE: |
|
case $.FIGCAPTION: { |
|
addressStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.LI: |
|
case $.DD: |
|
case $.DT: { |
|
listItemStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.BR: |
|
case $.IMG: |
|
case $.WBR: |
|
case $.AREA: |
|
case $.EMBED: |
|
case $.KEYGEN: { |
|
areaStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.HR: { |
|
hrStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.RB: |
|
case $.RTC: { |
|
rbStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.RT: |
|
case $.RP: { |
|
rtStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.PRE: |
|
case $.LISTING: { |
|
preStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.XMP: { |
|
xmpStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.SVG: { |
|
svgStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.HTML: { |
|
htmlStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.BASE: |
|
case $.LINK: |
|
case $.META: |
|
case $.STYLE: |
|
case $.TITLE: |
|
case $.SCRIPT: |
|
case $.BGSOUND: |
|
case $.BASEFONT: |
|
case $.TEMPLATE: { |
|
startTagInHead(p, token); |
|
break; |
|
} |
|
case $.BODY: { |
|
bodyStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.FORM: { |
|
formStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.NOBR: { |
|
nobrStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.MATH: { |
|
mathStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.TABLE: { |
|
tableStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.INPUT: { |
|
inputStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.PARAM: |
|
case $.TRACK: |
|
case $.SOURCE: { |
|
paramStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.IMAGE: { |
|
imageStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.BUTTON: { |
|
buttonStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.APPLET: |
|
case $.OBJECT: |
|
case $.MARQUEE: { |
|
appletStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.IFRAME: { |
|
iframeStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.SELECT: { |
|
selectStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.OPTION: |
|
case $.OPTGROUP: { |
|
optgroupStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.NOEMBED: { |
|
noembedStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.FRAMESET: { |
|
framesetStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.TEXTAREA: { |
|
textareaStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.NOSCRIPT: { |
|
if (p.options.scriptingEnabled) { |
|
noembedStartTagInBody(p, token); |
|
} |
|
else { |
|
genericStartTagInBody(p, token); |
|
} |
|
break; |
|
} |
|
case $.PLAINTEXT: { |
|
plaintextStartTagInBody(p, token); |
|
break; |
|
} |
|
case $.COL: |
|
case $.TH: |
|
case $.TD: |
|
case $.TR: |
|
case $.HEAD: |
|
case $.FRAME: |
|
case $.TBODY: |
|
case $.TFOOT: |
|
case $.THEAD: |
|
case $.CAPTION: |
|
case $.COLGROUP: { |
|
// Ignore token |
|
break; |
|
} |
|
default: { |
|
genericStartTagInBody(p, token); |
|
} |
|
} |
|
} |
|
function bodyEndTagInBody(p, token) { |
|
if (p.openElements.hasInScope($.BODY)) { |
|
p.insertionMode = InsertionMode.AFTER_BODY; |
|
//NOTE: <body> is never popped from the stack, so we need to updated |
|
//the end location explicitly. |
|
if (p.options.sourceCodeLocationInfo) { |
|
const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement(); |
|
if (bodyElement) { |
|
p._setEndLocation(bodyElement, token); |
|
} |
|
} |
|
} |
|
} |
|
function htmlEndTagInBody(p, token) { |
|
if (p.openElements.hasInScope($.BODY)) { |
|
p.insertionMode = InsertionMode.AFTER_BODY; |
|
endTagAfterBody(p, token); |
|
} |
|
} |
|
function addressEndTagInBody(p, token) { |
|
const tn = token.tagID; |
|
if (p.openElements.hasInScope(tn)) { |
|
p.openElements.generateImpliedEndTags(); |
|
p.openElements.popUntilTagNamePopped(tn); |
|
} |
|
} |
|
function formEndTagInBody(p) { |
|
const inTemplate = p.openElements.tmplCount > 0; |
|
const { formElement } = p; |
|
if (!inTemplate) { |
|
p.formElement = null; |
|
} |
|
if ((formElement || inTemplate) && p.openElements.hasInScope($.FORM)) { |
|
p.openElements.generateImpliedEndTags(); |
|
if (inTemplate) { |
|
p.openElements.popUntilTagNamePopped($.FORM); |
|
} |
|
else if (formElement) { |
|
p.openElements.remove(formElement); |
|
} |
|
} |
|
} |
|
function pEndTagInBody(p) { |
|
if (!p.openElements.hasInButtonScope($.P)) { |
|
p._insertFakeElement(TN.P, $.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.tagID; |
|
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.tagID; |
|
if (p.openElements.hasInScope(tn)) { |
|
p.openElements.generateImpliedEndTags(); |
|
p.openElements.popUntilTagNamePopped(tn); |
|
p.activeFormattingElements.clearToLastMarker(); |
|
} |
|
} |
|
function brEndTagInBody(p) { |
|
p._reconstructActiveFormattingElements(); |
|
p._insertFakeElement(TN.BR, $.BR); |
|
p.openElements.pop(); |
|
p.framesetOk = false; |
|
} |
|
function genericEndTagInBody(p, token) { |
|
const tn = token.tagName; |
|
const tid = token.tagID; |
|
for (let i = p.openElements.stackTop; i > 0; i--) { |
|
const element = p.openElements.items[i]; |
|
const elementId = p.openElements.tagIDs[i]; |
|
// Compare the tag name here, as the tag might not be a known tag with an ID. |
|
if (tid === elementId && (tid !== $.UNKNOWN || p.treeAdapter.getTagName(element) === tn)) { |
|
p.openElements.generateImpliedEndTagsWithExclusion(tid); |
|
if (p.openElements.stackTop >= i) |
|
p.openElements.shortenToLength(i); |
|
break; |
|
} |
|
if (p._isSpecialElement(element, elementId)) { |
|
break; |
|
} |
|
} |
|
} |
|
function endTagInBody(p, token) { |
|
switch (token.tagID) { |
|
case $.A: |
|
case $.B: |
|
case $.I: |
|
case $.S: |
|
case $.U: |
|
case $.EM: |
|
case $.TT: |
|
case $.BIG: |
|
case $.CODE: |
|
case $.FONT: |
|
case $.NOBR: |
|
case $.SMALL: |
|
case $.STRIKE: |
|
case $.STRONG: { |
|
callAdoptionAgency(p, token); |
|
break; |
|
} |
|
case $.P: { |
|
pEndTagInBody(p); |
|
break; |
|
} |
|
case $.DL: |
|
case $.UL: |
|
case $.OL: |
|
case $.DIR: |
|
case $.DIV: |
|
case $.NAV: |
|
case $.PRE: |
|
case $.MAIN: |
|
case $.MENU: |
|
case $.ASIDE: |
|
case $.BUTTON: |
|
case $.CENTER: |
|
case $.FIGURE: |
|
case $.FOOTER: |
|
case $.HEADER: |
|
case $.HGROUP: |
|
case $.DIALOG: |
|
case $.ADDRESS: |
|
case $.ARTICLE: |
|
case $.DETAILS: |
|
case $.SECTION: |
|
case $.SUMMARY: |
|
case $.LISTING: |
|
case $.FIELDSET: |
|
case $.BLOCKQUOTE: |
|
case $.FIGCAPTION: { |
|
addressEndTagInBody(p, token); |
|
break; |
|
} |
|
case $.LI: { |
|
liEndTagInBody(p); |
|
break; |
|
} |
|
case $.DD: |
|
case $.DT: { |
|
ddEndTagInBody(p, token); |
|
break; |
|
} |
|
case $.H1: |
|
case $.H2: |
|
case $.H3: |
|
case $.H4: |
|
case $.H5: |
|
case $.H6: { |
|
numberedHeaderEndTagInBody(p); |
|
break; |
|
} |
|
case $.BR: { |
|
brEndTagInBody(p); |
|
break; |
|
} |
|
case $.BODY: { |
|
bodyEndTagInBody(p, token); |
|
break; |
|
} |
|
case $.HTML: { |
|
htmlEndTagInBody(p, token); |
|
break; |
|
} |
|
case $.FORM: { |
|
formEndTagInBody(p); |
|
break; |
|
} |
|
case $.APPLET: |
|
case $.OBJECT: |
|
case $.MARQUEE: { |
|
appletEndTagInBody(p, token); |
|
break; |
|
} |
|
case $.TEMPLATE: { |
|
templateEndTagInHead(p, token); |
|
break; |
|
} |
|
default: { |
|
genericEndTagInBody(p, token); |
|
} |
|
} |
|
} |
|
function eofInBody(p, token) { |
|
if (p.tmplInsertionModeStack.length > 0) { |
|
eofInTemplate(p, token); |
|
} |
|
else { |
|
stopParsing(p, token); |
|
} |
|
} |
|
// The "text" insertion mode |
|
//------------------------------------------------------------------ |
|
function endTagInText(p, token) { |
|
var _a; |
|
if (token.tagID === $.SCRIPT) { |
|
(_a = p.scriptHandler) === null || _a === void 0 ? void 0 : _a.call(p, p.openElements.current); |
|
} |
|
p.openElements.pop(); |
|
p.insertionMode = p.originalInsertionMode; |
|
} |
|
function eofInText(p, token) { |
|
p._err(token, ERR.eofInElementThatCanContainOnlyText); |
|
p.openElements.pop(); |
|
p.insertionMode = p.originalInsertionMode; |
|
p.onEof(token); |
|
} |
|
// The "in table" insertion mode |
|
//------------------------------------------------------------------ |
|
function characterInTable(p, token) { |
|
if (TABLE_STRUCTURE_TAGS.has(p.openElements.currentTagId)) { |
|
p.pendingCharacterTokens.length = 0; |
|
p.hasNonWhitespacePendingCharacterToken = false; |
|
p.originalInsertionMode = p.insertionMode; |
|
p.insertionMode = InsertionMode.IN_TABLE_TEXT; |
|
switch (token.type) { |
|
case TokenType.CHARACTER: { |
|
characterInTableText(p, token); |
|
break; |
|
} |
|
case TokenType.WHITESPACE_CHARACTER: { |
|
whitespaceCharacterInTableText(p, token); |
|
break; |
|
} |
|
// Ignore null |
|
} |
|
} |
|
else { |
|
tokenInTable(p, token); |
|
} |
|
} |
|
function captionStartTagInTable(p, token) { |
|
p.openElements.clearBackToTableContext(); |
|
p.activeFormattingElements.insertMarker(); |
|
p._insertElement(token, NS.HTML); |
|
p.insertionMode = InsertionMode.IN_CAPTION; |
|
} |
|
function colgroupStartTagInTable(p, token) { |
|
p.openElements.clearBackToTableContext(); |
|
p._insertElement(token, NS.HTML); |
|
p.insertionMode = InsertionMode.IN_COLUMN_GROUP; |
|
} |
|
function colStartTagInTable(p, token) { |
|
p.openElements.clearBackToTableContext(); |
|
p._insertFakeElement(TN.COLGROUP, $.COLGROUP); |
|
p.insertionMode = InsertionMode.IN_COLUMN_GROUP; |
|
startTagInColumnGroup(p, token); |
|
} |
|
function tbodyStartTagInTable(p, token) { |
|
p.openElements.clearBackToTableContext(); |
|
p._insertElement(token, NS.HTML); |
|
p.insertionMode = InsertionMode.IN_TABLE_BODY; |
|
} |
|
function tdStartTagInTable(p, token) { |
|
p.openElements.clearBackToTableContext(); |
|
p._insertFakeElement(TN.TBODY, $.TBODY); |
|
p.insertionMode = InsertionMode.IN_TABLE_BODY; |
|
startTagInTableBody(p, token); |
|
} |
|
function tableStartTagInTable(p, token) { |
|
if (p.openElements.hasInTableScope($.TABLE)) { |
|
p.openElements.popUntilTagNamePopped($.TABLE); |
|
p._resetInsertionMode(); |
|
p._processStartTag(token); |
|
} |
|
} |
|
function inputStartTagInTable(p, token) { |
|
if (isHiddenInput(token)) { |
|
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) { |
|
switch (token.tagID) { |
|
case $.TD: |
|
case $.TH: |
|
case $.TR: { |
|
tdStartTagInTable(p, token); |
|
break; |
|
} |
|
case $.STYLE: |
|
case $.SCRIPT: |
|
case $.TEMPLATE: { |
|
startTagInHead(p, token); |
|
break; |
|
} |
|
case $.COL: { |
|
colStartTagInTable(p, token); |
|
break; |
|
} |
|
case $.FORM: { |
|
formStartTagInTable(p, token); |
|
break; |
|
} |
|
case $.TABLE: { |
|
tableStartTagInTable(p, token); |
|
break; |
|
} |
|
case $.TBODY: |
|
case $.TFOOT: |
|
case $.THEAD: { |
|
tbodyStartTagInTable(p, token); |
|
break; |
|
} |
|
case $.INPUT: { |
|
inputStartTagInTable(p, token); |
|
break; |
|
} |
|
case $.CAPTION: { |
|
captionStartTagInTable(p, token); |
|
break; |
|
} |
|
case $.COLGROUP: { |
|
colgroupStartTagInTable(p, token); |
|
break; |
|
} |
|
default: { |
|
tokenInTable(p, token); |
|
} |
|
} |
|
} |
|
function endTagInTable(p, token) { |
|
switch (token.tagID) { |
|
case $.TABLE: { |
|
if (p.openElements.hasInTableScope($.TABLE)) { |
|
p.openElements.popUntilTagNamePopped($.TABLE); |
|
p._resetInsertionMode(); |
|
} |
|
break; |
|
} |
|
case $.TEMPLATE: { |
|
templateEndTagInHead(p, token); |
|
break; |
|
} |
|
case $.BODY: |
|
case $.CAPTION: |
|
case $.COL: |
|
case $.COLGROUP: |
|
case $.HTML: |
|
case $.TBODY: |
|
case $.TD: |
|
case $.TFOOT: |
|
case $.TH: |
|
case $.THEAD: |
|
case $.TR: { |
|
// Ignore token |
|
break; |
|
} |
|
default: { |
|
tokenInTable(p, token); |
|
} |
|
} |
|
} |
|
function tokenInTable(p, token) { |
|
const savedFosterParentingState = p.fosterParentingEnabled; |
|
p.fosterParentingEnabled = true; |
|
// Process token in `In Body` mode |
|
modeInBody(p, 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 |
|
//------------------------------------------------------------------ |
|
const TABLE_VOID_ELEMENTS = new Set([$.CAPTION, $.COL, $.COLGROUP, $.TBODY, $.TD, $.TFOOT, $.TH, $.THEAD, $.TR]); |
|
function startTagInCaption(p, token) { |
|
const tn = token.tagID; |
|
if (TABLE_VOID_ELEMENTS.has(tn)) { |
|
if (p.openElements.hasInTableScope($.CAPTION)) { |
|
p.openElements.generateImpliedEndTags(); |
|
p.openElements.popUntilTagNamePopped($.CAPTION); |
|
p.activeFormattingElements.clearToLastMarker(); |
|
p.insertionMode = InsertionMode.IN_TABLE; |
|
startTagInTable(p, token); |
|
} |
|
} |
|
else { |
|
startTagInBody(p, token); |
|
} |
|
} |
|
function endTagInCaption(p, token) { |
|
const tn = token.tagID; |
|
switch (tn) { |
|
case $.CAPTION: |
|
case $.TABLE: { |
|
if (p.openElements.hasInTableScope($.CAPTION)) { |
|
p.openElements.generateImpliedEndTags(); |
|
p.openElements.popUntilTagNamePopped($.CAPTION); |
|
p.activeFormattingElements.clearToLastMarker(); |
|
p.insertionMode = InsertionMode.IN_TABLE; |
|
if (tn === $.TABLE) { |
|
endTagInTable(p, token); |
|
} |
|
} |
|
break; |
|
} |
|
case $.BODY: |
|
case $.COL: |
|
case $.COLGROUP: |
|
case $.HTML: |
|
case $.TBODY: |
|
case $.TD: |
|
case $.TFOOT: |
|
case $.TH: |
|
case $.THEAD: |
|
case $.TR: { |
|
// Ignore token |
|
break; |
|
} |
|
default: { |
|
endTagInBody(p, token); |
|
} |
|
} |
|
} |
|
// The "in column group" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagInColumnGroup(p, token) { |
|
switch (token.tagID) { |
|
case $.HTML: { |
|
startTagInBody(p, token); |
|
break; |
|
} |
|
case $.COL: { |
|
p._appendElement(token, NS.HTML); |
|
token.ackSelfClosing = true; |
|
break; |
|
} |
|
case $.TEMPLATE: { |
|
startTagInHead(p, token); |
|
break; |
|
} |
|
default: { |
|
tokenInColumnGroup(p, token); |
|
} |
|
} |
|
} |
|
function endTagInColumnGroup(p, token) { |
|
switch (token.tagID) { |
|
case $.COLGROUP: { |
|
if (p.openElements.currentTagId === $.COLGROUP) { |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.IN_TABLE; |
|
} |
|
break; |
|
} |
|
case $.TEMPLATE: { |
|
templateEndTagInHead(p, token); |
|
break; |
|
} |
|
case $.COL: { |
|
// Ignore token |
|
break; |
|
} |
|
default: { |
|
tokenInColumnGroup(p, token); |
|
} |
|
} |
|
} |
|
function tokenInColumnGroup(p, token) { |
|
if (p.openElements.currentTagId === $.COLGROUP) { |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.IN_TABLE; |
|
p._processToken(token); |
|
} |
|
} |
|
// The "in table body" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagInTableBody(p, token) { |
|
switch (token.tagID) { |
|
case $.TR: { |
|
p.openElements.clearBackToTableBodyContext(); |
|
p._insertElement(token, NS.HTML); |
|
p.insertionMode = InsertionMode.IN_ROW; |
|
break; |
|
} |
|
case $.TH: |
|
case $.TD: { |
|
p.openElements.clearBackToTableBodyContext(); |
|
p._insertFakeElement(TN.TR, $.TR); |
|
p.insertionMode = InsertionMode.IN_ROW; |
|
startTagInRow(p, token); |
|
break; |
|
} |
|
case $.CAPTION: |
|
case $.COL: |
|
case $.COLGROUP: |
|
case $.TBODY: |
|
case $.TFOOT: |
|
case $.THEAD: { |
|
if (p.openElements.hasTableBodyContextInTableScope()) { |
|
p.openElements.clearBackToTableBodyContext(); |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.IN_TABLE; |
|
startTagInTable(p, token); |
|
} |
|
break; |
|
} |
|
default: { |
|
startTagInTable(p, token); |
|
} |
|
} |
|
} |
|
function endTagInTableBody(p, token) { |
|
const tn = token.tagID; |
|
switch (token.tagID) { |
|
case $.TBODY: |
|
case $.TFOOT: |
|
case $.THEAD: { |
|
if (p.openElements.hasInTableScope(tn)) { |
|
p.openElements.clearBackToTableBodyContext(); |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.IN_TABLE; |
|
} |
|
break; |
|
} |
|
case $.TABLE: { |
|
if (p.openElements.hasTableBodyContextInTableScope()) { |
|
p.openElements.clearBackToTableBodyContext(); |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.IN_TABLE; |
|
endTagInTable(p, token); |
|
} |
|
break; |
|
} |
|
case $.BODY: |
|
case $.CAPTION: |
|
case $.COL: |
|
case $.COLGROUP: |
|
case $.HTML: |
|
case $.TD: |
|
case $.TH: |
|
case $.TR: { |
|
// Ignore token |
|
break; |
|
} |
|
default: { |
|
endTagInTable(p, token); |
|
} |
|
} |
|
} |
|
// The "in row" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagInRow(p, token) { |
|
switch (token.tagID) { |
|
case $.TH: |
|
case $.TD: { |
|
p.openElements.clearBackToTableRowContext(); |
|
p._insertElement(token, NS.HTML); |
|
p.insertionMode = InsertionMode.IN_CELL; |
|
p.activeFormattingElements.insertMarker(); |
|
break; |
|
} |
|
case $.CAPTION: |
|
case $.COL: |
|
case $.COLGROUP: |
|
case $.TBODY: |
|
case $.TFOOT: |
|
case $.THEAD: |
|
case $.TR: { |
|
if (p.openElements.hasInTableScope($.TR)) { |
|
p.openElements.clearBackToTableRowContext(); |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.IN_TABLE_BODY; |
|
startTagInTableBody(p, token); |
|
} |
|
break; |
|
} |
|
default: { |
|
startTagInTable(p, token); |
|
} |
|
} |
|
} |
|
function endTagInRow(p, token) { |
|
switch (token.tagID) { |
|
case $.TR: { |
|
if (p.openElements.hasInTableScope($.TR)) { |
|
p.openElements.clearBackToTableRowContext(); |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.IN_TABLE_BODY; |
|
} |
|
break; |
|
} |
|
case $.TABLE: { |
|
if (p.openElements.hasInTableScope($.TR)) { |
|
p.openElements.clearBackToTableRowContext(); |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.IN_TABLE_BODY; |
|
endTagInTableBody(p, token); |
|
} |
|
break; |
|
} |
|
case $.TBODY: |
|
case $.TFOOT: |
|
case $.THEAD: { |
|
if (p.openElements.hasInTableScope(token.tagID) || p.openElements.hasInTableScope($.TR)) { |
|
p.openElements.clearBackToTableRowContext(); |
|
p.openElements.pop(); |
|
p.insertionMode = InsertionMode.IN_TABLE_BODY; |
|
endTagInTableBody(p, token); |
|
} |
|
break; |
|
} |
|
case $.BODY: |
|
case $.CAPTION: |
|
case $.COL: |
|
case $.COLGROUP: |
|
case $.HTML: |
|
case $.TD: |
|
case $.TH: { |
|
// Ignore end tag |
|
break; |
|
} |
|
default: { |
|
endTagInTable(p, token); |
|
} |
|
} |
|
} |
|
// The "in cell" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagInCell(p, token) { |
|
const tn = token.tagID; |
|
if (TABLE_VOID_ELEMENTS.has(tn)) { |
|
if (p.openElements.hasInTableScope($.TD) || p.openElements.hasInTableScope($.TH)) { |
|
p._closeTableCell(); |
|
startTagInRow(p, token); |
|
} |
|
} |
|
else { |
|
startTagInBody(p, token); |
|
} |
|
} |
|
function endTagInCell(p, token) { |
|
const tn = token.tagID; |
|
switch (tn) { |
|
case $.TD: |
|
case $.TH: { |
|
if (p.openElements.hasInTableScope(tn)) { |
|
p.openElements.generateImpliedEndTags(); |
|
p.openElements.popUntilTagNamePopped(tn); |
|
p.activeFormattingElements.clearToLastMarker(); |
|
p.insertionMode = InsertionMode.IN_ROW; |
|
} |
|
break; |
|
} |
|
case $.TABLE: |
|
case $.TBODY: |
|
case $.TFOOT: |
|
case $.THEAD: |
|
case $.TR: { |
|
if (p.openElements.hasInTableScope(tn)) { |
|
p._closeTableCell(); |
|
endTagInRow(p, token); |
|
} |
|
break; |
|
} |
|
case $.BODY: |
|
case $.CAPTION: |
|
case $.COL: |
|
case $.COLGROUP: |
|
case $.HTML: { |
|
// Ignore token |
|
break; |
|
} |
|
default: { |
|
endTagInBody(p, token); |
|
} |
|
} |
|
} |
|
// The "in select" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagInSelect(p, token) { |
|
switch (token.tagID) { |
|
case $.HTML: { |
|
startTagInBody(p, token); |
|
break; |
|
} |
|
case $.OPTION: { |
|
if (p.openElements.currentTagId === $.OPTION) { |
|
p.openElements.pop(); |
|
} |
|
p._insertElement(token, NS.HTML); |
|
break; |
|
} |
|
case $.OPTGROUP: { |
|
if (p.openElements.currentTagId === $.OPTION) { |
|
p.openElements.pop(); |
|
} |
|
if (p.openElements.currentTagId === $.OPTGROUP) { |
|
p.openElements.pop(); |
|
} |
|
p._insertElement(token, NS.HTML); |
|
break; |
|
} |
|
case $.INPUT: |
|
case $.KEYGEN: |
|
case $.TEXTAREA: |
|
case $.SELECT: { |
|
if (p.openElements.hasInSelectScope($.SELECT)) { |
|
p.openElements.popUntilTagNamePopped($.SELECT); |
|
p._resetInsertionMode(); |
|
if (token.tagID !== $.SELECT) { |
|
p._processStartTag(token); |
|
} |
|
} |
|
break; |
|
} |
|
case $.SCRIPT: |
|
case $.TEMPLATE: { |
|
startTagInHead(p, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
function endTagInSelect(p, token) { |
|
switch (token.tagID) { |
|
case $.OPTGROUP: { |
|
if (p.openElements.stackTop > 0 && |
|
p.openElements.currentTagId === $.OPTION && |
|
p.openElements.tagIDs[p.openElements.stackTop - 1] === $.OPTGROUP) { |
|
p.openElements.pop(); |
|
} |
|
if (p.openElements.currentTagId === $.OPTGROUP) { |
|
p.openElements.pop(); |
|
} |
|
break; |
|
} |
|
case $.OPTION: { |
|
if (p.openElements.currentTagId === $.OPTION) { |
|
p.openElements.pop(); |
|
} |
|
break; |
|
} |
|
case $.SELECT: { |
|
if (p.openElements.hasInSelectScope($.SELECT)) { |
|
p.openElements.popUntilTagNamePopped($.SELECT); |
|
p._resetInsertionMode(); |
|
} |
|
break; |
|
} |
|
case $.TEMPLATE: { |
|
templateEndTagInHead(p, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
// The "in select in table" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagInSelectInTable(p, token) { |
|
const tn = token.tagID; |
|
if (tn === $.CAPTION || |
|
tn === $.TABLE || |
|
tn === $.TBODY || |
|
tn === $.TFOOT || |
|
tn === $.THEAD || |
|
tn === $.TR || |
|
tn === $.TD || |
|
tn === $.TH) { |
|
p.openElements.popUntilTagNamePopped($.SELECT); |
|
p._resetInsertionMode(); |
|
p._processStartTag(token); |
|
} |
|
else { |
|
startTagInSelect(p, token); |
|
} |
|
} |
|
function endTagInSelectInTable(p, token) { |
|
const tn = token.tagID; |
|
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.onEndTag(token); |
|
} |
|
} |
|
else { |
|
endTagInSelect(p, token); |
|
} |
|
} |
|
// The "in template" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagInTemplate(p, token) { |
|
switch (token.tagID) { |
|
// First, handle tags that can start without a mode change |
|
case $.BASE: |
|
case $.BASEFONT: |
|
case $.BGSOUND: |
|
case $.LINK: |
|
case $.META: |
|
case $.NOFRAMES: |
|
case $.SCRIPT: |
|
case $.STYLE: |
|
case $.TEMPLATE: |
|
case $.TITLE: { |
|
startTagInHead(p, token); |
|
break; |
|
} |
|
// Re-process the token in the appropriate mode |
|
case $.CAPTION: |
|
case $.COLGROUP: |
|
case $.TBODY: |
|
case $.TFOOT: |
|
case $.THEAD: { |
|
p.tmplInsertionModeStack[0] = InsertionMode.IN_TABLE; |
|
p.insertionMode = InsertionMode.IN_TABLE; |
|
startTagInTable(p, token); |
|
break; |
|
} |
|
case $.COL: { |
|
p.tmplInsertionModeStack[0] = InsertionMode.IN_COLUMN_GROUP; |
|
p.insertionMode = InsertionMode.IN_COLUMN_GROUP; |
|
startTagInColumnGroup(p, token); |
|
break; |
|
} |
|
case $.TR: { |
|
p.tmplInsertionModeStack[0] = InsertionMode.IN_TABLE_BODY; |
|
p.insertionMode = InsertionMode.IN_TABLE_BODY; |
|
startTagInTableBody(p, token); |
|
break; |
|
} |
|
case $.TD: |
|
case $.TH: { |
|
p.tmplInsertionModeStack[0] = InsertionMode.IN_ROW; |
|
p.insertionMode = InsertionMode.IN_ROW; |
|
startTagInRow(p, token); |
|
break; |
|
} |
|
default: { |
|
p.tmplInsertionModeStack[0] = InsertionMode.IN_BODY; |
|
p.insertionMode = InsertionMode.IN_BODY; |
|
startTagInBody(p, token); |
|
} |
|
} |
|
} |
|
function endTagInTemplate(p, token) { |
|
if (token.tagID === $.TEMPLATE) { |
|
templateEndTagInHead(p, token); |
|
} |
|
} |
|
function eofInTemplate(p, token) { |
|
if (p.openElements.tmplCount > 0) { |
|
p.openElements.popUntilTagNamePopped($.TEMPLATE); |
|
p.activeFormattingElements.clearToLastMarker(); |
|
p.tmplInsertionModeStack.shift(); |
|
p._resetInsertionMode(); |
|
p.onEof(token); |
|
} |
|
else { |
|
stopParsing(p, token); |
|
} |
|
} |
|
// The "after body" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagAfterBody(p, token) { |
|
if (token.tagID === $.HTML) { |
|
startTagInBody(p, token); |
|
} |
|
else { |
|
tokenAfterBody(p, token); |
|
} |
|
} |
|
function endTagAfterBody(p, token) { |
|
var _a; |
|
if (token.tagID === $.HTML) { |
|
if (!p.fragmentContext) { |
|
p.insertionMode = InsertionMode.AFTER_AFTER_BODY; |
|
} |
|
//NOTE: <html> is never popped from the stack, so we need to updated |
|
//the end location explicitly. |
|
if (p.options.sourceCodeLocationInfo && p.openElements.tagIDs[0] === $.HTML) { |
|
p._setEndLocation(p.openElements.items[0], token); |
|
// Update the body element, if it doesn't have an end tag |
|
const bodyElement = p.openElements.items[1]; |
|
if (bodyElement && !((_a = p.treeAdapter.getNodeSourceCodeLocation(bodyElement)) === null || _a === void 0 ? void 0 : _a.endTag)) { |
|
p._setEndLocation(bodyElement, token); |
|
} |
|
} |
|
} |
|
else { |
|
tokenAfterBody(p, token); |
|
} |
|
} |
|
function tokenAfterBody(p, token) { |
|
p.insertionMode = InsertionMode.IN_BODY; |
|
modeInBody(p, token); |
|
} |
|
// The "in frameset" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagInFrameset(p, token) { |
|
switch (token.tagID) { |
|
case $.HTML: { |
|
startTagInBody(p, token); |
|
break; |
|
} |
|
case $.FRAMESET: { |
|
p._insertElement(token, NS.HTML); |
|
break; |
|
} |
|
case $.FRAME: { |
|
p._appendElement(token, NS.HTML); |
|
token.ackSelfClosing = true; |
|
break; |
|
} |
|
case $.NOFRAMES: { |
|
startTagInHead(p, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
function endTagInFrameset(p, token) { |
|
if (token.tagID === $.FRAMESET && !p.openElements.isRootHtmlElementCurrent()) { |
|
p.openElements.pop(); |
|
if (!p.fragmentContext && p.openElements.currentTagId !== $.FRAMESET) { |
|
p.insertionMode = InsertionMode.AFTER_FRAMESET; |
|
} |
|
} |
|
} |
|
// The "after frameset" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagAfterFrameset(p, token) { |
|
switch (token.tagID) { |
|
case $.HTML: { |
|
startTagInBody(p, token); |
|
break; |
|
} |
|
case $.NOFRAMES: { |
|
startTagInHead(p, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
function endTagAfterFrameset(p, token) { |
|
if (token.tagID === $.HTML) { |
|
p.insertionMode = InsertionMode.AFTER_AFTER_FRAMESET; |
|
} |
|
} |
|
// The "after after body" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagAfterAfterBody(p, token) { |
|
if (token.tagID === $.HTML) { |
|
startTagInBody(p, token); |
|
} |
|
else { |
|
tokenAfterAfterBody(p, token); |
|
} |
|
} |
|
function tokenAfterAfterBody(p, token) { |
|
p.insertionMode = InsertionMode.IN_BODY; |
|
modeInBody(p, token); |
|
} |
|
// The "after after frameset" insertion mode |
|
//------------------------------------------------------------------ |
|
function startTagAfterAfterFrameset(p, token) { |
|
switch (token.tagID) { |
|
case $.HTML: { |
|
startTagInBody(p, token); |
|
break; |
|
} |
|
case $.NOFRAMES: { |
|
startTagInHead(p, token); |
|
break; |
|
} |
|
default: |
|
// Do nothing |
|
} |
|
} |
|
// 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 popUntilHtmlOrIntegrationPoint(p) { |
|
while (p.treeAdapter.getNamespaceURI(p.openElements.current) !== NS.HTML && |
|
!p._isIntegrationPoint(p.openElements.currentTagId, p.openElements.current)) { |
|
p.openElements.pop(); |
|
} |
|
} |
|
function startTagInForeignContent(p, token) { |
|
if (foreignContent.causesExit(token)) { |
|
popUntilHtmlOrIntegrationPoint(p); |
|
p._startTagOutsideForeignContent(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) { |
|
if (token.tagID === $.P || token.tagID === $.BR) { |
|
popUntilHtmlOrIntegrationPoint(p); |
|
p._endTagOutsideForeignContent(token); |
|
return; |
|
} |
|
for (let i = p.openElements.stackTop; i > 0; i--) { |
|
const element = p.openElements.items[i]; |
|
if (p.treeAdapter.getNamespaceURI(element) === NS.HTML) { |
|
p._endTagOutsideForeignContent(token); |
|
break; |
|
} |
|
const tagName = p.treeAdapter.getTagName(element); |
|
if (tagName.toLowerCase() === token.tagName) { |
|
//NOTE: update the token tag name for `_setEndLocation`. |
|
token.tagName = tagName; |
|
p.openElements.shortenToLength(i); |
|
break; |
|
} |
|
} |
|
} |
|
//# sourceMappingURL=index.js.map
|