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