element as the fragment context if no context element was provided,\n //so we will parse in a \"forgiving\" manner\n fragmentContext !== null && fragmentContext !== void 0 ? fragmentContext : (fragmentContext = opts.treeAdapter.createElement(TN.TEMPLATE, NS.HTML, []));\n //NOTE: create a fake element which will be used as the `document` for fragment parsing.\n //This is important for jsdom, where a new `document` cannot be created. This led to\n //fragment parsing messing with the main `document`.\n const documentMock = opts.treeAdapter.createElement('documentmock', NS.HTML, []);\n const parser = new this(opts, documentMock, fragmentContext);\n if (parser.fragmentContextID === $.TEMPLATE) {\n parser.tmplInsertionModeStack.unshift(InsertionMode.IN_TEMPLATE);\n }\n parser._initTokenizerForFragmentParsing();\n parser._insertFakeRootElement();\n parser._resetInsertionMode();\n parser._findFormInFragmentContext();\n return parser;\n }\n getFragment() {\n const rootElement = this.treeAdapter.getFirstChild(this.document);\n const fragment = this.treeAdapter.createDocumentFragment();\n this._adoptNodes(rootElement, fragment);\n return fragment;\n }\n //Errors\n _err(token, code, beforeToken) {\n var _a;\n if (!this.onParseError)\n return;\n const loc = (_a = token.location) !== null && _a !== void 0 ? _a : BASE_LOC;\n const err = {\n code,\n startLine: loc.startLine,\n startCol: loc.startCol,\n startOffset: loc.startOffset,\n endLine: beforeToken ? loc.startLine : loc.endLine,\n endCol: beforeToken ? loc.startCol : loc.endCol,\n endOffset: beforeToken ? loc.startOffset : loc.endOffset,\n };\n this.onParseError(err);\n }\n //Stack events\n onItemPush(node, tid, isTop) {\n var _a, _b;\n (_b = (_a = this.treeAdapter).onItemPush) === null || _b === void 0 ? void 0 : _b.call(_a, node);\n if (isTop && this.openElements.stackTop > 0)\n this._setContextModes(node, tid);\n }\n onItemPop(node, isTop) {\n var _a, _b;\n if (this.options.sourceCodeLocationInfo) {\n this._setEndLocation(node, this.currentToken);\n }\n (_b = (_a = this.treeAdapter).onItemPop) === null || _b === void 0 ? void 0 : _b.call(_a, node, this.openElements.current);\n if (isTop) {\n let current;\n let currentTagId;\n if (this.openElements.stackTop === 0 && this.fragmentContext) {\n current = this.fragmentContext;\n currentTagId = this.fragmentContextID;\n }\n else {\n ({ current, currentTagId } = this.openElements);\n }\n this._setContextModes(current, currentTagId);\n }\n }\n _setContextModes(current, tid) {\n const isHTML = current === this.document || this.treeAdapter.getNamespaceURI(current) === NS.HTML;\n this.currentNotInHTML = !isHTML;\n this.tokenizer.inForeignNode = !isHTML && !this._isIntegrationPoint(tid, current);\n }\n _switchToTextParsing(currentToken, nextTokenizerState) {\n this._insertElement(currentToken, NS.HTML);\n this.tokenizer.state = nextTokenizerState;\n this.originalInsertionMode = this.insertionMode;\n this.insertionMode = InsertionMode.TEXT;\n }\n switchToPlaintextParsing() {\n this.insertionMode = InsertionMode.TEXT;\n this.originalInsertionMode = InsertionMode.IN_BODY;\n this.tokenizer.state = TokenizerMode.PLAINTEXT;\n }\n //Fragment parsing\n _getAdjustedCurrentElement() {\n return this.openElements.stackTop === 0 && this.fragmentContext\n ? this.fragmentContext\n : this.openElements.current;\n }\n _findFormInFragmentContext() {\n let node = this.fragmentContext;\n while (node) {\n if (this.treeAdapter.getTagName(node) === TN.FORM) {\n this.formElement = node;\n break;\n }\n node = this.treeAdapter.getParentNode(node);\n }\n }\n _initTokenizerForFragmentParsing() {\n if (!this.fragmentContext || this.treeAdapter.getNamespaceURI(this.fragmentContext) !== NS.HTML) {\n return;\n }\n switch (this.fragmentContextID) {\n case $.TITLE:\n case $.TEXTAREA: {\n this.tokenizer.state = TokenizerMode.RCDATA;\n break;\n }\n case $.STYLE:\n case $.XMP:\n case $.IFRAME:\n case $.NOEMBED:\n case $.NOFRAMES:\n case $.NOSCRIPT: {\n this.tokenizer.state = TokenizerMode.RAWTEXT;\n break;\n }\n case $.SCRIPT: {\n this.tokenizer.state = TokenizerMode.SCRIPT_DATA;\n break;\n }\n case $.PLAINTEXT: {\n this.tokenizer.state = TokenizerMode.PLAINTEXT;\n break;\n }\n default:\n // Do nothing\n }\n }\n //Tree mutation\n _setDocumentType(token) {\n const name = token.name || '';\n const publicId = token.publicId || '';\n const systemId = token.systemId || '';\n this.treeAdapter.setDocumentType(this.document, name, publicId, systemId);\n if (token.location) {\n const documentChildren = this.treeAdapter.getChildNodes(this.document);\n const docTypeNode = documentChildren.find((node) => this.treeAdapter.isDocumentTypeNode(node));\n if (docTypeNode) {\n this.treeAdapter.setNodeSourceCodeLocation(docTypeNode, token.location);\n }\n }\n }\n _attachElementToTree(element, location) {\n if (this.options.sourceCodeLocationInfo) {\n const loc = location && {\n ...location,\n startTag: location,\n };\n this.treeAdapter.setNodeSourceCodeLocation(element, loc);\n }\n if (this._shouldFosterParentOnInsertion()) {\n this._fosterParentElement(element);\n }\n else {\n const parent = this.openElements.currentTmplContentOrNode;\n this.treeAdapter.appendChild(parent, element);\n }\n }\n _appendElement(token, namespaceURI) {\n const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);\n this._attachElementToTree(element, token.location);\n }\n _insertElement(token, namespaceURI) {\n const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);\n this._attachElementToTree(element, token.location);\n this.openElements.push(element, token.tagID);\n }\n _insertFakeElement(tagName, tagID) {\n const element = this.treeAdapter.createElement(tagName, NS.HTML, []);\n this._attachElementToTree(element, null);\n this.openElements.push(element, tagID);\n }\n _insertTemplate(token) {\n const tmpl = this.treeAdapter.createElement(token.tagName, NS.HTML, token.attrs);\n const content = this.treeAdapter.createDocumentFragment();\n this.treeAdapter.setTemplateContent(tmpl, content);\n this._attachElementToTree(tmpl, token.location);\n this.openElements.push(tmpl, token.tagID);\n if (this.options.sourceCodeLocationInfo)\n this.treeAdapter.setNodeSourceCodeLocation(content, null);\n }\n _insertFakeRootElement() {\n const element = this.treeAdapter.createElement(TN.HTML, NS.HTML, []);\n if (this.options.sourceCodeLocationInfo)\n this.treeAdapter.setNodeSourceCodeLocation(element, null);\n this.treeAdapter.appendChild(this.openElements.current, element);\n this.openElements.push(element, $.HTML);\n }\n _appendCommentNode(token, parent) {\n const commentNode = this.treeAdapter.createCommentNode(token.data);\n this.treeAdapter.appendChild(parent, commentNode);\n if (this.options.sourceCodeLocationInfo) {\n this.treeAdapter.setNodeSourceCodeLocation(commentNode, token.location);\n }\n }\n _insertCharacters(token) {\n let parent;\n let beforeElement;\n if (this._shouldFosterParentOnInsertion()) {\n ({ parent, beforeElement } = this._findFosterParentingLocation());\n if (beforeElement) {\n this.treeAdapter.insertTextBefore(parent, token.chars, beforeElement);\n }\n else {\n this.treeAdapter.insertText(parent, token.chars);\n }\n }\n else {\n parent = this.openElements.currentTmplContentOrNode;\n this.treeAdapter.insertText(parent, token.chars);\n }\n if (!token.location)\n return;\n const siblings = this.treeAdapter.getChildNodes(parent);\n const textNodeIdx = beforeElement ? siblings.lastIndexOf(beforeElement) : siblings.length;\n const textNode = siblings[textNodeIdx - 1];\n //NOTE: if we have a location assigned by another token, then just update the end position\n const tnLoc = this.treeAdapter.getNodeSourceCodeLocation(textNode);\n if (tnLoc) {\n const { endLine, endCol, endOffset } = token.location;\n this.treeAdapter.updateNodeSourceCodeLocation(textNode, { endLine, endCol, endOffset });\n }\n else if (this.options.sourceCodeLocationInfo) {\n this.treeAdapter.setNodeSourceCodeLocation(textNode, token.location);\n }\n }\n _adoptNodes(donor, recipient) {\n for (let child = this.treeAdapter.getFirstChild(donor); child; child = this.treeAdapter.getFirstChild(donor)) {\n this.treeAdapter.detachNode(child);\n this.treeAdapter.appendChild(recipient, child);\n }\n }\n _setEndLocation(element, closingToken) {\n if (this.treeAdapter.getNodeSourceCodeLocation(element) && closingToken.location) {\n const ctLoc = closingToken.location;\n const tn = this.treeAdapter.getTagName(element);\n const endLoc = \n // NOTE: For cases like
- First 'p' closes without a closing\n // tag and for cases like - 'p' closes without a closing tag.\n closingToken.type === TokenType.END_TAG && tn === closingToken.tagName\n ? {\n endTag: { ...ctLoc },\n endLine: ctLoc.endLine,\n endCol: ctLoc.endCol,\n endOffset: ctLoc.endOffset,\n }\n : {\n endLine: ctLoc.startLine,\n endCol: ctLoc.startCol,\n endOffset: ctLoc.startOffset,\n };\n this.treeAdapter.updateNodeSourceCodeLocation(element, endLoc);\n }\n }\n //Token processing\n shouldProcessStartTagTokenInForeignContent(token) {\n // Check that neither current === document, or ns === NS.HTML\n if (!this.currentNotInHTML)\n return false;\n let current;\n let currentTagId;\n if (this.openElements.stackTop === 0 && this.fragmentContext) {\n current = this.fragmentContext;\n currentTagId = this.fragmentContextID;\n }\n else {\n ({ current, currentTagId } = this.openElements);\n }\n if (token.tagID === $.SVG &&\n this.treeAdapter.getTagName(current) === TN.ANNOTATION_XML &&\n this.treeAdapter.getNamespaceURI(current) === NS.MATHML) {\n return false;\n }\n return (\n // Check that `current` is not an integration point for HTML or MathML elements.\n this.tokenizer.inForeignNode ||\n // If it _is_ an integration point, then we might have to check that it is not an HTML\n // integration point.\n ((token.tagID === $.MGLYPH || token.tagID === $.MALIGNMARK) &&\n !this._isIntegrationPoint(currentTagId, current, NS.HTML)));\n }\n _processToken(token) {\n switch (token.type) {\n case TokenType.CHARACTER: {\n this.onCharacter(token);\n break;\n }\n case TokenType.NULL_CHARACTER: {\n this.onNullCharacter(token);\n break;\n }\n case TokenType.COMMENT: {\n this.onComment(token);\n break;\n }\n case TokenType.DOCTYPE: {\n this.onDoctype(token);\n break;\n }\n case TokenType.START_TAG: {\n this._processStartTag(token);\n break;\n }\n case TokenType.END_TAG: {\n this.onEndTag(token);\n break;\n }\n case TokenType.EOF: {\n this.onEof(token);\n break;\n }\n case TokenType.WHITESPACE_CHARACTER: {\n this.onWhitespaceCharacter(token);\n break;\n }\n }\n }\n //Integration points\n _isIntegrationPoint(tid, element, foreignNS) {\n const ns = this.treeAdapter.getNamespaceURI(element);\n const attrs = this.treeAdapter.getAttrList(element);\n return foreignContent.isIntegrationPoint(tid, ns, attrs, foreignNS);\n }\n //Active formatting elements reconstruction\n _reconstructActiveFormattingElements() {\n const listLength = this.activeFormattingElements.entries.length;\n if (listLength) {\n const endIndex = this.activeFormattingElements.entries.findIndex((entry) => entry.type === EntryType.Marker || this.openElements.contains(entry.element));\n const unopenIdx = endIndex < 0 ? listLength - 1 : endIndex - 1;\n for (let i = unopenIdx; i >= 0; i--) {\n const entry = this.activeFormattingElements.entries[i];\n this._insertElement(entry.token, this.treeAdapter.getNamespaceURI(entry.element));\n entry.element = this.openElements.current;\n }\n }\n }\n //Close elements\n _closeTableCell() {\n this.openElements.generateImpliedEndTags();\n this.openElements.popUntilTableCellPopped();\n this.activeFormattingElements.clearToLastMarker();\n this.insertionMode = InsertionMode.IN_ROW;\n }\n _closePElement() {\n this.openElements.generateImpliedEndTagsWithExclusion($.P);\n this.openElements.popUntilTagNamePopped($.P);\n }\n //Insertion modes\n _resetInsertionMode() {\n for (let i = this.openElements.stackTop; i >= 0; i--) {\n //Insertion mode reset map\n switch (i === 0 && this.fragmentContext ? this.fragmentContextID : this.openElements.tagIDs[i]) {\n case $.TR: {\n this.insertionMode = InsertionMode.IN_ROW;\n return;\n }\n case $.TBODY:\n case $.THEAD:\n case $.TFOOT: {\n this.insertionMode = InsertionMode.IN_TABLE_BODY;\n return;\n }\n case $.CAPTION: {\n this.insertionMode = InsertionMode.IN_CAPTION;\n return;\n }\n case $.COLGROUP: {\n this.insertionMode = InsertionMode.IN_COLUMN_GROUP;\n return;\n }\n case $.TABLE: {\n this.insertionMode = InsertionMode.IN_TABLE;\n return;\n }\n case $.BODY: {\n this.insertionMode = InsertionMode.IN_BODY;\n return;\n }\n case $.FRAMESET: {\n this.insertionMode = InsertionMode.IN_FRAMESET;\n return;\n }\n case $.SELECT: {\n this._resetInsertionModeForSelect(i);\n return;\n }\n case $.TEMPLATE: {\n this.insertionMode = this.tmplInsertionModeStack[0];\n return;\n }\n case $.HTML: {\n this.insertionMode = this.headElement ? InsertionMode.AFTER_HEAD : InsertionMode.BEFORE_HEAD;\n return;\n }\n case $.TD:\n case $.TH: {\n if (i > 0) {\n this.insertionMode = InsertionMode.IN_CELL;\n return;\n }\n break;\n }\n case $.HEAD: {\n if (i > 0) {\n this.insertionMode = InsertionMode.IN_HEAD;\n return;\n }\n break;\n }\n }\n }\n this.insertionMode = InsertionMode.IN_BODY;\n }\n _resetInsertionModeForSelect(selectIdx) {\n if (selectIdx > 0) {\n for (let i = selectIdx - 1; i > 0; i--) {\n const tn = this.openElements.tagIDs[i];\n if (tn === $.TEMPLATE) {\n break;\n }\n else if (tn === $.TABLE) {\n this.insertionMode = InsertionMode.IN_SELECT_IN_TABLE;\n return;\n }\n }\n }\n this.insertionMode = InsertionMode.IN_SELECT;\n }\n //Foster parenting\n _isElementCausesFosterParenting(tn) {\n return TABLE_STRUCTURE_TAGS.has(tn);\n }\n _shouldFosterParentOnInsertion() {\n return this.fosterParentingEnabled && this._isElementCausesFosterParenting(this.openElements.currentTagId);\n }\n _findFosterParentingLocation() {\n for (let i = this.openElements.stackTop; i >= 0; i--) {\n const openElement = this.openElements.items[i];\n switch (this.openElements.tagIDs[i]) {\n case $.TEMPLATE: {\n if (this.treeAdapter.getNamespaceURI(openElement) === NS.HTML) {\n return { parent: this.treeAdapter.getTemplateContent(openElement), beforeElement: null };\n }\n break;\n }\n case $.TABLE: {\n const parent = this.treeAdapter.getParentNode(openElement);\n if (parent) {\n return { parent, beforeElement: openElement };\n }\n return { parent: this.openElements.items[i - 1], beforeElement: null };\n }\n default:\n // Do nothing\n }\n }\n return { parent: this.openElements.items[0], beforeElement: null };\n }\n _fosterParentElement(element) {\n const location = this._findFosterParentingLocation();\n if (location.beforeElement) {\n this.treeAdapter.insertBefore(location.parent, element, location.beforeElement);\n }\n else {\n this.treeAdapter.appendChild(location.parent, element);\n }\n }\n //Special elements\n _isSpecialElement(element, id) {\n const ns = this.treeAdapter.getNamespaceURI(element);\n return SPECIAL_ELEMENTS[ns].has(id);\n }\n onCharacter(token) {\n this.skipNextNewLine = false;\n if (this.tokenizer.inForeignNode) {\n characterInForeignContent(this, token);\n return;\n }\n switch (this.insertionMode) {\n case InsertionMode.INITIAL: {\n tokenInInitialMode(this, token);\n break;\n }\n case InsertionMode.BEFORE_HTML: {\n tokenBeforeHtml(this, token);\n break;\n }\n case InsertionMode.BEFORE_HEAD: {\n tokenBeforeHead(this, token);\n break;\n }\n case InsertionMode.IN_HEAD: {\n tokenInHead(this, token);\n break;\n }\n case InsertionMode.IN_HEAD_NO_SCRIPT: {\n tokenInHeadNoScript(this, token);\n break;\n }\n case InsertionMode.AFTER_HEAD: {\n tokenAfterHead(this, token);\n break;\n }\n case InsertionMode.IN_BODY:\n case InsertionMode.IN_CAPTION:\n case InsertionMode.IN_CELL:\n case InsertionMode.IN_TEMPLATE: {\n characterInBody(this, token);\n break;\n }\n case InsertionMode.TEXT:\n case InsertionMode.IN_SELECT:\n case InsertionMode.IN_SELECT_IN_TABLE: {\n this._insertCharacters(token);\n break;\n }\n case InsertionMode.IN_TABLE:\n case InsertionMode.IN_TABLE_BODY:\n case InsertionMode.IN_ROW: {\n characterInTable(this, token);\n break;\n }\n case InsertionMode.IN_TABLE_TEXT: {\n characterInTableText(this, token);\n break;\n }\n case InsertionMode.IN_COLUMN_GROUP: {\n tokenInColumnGroup(this, token);\n break;\n }\n case InsertionMode.AFTER_BODY: {\n tokenAfterBody(this, token);\n break;\n }\n case InsertionMode.AFTER_AFTER_BODY: {\n tokenAfterAfterBody(this, token);\n break;\n }\n default:\n // Do nothing\n }\n }\n onNullCharacter(token) {\n this.skipNextNewLine = false;\n if (this.tokenizer.inForeignNode) {\n nullCharacterInForeignContent(this, token);\n return;\n }\n switch (this.insertionMode) {\n case InsertionMode.INITIAL: {\n tokenInInitialMode(this, token);\n break;\n }\n case InsertionMode.BEFORE_HTML: {\n tokenBeforeHtml(this, token);\n break;\n }\n case InsertionMode.BEFORE_HEAD: {\n tokenBeforeHead(this, token);\n break;\n }\n case InsertionMode.IN_HEAD: {\n tokenInHead(this, token);\n break;\n }\n case InsertionMode.IN_HEAD_NO_SCRIPT: {\n tokenInHeadNoScript(this, token);\n break;\n }\n case InsertionMode.AFTER_HEAD: {\n tokenAfterHead(this, token);\n break;\n }\n case InsertionMode.TEXT: {\n this._insertCharacters(token);\n break;\n }\n case InsertionMode.IN_TABLE:\n case InsertionMode.IN_TABLE_BODY:\n case InsertionMode.IN_ROW: {\n characterInTable(this, token);\n break;\n }\n case InsertionMode.IN_COLUMN_GROUP: {\n tokenInColumnGroup(this, token);\n break;\n }\n case InsertionMode.AFTER_BODY: {\n tokenAfterBody(this, token);\n break;\n }\n case InsertionMode.AFTER_AFTER_BODY: {\n tokenAfterAfterBody(this, token);\n break;\n }\n default:\n // Do nothing\n }\n }\n onComment(token) {\n this.skipNextNewLine = false;\n if (this.currentNotInHTML) {\n appendComment(this, token);\n return;\n }\n switch (this.insertionMode) {\n case InsertionMode.INITIAL:\n case InsertionMode.BEFORE_HTML:\n case InsertionMode.BEFORE_HEAD:\n case InsertionMode.IN_HEAD:\n case InsertionMode.IN_HEAD_NO_SCRIPT:\n case InsertionMode.AFTER_HEAD:\n case InsertionMode.IN_BODY:\n case InsertionMode.IN_TABLE:\n case InsertionMode.IN_CAPTION:\n case InsertionMode.IN_COLUMN_GROUP:\n case InsertionMode.IN_TABLE_BODY:\n case InsertionMode.IN_ROW:\n case InsertionMode.IN_CELL:\n case InsertionMode.IN_SELECT:\n case InsertionMode.IN_SELECT_IN_TABLE:\n case InsertionMode.IN_TEMPLATE:\n case InsertionMode.IN_FRAMESET:\n case InsertionMode.AFTER_FRAMESET: {\n appendComment(this, token);\n break;\n }\n case InsertionMode.IN_TABLE_TEXT: {\n tokenInTableText(this, token);\n break;\n }\n case InsertionMode.AFTER_BODY: {\n appendCommentToRootHtmlElement(this, token);\n break;\n }\n case InsertionMode.AFTER_AFTER_BODY:\n case InsertionMode.AFTER_AFTER_FRAMESET: {\n appendCommentToDocument(this, token);\n break;\n }\n default:\n // Do nothing\n }\n }\n onDoctype(token) {\n this.skipNextNewLine = false;\n switch (this.insertionMode) {\n case InsertionMode.INITIAL: {\n doctypeInInitialMode(this, token);\n break;\n }\n case InsertionMode.BEFORE_HEAD:\n case InsertionMode.IN_HEAD:\n case InsertionMode.IN_HEAD_NO_SCRIPT:\n case InsertionMode.AFTER_HEAD: {\n this._err(token, ERR.misplacedDoctype);\n break;\n }\n case InsertionMode.IN_TABLE_TEXT: {\n tokenInTableText(this, token);\n break;\n }\n default:\n // Do nothing\n }\n }\n onStartTag(token) {\n this.skipNextNewLine = false;\n this.currentToken = token;\n this._processStartTag(token);\n if (token.selfClosing && !token.ackSelfClosing) {\n this._err(token, ERR.nonVoidHtmlElementStartTagWithTrailingSolidus);\n }\n }\n /**\n * Processes a given start tag.\n *\n * `onStartTag` checks if a self-closing tag was recognized. When a token\n * is moved inbetween multiple insertion modes, this check for self-closing\n * could lead to false positives. To avoid this, `_processStartTag` is used\n * for nested calls.\n *\n * @param token The token to process.\n */\n _processStartTag(token) {\n if (this.shouldProcessStartTagTokenInForeignContent(token)) {\n startTagInForeignContent(this, token);\n }\n else {\n this._startTagOutsideForeignContent(token);\n }\n }\n _startTagOutsideForeignContent(token) {\n switch (this.insertionMode) {\n case InsertionMode.INITIAL: {\n tokenInInitialMode(this, token);\n break;\n }\n case InsertionMode.BEFORE_HTML: {\n startTagBeforeHtml(this, token);\n break;\n }\n case InsertionMode.BEFORE_HEAD: {\n startTagBeforeHead(this, token);\n break;\n }\n case InsertionMode.IN_HEAD: {\n startTagInHead(this, token);\n break;\n }\n case InsertionMode.IN_HEAD_NO_SCRIPT: {\n startTagInHeadNoScript(this, token);\n break;\n }\n case InsertionMode.AFTER_HEAD: {\n startTagAfterHead(this, token);\n break;\n }\n case InsertionMode.IN_BODY: {\n startTagInBody(this, token);\n break;\n }\n case InsertionMode.IN_TABLE: {\n startTagInTable(this, token);\n break;\n }\n case InsertionMode.IN_TABLE_TEXT: {\n tokenInTableText(this, token);\n break;\n }\n case InsertionMode.IN_CAPTION: {\n startTagInCaption(this, token);\n break;\n }\n case InsertionMode.IN_COLUMN_GROUP: {\n startTagInColumnGroup(this, token);\n break;\n }\n case InsertionMode.IN_TABLE_BODY: {\n startTagInTableBody(this, token);\n break;\n }\n case InsertionMode.IN_ROW: {\n startTagInRow(this, token);\n break;\n }\n case InsertionMode.IN_CELL: {\n startTagInCell(this, token);\n break;\n }\n case InsertionMode.IN_SELECT: {\n startTagInSelect(this, token);\n break;\n }\n case InsertionMode.IN_SELECT_IN_TABLE: {\n startTagInSelectInTable(this, token);\n break;\n }\n case InsertionMode.IN_TEMPLATE: {\n startTagInTemplate(this, token);\n break;\n }\n case InsertionMode.AFTER_BODY: {\n startTagAfterBody(this, token);\n break;\n }\n case InsertionMode.IN_FRAMESET: {\n startTagInFrameset(this, token);\n break;\n }\n case InsertionMode.AFTER_FRAMESET: {\n startTagAfterFrameset(this, token);\n break;\n }\n case InsertionMode.AFTER_AFTER_BODY: {\n startTagAfterAfterBody(this, token);\n break;\n }\n case InsertionMode.AFTER_AFTER_FRAMESET: {\n startTagAfterAfterFrameset(this, token);\n break;\n }\n default:\n // Do nothing\n }\n }\n onEndTag(token) {\n this.skipNextNewLine = false;\n this.currentToken = token;\n if (this.currentNotInHTML) {\n endTagInForeignContent(this, token);\n }\n else {\n this._endTagOutsideForeignContent(token);\n }\n }\n _endTagOutsideForeignContent(token) {\n switch (this.insertionMode) {\n case InsertionMode.INITIAL: {\n tokenInInitialMode(this, token);\n break;\n }\n case InsertionMode.BEFORE_HTML: {\n endTagBeforeHtml(this, token);\n break;\n }\n case InsertionMode.BEFORE_HEAD: {\n endTagBeforeHead(this, token);\n break;\n }\n case InsertionMode.IN_HEAD: {\n endTagInHead(this, token);\n break;\n }\n case InsertionMode.IN_HEAD_NO_SCRIPT: {\n endTagInHeadNoScript(this, token);\n break;\n }\n case InsertionMode.AFTER_HEAD: {\n endTagAfterHead(this, token);\n break;\n }\n case InsertionMode.IN_BODY: {\n endTagInBody(this, token);\n break;\n }\n case InsertionMode.TEXT: {\n endTagInText(this, token);\n break;\n }\n case InsertionMode.IN_TABLE: {\n endTagInTable(this, token);\n break;\n }\n case InsertionMode.IN_TABLE_TEXT: {\n tokenInTableText(this, token);\n break;\n }\n case InsertionMode.IN_CAPTION: {\n endTagInCaption(this, token);\n break;\n }\n case InsertionMode.IN_COLUMN_GROUP: {\n endTagInColumnGroup(this, token);\n break;\n }\n case InsertionMode.IN_TABLE_BODY: {\n endTagInTableBody(this, token);\n break;\n }\n case InsertionMode.IN_ROW: {\n endTagInRow(this, token);\n break;\n }\n case InsertionMode.IN_CELL: {\n endTagInCell(this, token);\n break;\n }\n case InsertionMode.IN_SELECT: {\n endTagInSelect(this, token);\n break;\n }\n case InsertionMode.IN_SELECT_IN_TABLE: {\n endTagInSelectInTable(this, token);\n break;\n }\n case InsertionMode.IN_TEMPLATE: {\n endTagInTemplate(this, token);\n break;\n }\n case InsertionMode.AFTER_BODY: {\n endTagAfterBody(this, token);\n break;\n }\n case InsertionMode.IN_FRAMESET: {\n endTagInFrameset(this, token);\n break;\n }\n case InsertionMode.AFTER_FRAMESET: {\n endTagAfterFrameset(this, token);\n break;\n }\n case InsertionMode.AFTER_AFTER_BODY: {\n tokenAfterAfterBody(this, token);\n break;\n }\n default:\n // Do nothing\n }\n }\n onEof(token) {\n switch (this.insertionMode) {\n case InsertionMode.INITIAL: {\n tokenInInitialMode(this, token);\n break;\n }\n case InsertionMode.BEFORE_HTML: {\n tokenBeforeHtml(this, token);\n break;\n }\n case InsertionMode.BEFORE_HEAD: {\n tokenBeforeHead(this, token);\n break;\n }\n case InsertionMode.IN_HEAD: {\n tokenInHead(this, token);\n break;\n }\n case InsertionMode.IN_HEAD_NO_SCRIPT: {\n tokenInHeadNoScript(this, token);\n break;\n }\n case InsertionMode.AFTER_HEAD: {\n tokenAfterHead(this, token);\n break;\n }\n case InsertionMode.IN_BODY:\n case InsertionMode.IN_TABLE:\n case InsertionMode.IN_CAPTION:\n case InsertionMode.IN_COLUMN_GROUP:\n case InsertionMode.IN_TABLE_BODY:\n case InsertionMode.IN_ROW:\n case InsertionMode.IN_CELL:\n case InsertionMode.IN_SELECT:\n case InsertionMode.IN_SELECT_IN_TABLE: {\n eofInBody(this, token);\n break;\n }\n case InsertionMode.TEXT: {\n eofInText(this, token);\n break;\n }\n case InsertionMode.IN_TABLE_TEXT: {\n tokenInTableText(this, token);\n break;\n }\n case InsertionMode.IN_TEMPLATE: {\n eofInTemplate(this, token);\n break;\n }\n case InsertionMode.AFTER_BODY:\n case InsertionMode.IN_FRAMESET:\n case InsertionMode.AFTER_FRAMESET:\n case InsertionMode.AFTER_AFTER_BODY:\n case InsertionMode.AFTER_AFTER_FRAMESET: {\n stopParsing(this, token);\n break;\n }\n default:\n // Do nothing\n }\n }\n onWhitespaceCharacter(token) {\n if (this.skipNextNewLine) {\n this.skipNextNewLine = false;\n if (token.chars.charCodeAt(0) === unicode.CODE_POINTS.LINE_FEED) {\n if (token.chars.length === 1) {\n return;\n }\n token.chars = token.chars.substr(1);\n }\n }\n if (this.tokenizer.inForeignNode) {\n this._insertCharacters(token);\n return;\n }\n switch (this.insertionMode) {\n case InsertionMode.IN_HEAD:\n case InsertionMode.IN_HEAD_NO_SCRIPT:\n case InsertionMode.AFTER_HEAD:\n case InsertionMode.TEXT:\n case InsertionMode.IN_COLUMN_GROUP:\n case InsertionMode.IN_SELECT:\n case InsertionMode.IN_SELECT_IN_TABLE:\n case InsertionMode.IN_FRAMESET:\n case InsertionMode.AFTER_FRAMESET: {\n this._insertCharacters(token);\n break;\n }\n case InsertionMode.IN_BODY:\n case InsertionMode.IN_CAPTION:\n case InsertionMode.IN_CELL:\n case InsertionMode.IN_TEMPLATE:\n case InsertionMode.AFTER_BODY:\n case InsertionMode.AFTER_AFTER_BODY:\n case InsertionMode.AFTER_AFTER_FRAMESET: {\n whitespaceCharacterInBody(this, token);\n break;\n }\n case InsertionMode.IN_TABLE:\n case InsertionMode.IN_TABLE_BODY:\n case InsertionMode.IN_ROW: {\n characterInTable(this, token);\n break;\n }\n case InsertionMode.IN_TABLE_TEXT: {\n whitespaceCharacterInTableText(this, token);\n break;\n }\n default:\n // Do nothing\n }\n }\n}\n//Adoption agency algorithm\n//(see: http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adoptionAgency)\n//------------------------------------------------------------------\n//Steps 5-8 of the algorithm\nfunction aaObtainFormattingElementEntry(p, token) {\n let formattingElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName(token.tagName);\n if (formattingElementEntry) {\n if (!p.openElements.contains(formattingElementEntry.element)) {\n p.activeFormattingElements.removeEntry(formattingElementEntry);\n formattingElementEntry = null;\n }\n else if (!p.openElements.hasInScope(token.tagID)) {\n formattingElementEntry = null;\n }\n }\n else {\n genericEndTagInBody(p, token);\n }\n return formattingElementEntry;\n}\n//Steps 9 and 10 of the algorithm\nfunction aaObtainFurthestBlock(p, formattingElementEntry) {\n let furthestBlock = null;\n let idx = p.openElements.stackTop;\n for (; idx >= 0; idx--) {\n const element = p.openElements.items[idx];\n if (element === formattingElementEntry.element) {\n break;\n }\n if (p._isSpecialElement(element, p.openElements.tagIDs[idx])) {\n furthestBlock = element;\n }\n }\n if (!furthestBlock) {\n p.openElements.shortenToLength(idx < 0 ? 0 : idx);\n p.activeFormattingElements.removeEntry(formattingElementEntry);\n }\n return furthestBlock;\n}\n//Step 13 of the algorithm\nfunction aaInnerLoop(p, furthestBlock, formattingElement) {\n let lastElement = furthestBlock;\n let nextElement = p.openElements.getCommonAncestor(furthestBlock);\n for (let i = 0, element = nextElement; element !== formattingElement; i++, element = nextElement) {\n //NOTE: store the next element for the next loop iteration (it may be deleted from the stack by step 9.5)\n nextElement = p.openElements.getCommonAncestor(element);\n const elementEntry = p.activeFormattingElements.getElementEntry(element);\n const counterOverflow = elementEntry && i >= AA_INNER_LOOP_ITER;\n const shouldRemoveFromOpenElements = !elementEntry || counterOverflow;\n if (shouldRemoveFromOpenElements) {\n if (counterOverflow) {\n p.activeFormattingElements.removeEntry(elementEntry);\n }\n p.openElements.remove(element);\n }\n else {\n element = aaRecreateElementFromEntry(p, elementEntry);\n if (lastElement === furthestBlock) {\n p.activeFormattingElements.bookmark = elementEntry;\n }\n p.treeAdapter.detachNode(lastElement);\n p.treeAdapter.appendChild(element, lastElement);\n lastElement = element;\n }\n }\n return lastElement;\n}\n//Step 13.7 of the algorithm\nfunction aaRecreateElementFromEntry(p, elementEntry) {\n const ns = p.treeAdapter.getNamespaceURI(elementEntry.element);\n const newElement = p.treeAdapter.createElement(elementEntry.token.tagName, ns, elementEntry.token.attrs);\n p.openElements.replace(elementEntry.element, newElement);\n elementEntry.element = newElement;\n return newElement;\n}\n//Step 14 of the algorithm\nfunction aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement) {\n const tn = p.treeAdapter.getTagName(commonAncestor);\n const tid = getTagID(tn);\n if (p._isElementCausesFosterParenting(tid)) {\n p._fosterParentElement(lastElement);\n }\n else {\n const ns = p.treeAdapter.getNamespaceURI(commonAncestor);\n if (tid === $.TEMPLATE && ns === NS.HTML) {\n commonAncestor = p.treeAdapter.getTemplateContent(commonAncestor);\n }\n p.treeAdapter.appendChild(commonAncestor, lastElement);\n }\n}\n//Steps 15-19 of the algorithm\nfunction aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry) {\n const ns = p.treeAdapter.getNamespaceURI(formattingElementEntry.element);\n const { token } = formattingElementEntry;\n const newElement = p.treeAdapter.createElement(token.tagName, ns, token.attrs);\n p._adoptNodes(furthestBlock, newElement);\n p.treeAdapter.appendChild(furthestBlock, newElement);\n p.activeFormattingElements.insertElementAfterBookmark(newElement, token);\n p.activeFormattingElements.removeEntry(formattingElementEntry);\n p.openElements.remove(formattingElementEntry.element);\n p.openElements.insertAfter(furthestBlock, newElement, token.tagID);\n}\n//Algorithm entry point\nfunction callAdoptionAgency(p, token) {\n for (let i = 0; i < AA_OUTER_LOOP_ITER; i++) {\n const formattingElementEntry = aaObtainFormattingElementEntry(p, token);\n if (!formattingElementEntry) {\n break;\n }\n const furthestBlock = aaObtainFurthestBlock(p, formattingElementEntry);\n if (!furthestBlock) {\n break;\n }\n p.activeFormattingElements.bookmark = formattingElementEntry;\n const lastElement = aaInnerLoop(p, furthestBlock, formattingElementEntry.element);\n const commonAncestor = p.openElements.getCommonAncestor(formattingElementEntry.element);\n p.treeAdapter.detachNode(lastElement);\n if (commonAncestor)\n aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement);\n aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry);\n }\n}\n//Generic token handlers\n//------------------------------------------------------------------\nfunction appendComment(p, token) {\n p._appendCommentNode(token, p.openElements.currentTmplContentOrNode);\n}\nfunction appendCommentToRootHtmlElement(p, token) {\n p._appendCommentNode(token, p.openElements.items[0]);\n}\nfunction appendCommentToDocument(p, token) {\n p._appendCommentNode(token, p.document);\n}\nfunction stopParsing(p, token) {\n p.stopped = true;\n // NOTE: Set end locations for elements that remain on the open element stack.\n if (token.location) {\n // NOTE: If we are not in a fragment, `html` and `body` will stay on the stack.\n // This is a problem, as we might overwrite their end position here.\n const target = p.fragmentContext ? 0 : 2;\n for (let i = p.openElements.stackTop; i >= target; i--) {\n p._setEndLocation(p.openElements.items[i], token);\n }\n // Handle `html` and `body`\n if (!p.fragmentContext && p.openElements.stackTop >= 0) {\n const htmlElement = p.openElements.items[0];\n const htmlLocation = p.treeAdapter.getNodeSourceCodeLocation(htmlElement);\n if (htmlLocation && !htmlLocation.endTag) {\n p._setEndLocation(htmlElement, token);\n if (p.openElements.stackTop >= 1) {\n const bodyElement = p.openElements.items[1];\n const bodyLocation = p.treeAdapter.getNodeSourceCodeLocation(bodyElement);\n if (bodyLocation && !bodyLocation.endTag) {\n p._setEndLocation(bodyElement, token);\n }\n }\n }\n }\n }\n}\n// The \"initial\" insertion mode\n//------------------------------------------------------------------\nfunction doctypeInInitialMode(p, token) {\n p._setDocumentType(token);\n const mode = token.forceQuirks ? DOCUMENT_MODE.QUIRKS : doctype.getDocumentMode(token);\n if (!doctype.isConforming(token)) {\n p._err(token, ERR.nonConformingDoctype);\n }\n p.treeAdapter.setDocumentMode(p.document, mode);\n p.insertionMode = InsertionMode.BEFORE_HTML;\n}\nfunction tokenInInitialMode(p, token) {\n p._err(token, ERR.missingDoctype, true);\n p.treeAdapter.setDocumentMode(p.document, DOCUMENT_MODE.QUIRKS);\n p.insertionMode = InsertionMode.BEFORE_HTML;\n p._processToken(token);\n}\n// The \"before html\" insertion mode\n//------------------------------------------------------------------\nfunction startTagBeforeHtml(p, token) {\n if (token.tagID === $.HTML) {\n p._insertElement(token, NS.HTML);\n p.insertionMode = InsertionMode.BEFORE_HEAD;\n }\n else {\n tokenBeforeHtml(p, token);\n }\n}\nfunction endTagBeforeHtml(p, token) {\n const tn = token.tagID;\n if (tn === $.HTML || tn === $.HEAD || tn === $.BODY || tn === $.BR) {\n tokenBeforeHtml(p, token);\n }\n}\nfunction tokenBeforeHtml(p, token) {\n p._insertFakeRootElement();\n p.insertionMode = InsertionMode.BEFORE_HEAD;\n p._processToken(token);\n}\n// The \"before head\" insertion mode\n//------------------------------------------------------------------\nfunction startTagBeforeHead(p, token) {\n switch (token.tagID) {\n case $.HTML: {\n startTagInBody(p, token);\n break;\n }\n case $.HEAD: {\n p._insertElement(token, NS.HTML);\n p.headElement = p.openElements.current;\n p.insertionMode = InsertionMode.IN_HEAD;\n break;\n }\n default: {\n tokenBeforeHead(p, token);\n }\n }\n}\nfunction endTagBeforeHead(p, token) {\n const tn = token.tagID;\n if (tn === $.HEAD || tn === $.BODY || tn === $.HTML || tn === $.BR) {\n tokenBeforeHead(p, token);\n }\n else {\n p._err(token, ERR.endTagWithoutMatchingOpenElement);\n }\n}\nfunction tokenBeforeHead(p, token) {\n p._insertFakeElement(TN.HEAD, $.HEAD);\n p.headElement = p.openElements.current;\n p.insertionMode = InsertionMode.IN_HEAD;\n p._processToken(token);\n}\n// The \"in head\" insertion mode\n//------------------------------------------------------------------\nfunction startTagInHead(p, token) {\n switch (token.tagID) {\n case $.HTML: {\n startTagInBody(p, token);\n break;\n }\n case $.BASE:\n case $.BASEFONT:\n case $.BGSOUND:\n case $.LINK:\n case $.META: {\n p._appendElement(token, NS.HTML);\n token.ackSelfClosing = true;\n break;\n }\n case $.TITLE: {\n p._switchToTextParsing(token, TokenizerMode.RCDATA);\n break;\n }\n case $.NOSCRIPT: {\n if (p.options.scriptingEnabled) {\n p._switchToTextParsing(token, TokenizerMode.RAWTEXT);\n }\n else {\n p._insertElement(token, NS.HTML);\n p.insertionMode = InsertionMode.IN_HEAD_NO_SCRIPT;\n }\n break;\n }\n case $.NOFRAMES:\n case $.STYLE: {\n p._switchToTextParsing(token, TokenizerMode.RAWTEXT);\n break;\n }\n case $.SCRIPT: {\n p._switchToTextParsing(token, TokenizerMode.SCRIPT_DATA);\n break;\n }\n case $.TEMPLATE: {\n p._insertTemplate(token);\n p.activeFormattingElements.insertMarker();\n p.framesetOk = false;\n p.insertionMode = InsertionMode.IN_TEMPLATE;\n p.tmplInsertionModeStack.unshift(InsertionMode.IN_TEMPLATE);\n break;\n }\n case $.HEAD: {\n p._err(token, ERR.misplacedStartTagForHeadElement);\n break;\n }\n default: {\n tokenInHead(p, token);\n }\n }\n}\nfunction endTagInHead(p, token) {\n switch (token.tagID) {\n case $.HEAD: {\n p.openElements.pop();\n p.insertionMode = InsertionMode.AFTER_HEAD;\n break;\n }\n case $.BODY:\n case $.BR:\n case $.HTML: {\n tokenInHead(p, token);\n break;\n }\n case $.TEMPLATE: {\n templateEndTagInHead(p, token);\n break;\n }\n default: {\n p._err(token, ERR.endTagWithoutMatchingOpenElement);\n }\n }\n}\nfunction templateEndTagInHead(p, token) {\n if (p.openElements.tmplCount > 0) {\n p.openElements.generateImpliedEndTagsThoroughly();\n if (p.openElements.currentTagId !== $.TEMPLATE) {\n p._err(token, ERR.closingOfElementWithOpenChildElements);\n }\n p.openElements.popUntilTagNamePopped($.TEMPLATE);\n p.activeFormattingElements.clearToLastMarker();\n p.tmplInsertionModeStack.shift();\n p._resetInsertionMode();\n }\n else {\n p._err(token, ERR.endTagWithoutMatchingOpenElement);\n }\n}\nfunction tokenInHead(p, token) {\n p.openElements.pop();\n p.insertionMode = InsertionMode.AFTER_HEAD;\n p._processToken(token);\n}\n// The \"in head no script\" insertion mode\n//------------------------------------------------------------------\nfunction startTagInHeadNoScript(p, token) {\n switch (token.tagID) {\n case $.HTML: {\n startTagInBody(p, token);\n break;\n }\n case $.BASEFONT:\n case $.BGSOUND:\n case $.HEAD:\n case $.LINK:\n case $.META:\n case $.NOFRAMES:\n case $.STYLE: {\n startTagInHead(p, token);\n break;\n }\n case $.NOSCRIPT: {\n p._err(token, ERR.nestedNoscriptInHead);\n break;\n }\n default: {\n tokenInHeadNoScript(p, token);\n }\n }\n}\nfunction endTagInHeadNoScript(p, token) {\n switch (token.tagID) {\n case $.NOSCRIPT: {\n p.openElements.pop();\n p.insertionMode = InsertionMode.IN_HEAD;\n break;\n }\n case $.BR: {\n tokenInHeadNoScript(p, token);\n break;\n }\n default: {\n p._err(token, ERR.endTagWithoutMatchingOpenElement);\n }\n }\n}\nfunction tokenInHeadNoScript(p, token) {\n const errCode = token.type === TokenType.EOF ? ERR.openElementsLeftAfterEof : ERR.disallowedContentInNoscriptInHead;\n p._err(token, errCode);\n p.openElements.pop();\n p.insertionMode = InsertionMode.IN_HEAD;\n p._processToken(token);\n}\n// The \"after head\" insertion mode\n//------------------------------------------------------------------\nfunction startTagAfterHead(p, token) {\n switch (token.tagID) {\n case $.HTML: {\n startTagInBody(p, token);\n break;\n }\n case $.BODY: {\n p._insertElement(token, NS.HTML);\n p.framesetOk = false;\n p.insertionMode = InsertionMode.IN_BODY;\n break;\n }\n case $.FRAMESET: {\n p._insertElement(token, NS.HTML);\n p.insertionMode = InsertionMode.IN_FRAMESET;\n break;\n }\n case $.BASE:\n case $.BASEFONT:\n case $.BGSOUND:\n case $.LINK:\n case $.META:\n case $.NOFRAMES:\n case $.SCRIPT:\n case $.STYLE:\n case $.TEMPLATE:\n case $.TITLE: {\n p._err(token, ERR.abandonedHeadElementChild);\n p.openElements.push(p.headElement, $.HEAD);\n startTagInHead(p, token);\n p.openElements.remove(p.headElement);\n break;\n }\n case $.HEAD: {\n p._err(token, ERR.misplacedStartTagForHeadElement);\n break;\n }\n default: {\n tokenAfterHead(p, token);\n }\n }\n}\nfunction endTagAfterHead(p, token) {\n switch (token.tagID) {\n case $.BODY:\n case $.HTML:\n case $.BR: {\n tokenAfterHead(p, token);\n break;\n }\n case $.TEMPLATE: {\n templateEndTagInHead(p, token);\n break;\n }\n default: {\n p._err(token, ERR.endTagWithoutMatchingOpenElement);\n }\n }\n}\nfunction tokenAfterHead(p, token) {\n p._insertFakeElement(TN.BODY, $.BODY);\n p.insertionMode = InsertionMode.IN_BODY;\n modeInBody(p, token);\n}\n// The \"in body\" insertion mode\n//------------------------------------------------------------------\nfunction modeInBody(p, token) {\n switch (token.type) {\n case TokenType.CHARACTER: {\n characterInBody(p, token);\n break;\n }\n case TokenType.WHITESPACE_CHARACTER: {\n whitespaceCharacterInBody(p, token);\n break;\n }\n case TokenType.COMMENT: {\n appendComment(p, token);\n break;\n }\n case TokenType.START_TAG: {\n startTagInBody(p, token);\n break;\n }\n case TokenType.END_TAG: {\n endTagInBody(p, token);\n break;\n }\n case TokenType.EOF: {\n eofInBody(p, token);\n break;\n }\n default:\n // Do nothing\n }\n}\nfunction whitespaceCharacterInBody(p, token) {\n p._reconstructActiveFormattingElements();\n p._insertCharacters(token);\n}\nfunction characterInBody(p, token) {\n p._reconstructActiveFormattingElements();\n p._insertCharacters(token);\n p.framesetOk = false;\n}\nfunction htmlStartTagInBody(p, token) {\n if (p.openElements.tmplCount === 0) {\n p.treeAdapter.adoptAttributes(p.openElements.items[0], token.attrs);\n }\n}\nfunction bodyStartTagInBody(p, token) {\n const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement();\n if (bodyElement && p.openElements.tmplCount === 0) {\n p.framesetOk = false;\n p.treeAdapter.adoptAttributes(bodyElement, token.attrs);\n }\n}\nfunction framesetStartTagInBody(p, token) {\n const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement();\n if (p.framesetOk && bodyElement) {\n p.treeAdapter.detachNode(bodyElement);\n p.openElements.popAllUpToHtmlElement();\n p._insertElement(token, NS.HTML);\n p.insertionMode = InsertionMode.IN_FRAMESET;\n }\n}\nfunction addressStartTagInBody(p, token) {\n if (p.openElements.hasInButtonScope($.P)) {\n p._closePElement();\n }\n p._insertElement(token, NS.HTML);\n}\nfunction numberedHeaderStartTagInBody(p, token) {\n if (p.openElements.hasInButtonScope($.P)) {\n p._closePElement();\n }\n if (isNumberedHeader(p.openElements.currentTagId)) {\n p.openElements.pop();\n }\n p._insertElement(token, NS.HTML);\n}\nfunction preStartTagInBody(p, token) {\n if (p.openElements.hasInButtonScope($.P)) {\n p._closePElement();\n }\n p._insertElement(token, NS.HTML);\n //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move\n //on to the next one. (Newlines at the start of pre blocks are ignored as an authoring convenience.)\n p.skipNextNewLine = true;\n p.framesetOk = false;\n}\nfunction formStartTagInBody(p, token) {\n const inTemplate = p.openElements.tmplCount > 0;\n if (!p.formElement || inTemplate) {\n if (p.openElements.hasInButtonScope($.P)) {\n p._closePElement();\n }\n p._insertElement(token, NS.HTML);\n if (!inTemplate) {\n p.formElement = p.openElements.current;\n }\n }\n}\nfunction listItemStartTagInBody(p, token) {\n p.framesetOk = false;\n const tn = token.tagID;\n for (let i = p.openElements.stackTop; i >= 0; i--) {\n const elementId = p.openElements.tagIDs[i];\n if ((tn === $.LI && elementId === $.LI) ||\n ((tn === $.DD || tn === $.DT) && (elementId === $.DD || elementId === $.DT))) {\n p.openElements.generateImpliedEndTagsWithExclusion(elementId);\n p.openElements.popUntilTagNamePopped(elementId);\n break;\n }\n if (elementId !== $.ADDRESS &&\n elementId !== $.DIV &&\n elementId !== $.P &&\n p._isSpecialElement(p.openElements.items[i], elementId)) {\n break;\n }\n }\n if (p.openElements.hasInButtonScope($.P)) {\n p._closePElement();\n }\n p._insertElement(token, NS.HTML);\n}\nfunction plaintextStartTagInBody(p, token) {\n if (p.openElements.hasInButtonScope($.P)) {\n p._closePElement();\n }\n p._insertElement(token, NS.HTML);\n p.tokenizer.state = TokenizerMode.PLAINTEXT;\n}\nfunction buttonStartTagInBody(p, token) {\n if (p.openElements.hasInScope($.BUTTON)) {\n p.openElements.generateImpliedEndTags();\n p.openElements.popUntilTagNamePopped($.BUTTON);\n }\n p._reconstructActiveFormattingElements();\n p._insertElement(token, NS.HTML);\n p.framesetOk = false;\n}\nfunction aStartTagInBody(p, token) {\n const activeElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName(TN.A);\n if (activeElementEntry) {\n callAdoptionAgency(p, token);\n p.openElements.remove(activeElementEntry.element);\n p.activeFormattingElements.removeEntry(activeElementEntry);\n }\n p._reconstructActiveFormattingElements();\n p._insertElement(token, NS.HTML);\n p.activeFormattingElements.pushElement(p.openElements.current, token);\n}\nfunction bStartTagInBody(p, token) {\n p._reconstructActiveFormattingElements();\n p._insertElement(token, NS.HTML);\n p.activeFormattingElements.pushElement(p.openElements.current, token);\n}\nfunction nobrStartTagInBody(p, token) {\n p._reconstructActiveFormattingElements();\n if (p.openElements.hasInScope($.NOBR)) {\n callAdoptionAgency(p, token);\n p._reconstructActiveFormattingElements();\n }\n p._insertElement(token, NS.HTML);\n p.activeFormattingElements.pushElement(p.openElements.current, token);\n}\nfunction appletStartTagInBody(p, token) {\n p._reconstructActiveFormattingElements();\n p._insertElement(token, NS.HTML);\n p.activeFormattingElements.insertMarker();\n p.framesetOk = false;\n}\nfunction tableStartTagInBody(p, token) {\n if (p.treeAdapter.getDocumentMode(p.document) !== DOCUMENT_MODE.QUIRKS && p.openElements.hasInButtonScope($.P)) {\n p._closePElement();\n }\n p._insertElement(token, NS.HTML);\n p.framesetOk = false;\n p.insertionMode = InsertionMode.IN_TABLE;\n}\nfunction areaStartTagInBody(p, token) {\n p._reconstructActiveFormattingElements();\n p._appendElement(token, NS.HTML);\n p.framesetOk = false;\n token.ackSelfClosing = true;\n}\nfunction isHiddenInput(token) {\n const inputType = getTokenAttr(token, ATTRS.TYPE);\n return inputType != null && inputType.toLowerCase() === HIDDEN_INPUT_TYPE;\n}\nfunction inputStartTagInBody(p, token) {\n p._reconstructActiveFormattingElements();\n p._appendElement(token, NS.HTML);\n if (!isHiddenInput(token)) {\n p.framesetOk = false;\n }\n token.ackSelfClosing = true;\n}\nfunction paramStartTagInBody(p, token) {\n p._appendElement(token, NS.HTML);\n token.ackSelfClosing = true;\n}\nfunction hrStartTagInBody(p, token) {\n if (p.openElements.hasInButtonScope($.P)) {\n p._closePElement();\n }\n p._appendElement(token, NS.HTML);\n p.framesetOk = false;\n token.ackSelfClosing = true;\n}\nfunction imageStartTagInBody(p, token) {\n token.tagName = TN.IMG;\n token.tagID = $.IMG;\n areaStartTagInBody(p, token);\n}\nfunction textareaStartTagInBody(p, token) {\n p._insertElement(token, NS.HTML);\n //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move\n //on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.)\n p.skipNextNewLine = true;\n p.tokenizer.state = TokenizerMode.RCDATA;\n p.originalInsertionMode = p.insertionMode;\n p.framesetOk = false;\n p.insertionMode = InsertionMode.TEXT;\n}\nfunction xmpStartTagInBody(p, token) {\n if (p.openElements.hasInButtonScope($.P)) {\n p._closePElement();\n }\n p._reconstructActiveFormattingElements();\n p.framesetOk = false;\n p._switchToTextParsing(token, TokenizerMode.RAWTEXT);\n}\nfunction iframeStartTagInBody(p, token) {\n p.framesetOk = false;\n p._switchToTextParsing(token, TokenizerMode.RAWTEXT);\n}\n//NOTE: here we assume that we always act as an user agent with enabled plugins, so we parse\n// as rawtext.\nfunction noembedStartTagInBody(p, token) {\n p._switchToTextParsing(token, TokenizerMode.RAWTEXT);\n}\nfunction selectStartTagInBody(p, token) {\n p._reconstructActiveFormattingElements();\n p._insertElement(token, NS.HTML);\n p.framesetOk = false;\n p.insertionMode =\n p.insertionMode === InsertionMode.IN_TABLE ||\n p.insertionMode === InsertionMode.IN_CAPTION ||\n p.insertionMode === InsertionMode.IN_TABLE_BODY ||\n p.insertionMode === InsertionMode.IN_ROW ||\n p.insertionMode === InsertionMode.IN_CELL\n ? InsertionMode.IN_SELECT_IN_TABLE\n : InsertionMode.IN_SELECT;\n}\nfunction optgroupStartTagInBody(p, token) {\n if (p.openElements.currentTagId === $.OPTION) {\n p.openElements.pop();\n }\n p._reconstructActiveFormattingElements();\n p._insertElement(token, NS.HTML);\n}\nfunction rbStartTagInBody(p, token) {\n if (p.openElements.hasInScope($.RUBY)) {\n p.openElements.generateImpliedEndTags();\n }\n p._insertElement(token, NS.HTML);\n}\nfunction rtStartTagInBody(p, token) {\n if (p.openElements.hasInScope($.RUBY)) {\n p.openElements.generateImpliedEndTagsWithExclusion($.RTC);\n }\n p._insertElement(token, NS.HTML);\n}\nfunction mathStartTagInBody(p, token) {\n p._reconstructActiveFormattingElements();\n foreignContent.adjustTokenMathMLAttrs(token);\n foreignContent.adjustTokenXMLAttrs(token);\n if (token.selfClosing) {\n p._appendElement(token, NS.MATHML);\n }\n else {\n p._insertElement(token, NS.MATHML);\n }\n token.ackSelfClosing = true;\n}\nfunction svgStartTagInBody(p, token) {\n p._reconstructActiveFormattingElements();\n foreignContent.adjustTokenSVGAttrs(token);\n foreignContent.adjustTokenXMLAttrs(token);\n if (token.selfClosing) {\n p._appendElement(token, NS.SVG);\n }\n else {\n p._insertElement(token, NS.SVG);\n }\n token.ackSelfClosing = true;\n}\nfunction genericStartTagInBody(p, token) {\n p._reconstructActiveFormattingElements();\n p._insertElement(token, NS.HTML);\n}\nfunction startTagInBody(p, token) {\n switch (token.tagID) {\n case $.I:\n case $.S:\n case $.B:\n case $.U:\n case $.EM:\n case $.TT:\n case $.BIG:\n case $.CODE:\n case $.FONT:\n case $.SMALL:\n case $.STRIKE:\n case $.STRONG: {\n bStartTagInBody(p, token);\n break;\n }\n case $.A: {\n aStartTagInBody(p, token);\n break;\n }\n case $.H1:\n case $.H2:\n case $.H3:\n case $.H4:\n case $.H5:\n case $.H6: {\n numberedHeaderStartTagInBody(p, token);\n break;\n }\n case $.P:\n case $.DL:\n case $.OL:\n case $.UL:\n case $.DIV:\n case $.DIR:\n case $.NAV:\n case $.MAIN:\n case $.MENU:\n case $.ASIDE:\n case $.CENTER:\n case $.FIGURE:\n case $.FOOTER:\n case $.HEADER:\n case $.HGROUP:\n case $.DIALOG:\n case $.DETAILS:\n case $.ADDRESS:\n case $.ARTICLE:\n case $.SECTION:\n case $.SUMMARY:\n case $.FIELDSET:\n case $.BLOCKQUOTE:\n case $.FIGCAPTION: {\n addressStartTagInBody(p, token);\n break;\n }\n case $.LI:\n case $.DD:\n case $.DT: {\n listItemStartTagInBody(p, token);\n break;\n }\n case $.BR:\n case $.IMG:\n case $.WBR:\n case $.AREA:\n case $.EMBED:\n case $.KEYGEN: {\n areaStartTagInBody(p, token);\n break;\n }\n case $.HR: {\n hrStartTagInBody(p, token);\n break;\n }\n case $.RB:\n case $.RTC: {\n rbStartTagInBody(p, token);\n break;\n }\n case $.RT:\n case $.RP: {\n rtStartTagInBody(p, token);\n break;\n }\n case $.PRE:\n case $.LISTING: {\n preStartTagInBody(p, token);\n break;\n }\n case $.XMP: {\n xmpStartTagInBody(p, token);\n break;\n }\n case $.SVG: {\n svgStartTagInBody(p, token);\n break;\n }\n case $.HTML: {\n htmlStartTagInBody(p, token);\n break;\n }\n case $.BASE:\n case $.LINK:\n case $.META:\n case $.STYLE:\n case $.TITLE:\n case $.SCRIPT:\n case $.BGSOUND:\n case $.BASEFONT:\n case $.TEMPLATE: {\n startTagInHead(p, token);\n break;\n }\n case $.BODY: {\n bodyStartTagInBody(p, token);\n break;\n }\n case $.FORM: {\n formStartTagInBody(p, token);\n break;\n }\n case $.NOBR: {\n nobrStartTagInBody(p, token);\n break;\n }\n case $.MATH: {\n mathStartTagInBody(p, token);\n break;\n }\n case $.TABLE: {\n tableStartTagInBody(p, token);\n break;\n }\n case $.INPUT: {\n inputStartTagInBody(p, token);\n break;\n }\n case $.PARAM:\n case $.TRACK:\n case $.SOURCE: {\n paramStartTagInBody(p, token);\n break;\n }\n case $.IMAGE: {\n imageStartTagInBody(p, token);\n break;\n }\n case $.BUTTON: {\n buttonStartTagInBody(p, token);\n break;\n }\n case $.APPLET:\n case $.OBJECT:\n case $.MARQUEE: {\n appletStartTagInBody(p, token);\n break;\n }\n case $.IFRAME: {\n iframeStartTagInBody(p, token);\n break;\n }\n case $.SELECT: {\n selectStartTagInBody(p, token);\n break;\n }\n case $.OPTION:\n case $.OPTGROUP: {\n optgroupStartTagInBody(p, token);\n break;\n }\n case $.NOEMBED: {\n noembedStartTagInBody(p, token);\n break;\n }\n case $.FRAMESET: {\n framesetStartTagInBody(p, token);\n break;\n }\n case $.TEXTAREA: {\n textareaStartTagInBody(p, token);\n break;\n }\n case $.NOSCRIPT: {\n if (p.options.scriptingEnabled) {\n noembedStartTagInBody(p, token);\n }\n else {\n genericStartTagInBody(p, token);\n }\n break;\n }\n case $.PLAINTEXT: {\n plaintextStartTagInBody(p, token);\n break;\n }\n case $.COL:\n case $.TH:\n case $.TD:\n case $.TR:\n case $.HEAD:\n case $.FRAME:\n case $.TBODY:\n case $.TFOOT:\n case $.THEAD:\n case $.CAPTION:\n case $.COLGROUP: {\n // Ignore token\n break;\n }\n default: {\n genericStartTagInBody(p, token);\n }\n }\n}\nfunction bodyEndTagInBody(p, token) {\n if (p.openElements.hasInScope($.BODY)) {\n p.insertionMode = InsertionMode.AFTER_BODY;\n //NOTE: is never popped from the stack, so we need to updated\n //the end location explicitly.\n if (p.options.sourceCodeLocationInfo) {\n const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement();\n if (bodyElement) {\n p._setEndLocation(bodyElement, token);\n }\n }\n }\n}\nfunction htmlEndTagInBody(p, token) {\n if (p.openElements.hasInScope($.BODY)) {\n p.insertionMode = InsertionMode.AFTER_BODY;\n endTagAfterBody(p, token);\n }\n}\nfunction addressEndTagInBody(p, token) {\n const tn = token.tagID;\n if (p.openElements.hasInScope(tn)) {\n p.openElements.generateImpliedEndTags();\n p.openElements.popUntilTagNamePopped(tn);\n }\n}\nfunction formEndTagInBody(p) {\n const inTemplate = p.openElements.tmplCount > 0;\n const { formElement } = p;\n if (!inTemplate) {\n p.formElement = null;\n }\n if ((formElement || inTemplate) && p.openElements.hasInScope($.FORM)) {\n p.openElements.generateImpliedEndTags();\n if (inTemplate) {\n p.openElements.popUntilTagNamePopped($.FORM);\n }\n else if (formElement) {\n p.openElements.remove(formElement);\n }\n }\n}\nfunction pEndTagInBody(p) {\n if (!p.openElements.hasInButtonScope($.P)) {\n p._insertFakeElement(TN.P, $.P);\n }\n p._closePElement();\n}\nfunction liEndTagInBody(p) {\n if (p.openElements.hasInListItemScope($.LI)) {\n p.openElements.generateImpliedEndTagsWithExclusion($.LI);\n p.openElements.popUntilTagNamePopped($.LI);\n }\n}\nfunction ddEndTagInBody(p, token) {\n const tn = token.tagID;\n if (p.openElements.hasInScope(tn)) {\n p.openElements.generateImpliedEndTagsWithExclusion(tn);\n p.openElements.popUntilTagNamePopped(tn);\n }\n}\nfunction numberedHeaderEndTagInBody(p) {\n if (p.openElements.hasNumberedHeaderInScope()) {\n p.openElements.generateImpliedEndTags();\n p.openElements.popUntilNumberedHeaderPopped();\n }\n}\nfunction appletEndTagInBody(p, token) {\n const tn = token.tagID;\n if (p.openElements.hasInScope(tn)) {\n p.openElements.generateImpliedEndTags();\n p.openElements.popUntilTagNamePopped(tn);\n p.activeFormattingElements.clearToLastMarker();\n }\n}\nfunction brEndTagInBody(p) {\n p._reconstructActiveFormattingElements();\n p._insertFakeElement(TN.BR, $.BR);\n p.openElements.pop();\n p.framesetOk = false;\n}\nfunction genericEndTagInBody(p, token) {\n const tn = token.tagName;\n const tid = token.tagID;\n for (let i = p.openElements.stackTop; i > 0; i--) {\n const element = p.openElements.items[i];\n const elementId = p.openElements.tagIDs[i];\n // Compare the tag name here, as the tag might not be a known tag with an ID.\n if (tid === elementId && (tid !== $.UNKNOWN || p.treeAdapter.getTagName(element) === tn)) {\n p.openElements.generateImpliedEndTagsWithExclusion(tid);\n if (p.openElements.stackTop >= i)\n p.openElements.shortenToLength(i);\n break;\n }\n if (p._isSpecialElement(element, elementId)) {\n break;\n }\n }\n}\nfunction endTagInBody(p, token) {\n switch (token.tagID) {\n case $.A:\n case $.B:\n case $.I:\n case $.S:\n case $.U:\n case $.EM:\n case $.TT:\n case $.BIG:\n case $.CODE:\n case $.FONT:\n case $.NOBR:\n case $.SMALL:\n case $.STRIKE:\n case $.STRONG: {\n callAdoptionAgency(p, token);\n break;\n }\n case $.P: {\n pEndTagInBody(p);\n break;\n }\n case $.DL:\n case $.UL:\n case $.OL:\n case $.DIR:\n case $.DIV:\n case $.NAV:\n case $.PRE:\n case $.MAIN:\n case $.MENU:\n case $.ASIDE:\n case $.BUTTON:\n case $.CENTER:\n case $.FIGURE:\n case $.FOOTER:\n case $.HEADER:\n case $.HGROUP:\n case $.DIALOG:\n case $.ADDRESS:\n case $.ARTICLE:\n case $.DETAILS:\n case $.SECTION:\n case $.SUMMARY:\n case $.LISTING:\n case $.FIELDSET:\n case $.BLOCKQUOTE:\n case $.FIGCAPTION: {\n addressEndTagInBody(p, token);\n break;\n }\n case $.LI: {\n liEndTagInBody(p);\n break;\n }\n case $.DD:\n case $.DT: {\n ddEndTagInBody(p, token);\n break;\n }\n case $.H1:\n case $.H2:\n case $.H3:\n case $.H4:\n case $.H5:\n case $.H6: {\n numberedHeaderEndTagInBody(p);\n break;\n }\n case $.BR: {\n brEndTagInBody(p);\n break;\n }\n case $.BODY: {\n bodyEndTagInBody(p, token);\n break;\n }\n case $.HTML: {\n htmlEndTagInBody(p, token);\n break;\n }\n case $.FORM: {\n formEndTagInBody(p);\n break;\n }\n case $.APPLET:\n case $.OBJECT:\n case $.MARQUEE: {\n appletEndTagInBody(p, token);\n break;\n }\n case $.TEMPLATE: {\n templateEndTagInHead(p, token);\n break;\n }\n default: {\n genericEndTagInBody(p, token);\n }\n }\n}\nfunction eofInBody(p, token) {\n if (p.tmplInsertionModeStack.length > 0) {\n eofInTemplate(p, token);\n }\n else {\n stopParsing(p, token);\n }\n}\n// The \"text\" insertion mode\n//------------------------------------------------------------------\nfunction endTagInText(p, token) {\n var _a;\n if (token.tagID === $.SCRIPT) {\n (_a = p.scriptHandler) === null || _a === void 0 ? void 0 : _a.call(p, p.openElements.current);\n }\n p.openElements.pop();\n p.insertionMode = p.originalInsertionMode;\n}\nfunction eofInText(p, token) {\n p._err(token, ERR.eofInElementThatCanContainOnlyText);\n p.openElements.pop();\n p.insertionMode = p.originalInsertionMode;\n p.onEof(token);\n}\n// The \"in table\" insertion mode\n//------------------------------------------------------------------\nfunction characterInTable(p, token) {\n if (TABLE_STRUCTURE_TAGS.has(p.openElements.currentTagId)) {\n p.pendingCharacterTokens.length = 0;\n p.hasNonWhitespacePendingCharacterToken = false;\n p.originalInsertionMode = p.insertionMode;\n p.insertionMode = InsertionMode.IN_TABLE_TEXT;\n switch (token.type) {\n case TokenType.CHARACTER: {\n characterInTableText(p, token);\n break;\n }\n case TokenType.WHITESPACE_CHARACTER: {\n whitespaceCharacterInTableText(p, token);\n break;\n }\n // Ignore null\n }\n }\n else {\n tokenInTable(p, token);\n }\n}\nfunction captionStartTagInTable(p, token) {\n p.openElements.clearBackToTableContext();\n p.activeFormattingElements.insertMarker();\n p._insertElement(token, NS.HTML);\n p.insertionMode = InsertionMode.IN_CAPTION;\n}\nfunction colgroupStartTagInTable(p, token) {\n p.openElements.clearBackToTableContext();\n p._insertElement(token, NS.HTML);\n p.insertionMode = InsertionMode.IN_COLUMN_GROUP;\n}\nfunction colStartTagInTable(p, token) {\n p.openElements.clearBackToTableContext();\n p._insertFakeElement(TN.COLGROUP, $.COLGROUP);\n p.insertionMode = InsertionMode.IN_COLUMN_GROUP;\n startTagInColumnGroup(p, token);\n}\nfunction tbodyStartTagInTable(p, token) {\n p.openElements.clearBackToTableContext();\n p._insertElement(token, NS.HTML);\n p.insertionMode = InsertionMode.IN_TABLE_BODY;\n}\nfunction tdStartTagInTable(p, token) {\n p.openElements.clearBackToTableContext();\n p._insertFakeElement(TN.TBODY, $.TBODY);\n p.insertionMode = InsertionMode.IN_TABLE_BODY;\n startTagInTableBody(p, token);\n}\nfunction tableStartTagInTable(p, token) {\n if (p.openElements.hasInTableScope($.TABLE)) {\n p.openElements.popUntilTagNamePopped($.TABLE);\n p._resetInsertionMode();\n p._processStartTag(token);\n }\n}\nfunction inputStartTagInTable(p, token) {\n if (isHiddenInput(token)) {\n p._appendElement(token, NS.HTML);\n }\n else {\n tokenInTable(p, token);\n }\n token.ackSelfClosing = true;\n}\nfunction formStartTagInTable(p, token) {\n if (!p.formElement && p.openElements.tmplCount === 0) {\n p._insertElement(token, NS.HTML);\n p.formElement = p.openElements.current;\n p.openElements.pop();\n }\n}\nfunction startTagInTable(p, token) {\n switch (token.tagID) {\n case $.TD:\n case $.TH:\n case $.TR: {\n tdStartTagInTable(p, token);\n break;\n }\n case $.STYLE:\n case $.SCRIPT:\n case $.TEMPLATE: {\n startTagInHead(p, token);\n break;\n }\n case $.COL: {\n colStartTagInTable(p, token);\n break;\n }\n case $.FORM: {\n formStartTagInTable(p, token);\n break;\n }\n case $.TABLE: {\n tableStartTagInTable(p, token);\n break;\n }\n case $.TBODY:\n case $.TFOOT:\n case $.THEAD: {\n tbodyStartTagInTable(p, token);\n break;\n }\n case $.INPUT: {\n inputStartTagInTable(p, token);\n break;\n }\n case $.CAPTION: {\n captionStartTagInTable(p, token);\n break;\n }\n case $.COLGROUP: {\n colgroupStartTagInTable(p, token);\n break;\n }\n default: {\n tokenInTable(p, token);\n }\n }\n}\nfunction endTagInTable(p, token) {\n switch (token.tagID) {\n case $.TABLE: {\n if (p.openElements.hasInTableScope($.TABLE)) {\n p.openElements.popUntilTagNamePopped($.TABLE);\n p._resetInsertionMode();\n }\n break;\n }\n case $.TEMPLATE: {\n templateEndTagInHead(p, token);\n break;\n }\n case $.BODY:\n case $.CAPTION:\n case $.COL:\n case $.COLGROUP:\n case $.HTML:\n case $.TBODY:\n case $.TD:\n case $.TFOOT:\n case $.TH:\n case $.THEAD:\n case $.TR: {\n // Ignore token\n break;\n }\n default: {\n tokenInTable(p, token);\n }\n }\n}\nfunction tokenInTable(p, token) {\n const savedFosterParentingState = p.fosterParentingEnabled;\n p.fosterParentingEnabled = true;\n // Process token in `In Body` mode\n modeInBody(p, token);\n p.fosterParentingEnabled = savedFosterParentingState;\n}\n// The \"in table text\" insertion mode\n//------------------------------------------------------------------\nfunction whitespaceCharacterInTableText(p, token) {\n p.pendingCharacterTokens.push(token);\n}\nfunction characterInTableText(p, token) {\n p.pendingCharacterTokens.push(token);\n p.hasNonWhitespacePendingCharacterToken = true;\n}\nfunction tokenInTableText(p, token) {\n let i = 0;\n if (p.hasNonWhitespacePendingCharacterToken) {\n for (; i < p.pendingCharacterTokens.length; i++) {\n tokenInTable(p, p.pendingCharacterTokens[i]);\n }\n }\n else {\n for (; i < p.pendingCharacterTokens.length; i++) {\n p._insertCharacters(p.pendingCharacterTokens[i]);\n }\n }\n p.insertionMode = p.originalInsertionMode;\n p._processToken(token);\n}\n// The \"in caption\" insertion mode\n//------------------------------------------------------------------\nconst TABLE_VOID_ELEMENTS = new Set([$.CAPTION, $.COL, $.COLGROUP, $.TBODY, $.TD, $.TFOOT, $.TH, $.THEAD, $.TR]);\nfunction startTagInCaption(p, token) {\n const tn = token.tagID;\n if (TABLE_VOID_ELEMENTS.has(tn)) {\n if (p.openElements.hasInTableScope($.CAPTION)) {\n p.openElements.generateImpliedEndTags();\n p.openElements.popUntilTagNamePopped($.CAPTION);\n p.activeFormattingElements.clearToLastMarker();\n p.insertionMode = InsertionMode.IN_TABLE;\n startTagInTable(p, token);\n }\n }\n else {\n startTagInBody(p, token);\n }\n}\nfunction endTagInCaption(p, token) {\n const tn = token.tagID;\n switch (tn) {\n case $.CAPTION:\n case $.TABLE: {\n if (p.openElements.hasInTableScope($.CAPTION)) {\n p.openElements.generateImpliedEndTags();\n p.openElements.popUntilTagNamePopped($.CAPTION);\n p.activeFormattingElements.clearToLastMarker();\n p.insertionMode = InsertionMode.IN_TABLE;\n if (tn === $.TABLE) {\n endTagInTable(p, token);\n }\n }\n break;\n }\n case $.BODY:\n case $.COL:\n case $.COLGROUP:\n case $.HTML:\n case $.TBODY:\n case $.TD:\n case $.TFOOT:\n case $.TH:\n case $.THEAD:\n case $.TR: {\n // Ignore token\n break;\n }\n default: {\n endTagInBody(p, token);\n }\n }\n}\n// The \"in column group\" insertion mode\n//------------------------------------------------------------------\nfunction startTagInColumnGroup(p, token) {\n switch (token.tagID) {\n case $.HTML: {\n startTagInBody(p, token);\n break;\n }\n case $.COL: {\n p._appendElement(token, NS.HTML);\n token.ackSelfClosing = true;\n break;\n }\n case $.TEMPLATE: {\n startTagInHead(p, token);\n break;\n }\n default: {\n tokenInColumnGroup(p, token);\n }\n }\n}\nfunction endTagInColumnGroup(p, token) {\n switch (token.tagID) {\n case $.COLGROUP: {\n if (p.openElements.currentTagId === $.COLGROUP) {\n p.openElements.pop();\n p.insertionMode = InsertionMode.IN_TABLE;\n }\n break;\n }\n case $.TEMPLATE: {\n templateEndTagInHead(p, token);\n break;\n }\n case $.COL: {\n // Ignore token\n break;\n }\n default: {\n tokenInColumnGroup(p, token);\n }\n }\n}\nfunction tokenInColumnGroup(p, token) {\n if (p.openElements.currentTagId === $.COLGROUP) {\n p.openElements.pop();\n p.insertionMode = InsertionMode.IN_TABLE;\n p._processToken(token);\n }\n}\n// The \"in table body\" insertion mode\n//------------------------------------------------------------------\nfunction startTagInTableBody(p, token) {\n switch (token.tagID) {\n case $.TR: {\n p.openElements.clearBackToTableBodyContext();\n p._insertElement(token, NS.HTML);\n p.insertionMode = InsertionMode.IN_ROW;\n break;\n }\n case $.TH:\n case $.TD: {\n p.openElements.clearBackToTableBodyContext();\n p._insertFakeElement(TN.TR, $.TR);\n p.insertionMode = InsertionMode.IN_ROW;\n startTagInRow(p, token);\n break;\n }\n case $.CAPTION:\n case $.COL:\n case $.COLGROUP:\n case $.TBODY:\n case $.TFOOT:\n case $.THEAD: {\n if (p.openElements.hasTableBodyContextInTableScope()) {\n p.openElements.clearBackToTableBodyContext();\n p.openElements.pop();\n p.insertionMode = InsertionMode.IN_TABLE;\n startTagInTable(p, token);\n }\n break;\n }\n default: {\n startTagInTable(p, token);\n }\n }\n}\nfunction endTagInTableBody(p, token) {\n const tn = token.tagID;\n switch (token.tagID) {\n case $.TBODY:\n case $.TFOOT:\n case $.THEAD: {\n if (p.openElements.hasInTableScope(tn)) {\n p.openElements.clearBackToTableBodyContext();\n p.openElements.pop();\n p.insertionMode = InsertionMode.IN_TABLE;\n }\n break;\n }\n case $.TABLE: {\n if (p.openElements.hasTableBodyContextInTableScope()) {\n p.openElements.clearBackToTableBodyContext();\n p.openElements.pop();\n p.insertionMode = InsertionMode.IN_TABLE;\n endTagInTable(p, token);\n }\n break;\n }\n case $.BODY:\n case $.CAPTION:\n case $.COL:\n case $.COLGROUP:\n case $.HTML:\n case $.TD:\n case $.TH:\n case $.TR: {\n // Ignore token\n break;\n }\n default: {\n endTagInTable(p, token);\n }\n }\n}\n// The \"in row\" insertion mode\n//------------------------------------------------------------------\nfunction startTagInRow(p, token) {\n switch (token.tagID) {\n case $.TH:\n case $.TD: {\n p.openElements.clearBackToTableRowContext();\n p._insertElement(token, NS.HTML);\n p.insertionMode = InsertionMode.IN_CELL;\n p.activeFormattingElements.insertMarker();\n break;\n }\n case $.CAPTION:\n case $.COL:\n case $.COLGROUP:\n case $.TBODY:\n case $.TFOOT:\n case $.THEAD:\n case $.TR: {\n if (p.openElements.hasInTableScope($.TR)) {\n p.openElements.clearBackToTableRowContext();\n p.openElements.pop();\n p.insertionMode = InsertionMode.IN_TABLE_BODY;\n startTagInTableBody(p, token);\n }\n break;\n }\n default: {\n startTagInTable(p, token);\n }\n }\n}\nfunction endTagInRow(p, token) {\n switch (token.tagID) {\n case $.TR: {\n if (p.openElements.hasInTableScope($.TR)) {\n p.openElements.clearBackToTableRowContext();\n p.openElements.pop();\n p.insertionMode = InsertionMode.IN_TABLE_BODY;\n }\n break;\n }\n case $.TABLE: {\n if (p.openElements.hasInTableScope($.TR)) {\n p.openElements.clearBackToTableRowContext();\n p.openElements.pop();\n p.insertionMode = InsertionMode.IN_TABLE_BODY;\n endTagInTableBody(p, token);\n }\n break;\n }\n case $.TBODY:\n case $.TFOOT:\n case $.THEAD: {\n if (p.openElements.hasInTableScope(token.tagID) || p.openElements.hasInTableScope($.TR)) {\n p.openElements.clearBackToTableRowContext();\n p.openElements.pop();\n p.insertionMode = InsertionMode.IN_TABLE_BODY;\n endTagInTableBody(p, token);\n }\n break;\n }\n case $.BODY:\n case $.CAPTION:\n case $.COL:\n case $.COLGROUP:\n case $.HTML:\n case $.TD:\n case $.TH: {\n // Ignore end tag\n break;\n }\n default: {\n endTagInTable(p, token);\n }\n }\n}\n// The \"in cell\" insertion mode\n//------------------------------------------------------------------\nfunction startTagInCell(p, token) {\n const tn = token.tagID;\n if (TABLE_VOID_ELEMENTS.has(tn)) {\n if (p.openElements.hasInTableScope($.TD) || p.openElements.hasInTableScope($.TH)) {\n p._closeTableCell();\n startTagInRow(p, token);\n }\n }\n else {\n startTagInBody(p, token);\n }\n}\nfunction endTagInCell(p, token) {\n const tn = token.tagID;\n switch (tn) {\n case $.TD:\n case $.TH: {\n if (p.openElements.hasInTableScope(tn)) {\n p.openElements.generateImpliedEndTags();\n p.openElements.popUntilTagNamePopped(tn);\n p.activeFormattingElements.clearToLastMarker();\n p.insertionMode = InsertionMode.IN_ROW;\n }\n break;\n }\n case $.TABLE:\n case $.TBODY:\n case $.TFOOT:\n case $.THEAD:\n case $.TR: {\n if (p.openElements.hasInTableScope(tn)) {\n p._closeTableCell();\n endTagInRow(p, token);\n }\n break;\n }\n case $.BODY:\n case $.CAPTION:\n case $.COL:\n case $.COLGROUP:\n case $.HTML: {\n // Ignore token\n break;\n }\n default: {\n endTagInBody(p, token);\n }\n }\n}\n// The \"in select\" insertion mode\n//------------------------------------------------------------------\nfunction startTagInSelect(p, token) {\n switch (token.tagID) {\n case $.HTML: {\n startTagInBody(p, token);\n break;\n }\n case $.OPTION: {\n if (p.openElements.currentTagId === $.OPTION) {\n p.openElements.pop();\n }\n p._insertElement(token, NS.HTML);\n break;\n }\n case $.OPTGROUP: {\n if (p.openElements.currentTagId === $.OPTION) {\n p.openElements.pop();\n }\n if (p.openElements.currentTagId === $.OPTGROUP) {\n p.openElements.pop();\n }\n p._insertElement(token, NS.HTML);\n break;\n }\n case $.INPUT:\n case $.KEYGEN:\n case $.TEXTAREA:\n case $.SELECT: {\n if (p.openElements.hasInSelectScope($.SELECT)) {\n p.openElements.popUntilTagNamePopped($.SELECT);\n p._resetInsertionMode();\n if (token.tagID !== $.SELECT) {\n p._processStartTag(token);\n }\n }\n break;\n }\n case $.SCRIPT:\n case $.TEMPLATE: {\n startTagInHead(p, token);\n break;\n }\n default:\n // Do nothing\n }\n}\nfunction endTagInSelect(p, token) {\n switch (token.tagID) {\n case $.OPTGROUP: {\n if (p.openElements.stackTop > 0 &&\n p.openElements.currentTagId === $.OPTION &&\n p.openElements.tagIDs[p.openElements.stackTop - 1] === $.OPTGROUP) {\n p.openElements.pop();\n }\n if (p.openElements.currentTagId === $.OPTGROUP) {\n p.openElements.pop();\n }\n break;\n }\n case $.OPTION: {\n if (p.openElements.currentTagId === $.OPTION) {\n p.openElements.pop();\n }\n break;\n }\n case $.SELECT: {\n if (p.openElements.hasInSelectScope($.SELECT)) {\n p.openElements.popUntilTagNamePopped($.SELECT);\n p._resetInsertionMode();\n }\n break;\n }\n case $.TEMPLATE: {\n templateEndTagInHead(p, token);\n break;\n }\n default:\n // Do nothing\n }\n}\n// The \"in select in table\" insertion mode\n//------------------------------------------------------------------\nfunction startTagInSelectInTable(p, token) {\n const tn = token.tagID;\n if (tn === $.CAPTION ||\n tn === $.TABLE ||\n tn === $.TBODY ||\n tn === $.TFOOT ||\n tn === $.THEAD ||\n tn === $.TR ||\n tn === $.TD ||\n tn === $.TH) {\n p.openElements.popUntilTagNamePopped($.SELECT);\n p._resetInsertionMode();\n p._processStartTag(token);\n }\n else {\n startTagInSelect(p, token);\n }\n}\nfunction endTagInSelectInTable(p, token) {\n const tn = token.tagID;\n if (tn === $.CAPTION ||\n tn === $.TABLE ||\n tn === $.TBODY ||\n tn === $.TFOOT ||\n tn === $.THEAD ||\n tn === $.TR ||\n tn === $.TD ||\n tn === $.TH) {\n if (p.openElements.hasInTableScope(tn)) {\n p.openElements.popUntilTagNamePopped($.SELECT);\n p._resetInsertionMode();\n p.onEndTag(token);\n }\n }\n else {\n endTagInSelect(p, token);\n }\n}\n// The \"in template\" insertion mode\n//------------------------------------------------------------------\nfunction startTagInTemplate(p, token) {\n switch (token.tagID) {\n // First, handle tags that can start without a mode change\n case $.BASE:\n case $.BASEFONT:\n case $.BGSOUND:\n case $.LINK:\n case $.META:\n case $.NOFRAMES:\n case $.SCRIPT:\n case $.STYLE:\n case $.TEMPLATE:\n case $.TITLE: {\n startTagInHead(p, token);\n break;\n }\n // Re-process the token in the appropriate mode\n case $.CAPTION:\n case $.COLGROUP:\n case $.TBODY:\n case $.TFOOT:\n case $.THEAD: {\n p.tmplInsertionModeStack[0] = InsertionMode.IN_TABLE;\n p.insertionMode = InsertionMode.IN_TABLE;\n startTagInTable(p, token);\n break;\n }\n case $.COL: {\n p.tmplInsertionModeStack[0] = InsertionMode.IN_COLUMN_GROUP;\n p.insertionMode = InsertionMode.IN_COLUMN_GROUP;\n startTagInColumnGroup(p, token);\n break;\n }\n case $.TR: {\n p.tmplInsertionModeStack[0] = InsertionMode.IN_TABLE_BODY;\n p.insertionMode = InsertionMode.IN_TABLE_BODY;\n startTagInTableBody(p, token);\n break;\n }\n case $.TD:\n case $.TH: {\n p.tmplInsertionModeStack[0] = InsertionMode.IN_ROW;\n p.insertionMode = InsertionMode.IN_ROW;\n startTagInRow(p, token);\n break;\n }\n default: {\n p.tmplInsertionModeStack[0] = InsertionMode.IN_BODY;\n p.insertionMode = InsertionMode.IN_BODY;\n startTagInBody(p, token);\n }\n }\n}\nfunction endTagInTemplate(p, token) {\n if (token.tagID === $.TEMPLATE) {\n templateEndTagInHead(p, token);\n }\n}\nfunction eofInTemplate(p, token) {\n if (p.openElements.tmplCount > 0) {\n p.openElements.popUntilTagNamePopped($.TEMPLATE);\n p.activeFormattingElements.clearToLastMarker();\n p.tmplInsertionModeStack.shift();\n p._resetInsertionMode();\n p.onEof(token);\n }\n else {\n stopParsing(p, token);\n }\n}\n// The \"after body\" insertion mode\n//------------------------------------------------------------------\nfunction startTagAfterBody(p, token) {\n if (token.tagID === $.HTML) {\n startTagInBody(p, token);\n }\n else {\n tokenAfterBody(p, token);\n }\n}\nfunction endTagAfterBody(p, token) {\n var _a;\n if (token.tagID === $.HTML) {\n if (!p.fragmentContext) {\n p.insertionMode = InsertionMode.AFTER_AFTER_BODY;\n }\n //NOTE: is never popped from the stack, so we need to updated\n //the end location explicitly.\n if (p.options.sourceCodeLocationInfo && p.openElements.tagIDs[0] === $.HTML) {\n p._setEndLocation(p.openElements.items[0], token);\n // Update the body element, if it doesn't have an end tag\n const bodyElement = p.openElements.items[1];\n if (bodyElement && !((_a = p.treeAdapter.getNodeSourceCodeLocation(bodyElement)) === null || _a === void 0 ? void 0 : _a.endTag)) {\n p._setEndLocation(bodyElement, token);\n }\n }\n }\n else {\n tokenAfterBody(p, token);\n }\n}\nfunction tokenAfterBody(p, token) {\n p.insertionMode = InsertionMode.IN_BODY;\n modeInBody(p, token);\n}\n// The \"in frameset\" insertion mode\n//------------------------------------------------------------------\nfunction startTagInFrameset(p, token) {\n switch (token.tagID) {\n case $.HTML: {\n startTagInBody(p, token);\n break;\n }\n case $.FRAMESET: {\n p._insertElement(token, NS.HTML);\n break;\n }\n case $.FRAME: {\n p._appendElement(token, NS.HTML);\n token.ackSelfClosing = true;\n break;\n }\n case $.NOFRAMES: {\n startTagInHead(p, token);\n break;\n }\n default:\n // Do nothing\n }\n}\nfunction endTagInFrameset(p, token) {\n if (token.tagID === $.FRAMESET && !p.openElements.isRootHtmlElementCurrent()) {\n p.openElements.pop();\n if (!p.fragmentContext && p.openElements.currentTagId !== $.FRAMESET) {\n p.insertionMode = InsertionMode.AFTER_FRAMESET;\n }\n }\n}\n// The \"after frameset\" insertion mode\n//------------------------------------------------------------------\nfunction startTagAfterFrameset(p, token) {\n switch (token.tagID) {\n case $.HTML: {\n startTagInBody(p, token);\n break;\n }\n case $.NOFRAMES: {\n startTagInHead(p, token);\n break;\n }\n default:\n // Do nothing\n }\n}\nfunction endTagAfterFrameset(p, token) {\n if (token.tagID === $.HTML) {\n p.insertionMode = InsertionMode.AFTER_AFTER_FRAMESET;\n }\n}\n// The \"after after body\" insertion mode\n//------------------------------------------------------------------\nfunction startTagAfterAfterBody(p, token) {\n if (token.tagID === $.HTML) {\n startTagInBody(p, token);\n }\n else {\n tokenAfterAfterBody(p, token);\n }\n}\nfunction tokenAfterAfterBody(p, token) {\n p.insertionMode = InsertionMode.IN_BODY;\n modeInBody(p, token);\n}\n// The \"after after frameset\" insertion mode\n//------------------------------------------------------------------\nfunction startTagAfterAfterFrameset(p, token) {\n switch (token.tagID) {\n case $.HTML: {\n startTagInBody(p, token);\n break;\n }\n case $.NOFRAMES: {\n startTagInHead(p, token);\n break;\n }\n default:\n // Do nothing\n }\n}\n// The rules for parsing tokens in foreign content\n//------------------------------------------------------------------\nfunction nullCharacterInForeignContent(p, token) {\n token.chars = unicode.REPLACEMENT_CHARACTER;\n p._insertCharacters(token);\n}\nfunction characterInForeignContent(p, token) {\n p._insertCharacters(token);\n p.framesetOk = false;\n}\nfunction popUntilHtmlOrIntegrationPoint(p) {\n while (p.treeAdapter.getNamespaceURI(p.openElements.current) !== NS.HTML &&\n !p._isIntegrationPoint(p.openElements.currentTagId, p.openElements.current)) {\n p.openElements.pop();\n }\n}\nfunction startTagInForeignContent(p, token) {\n if (foreignContent.causesExit(token)) {\n popUntilHtmlOrIntegrationPoint(p);\n p._startTagOutsideForeignContent(token);\n }\n else {\n const current = p._getAdjustedCurrentElement();\n const currentNs = p.treeAdapter.getNamespaceURI(current);\n if (currentNs === NS.MATHML) {\n foreignContent.adjustTokenMathMLAttrs(token);\n }\n else if (currentNs === NS.SVG) {\n foreignContent.adjustTokenSVGTagName(token);\n foreignContent.adjustTokenSVGAttrs(token);\n }\n foreignContent.adjustTokenXMLAttrs(token);\n if (token.selfClosing) {\n p._appendElement(token, currentNs);\n }\n else {\n p._insertElement(token, currentNs);\n }\n token.ackSelfClosing = true;\n }\n}\nfunction endTagInForeignContent(p, token) {\n if (token.tagID === $.P || token.tagID === $.BR) {\n popUntilHtmlOrIntegrationPoint(p);\n p._endTagOutsideForeignContent(token);\n return;\n }\n for (let i = p.openElements.stackTop; i > 0; i--) {\n const element = p.openElements.items[i];\n if (p.treeAdapter.getNamespaceURI(element) === NS.HTML) {\n p._endTagOutsideForeignContent(token);\n break;\n }\n const tagName = p.treeAdapter.getTagName(element);\n if (tagName.toLowerCase() === token.tagName) {\n //NOTE: update the token tag name for `_setEndLocation`.\n token.tagName = tagName;\n p.openElements.shortenToLength(i);\n break;\n }\n }\n}\n",null,"import { TAG_NAMES as $, NS, hasUnescapedText } from '../common/html.js';\nimport { escapeText, escapeAttribute } from 'entities/lib/escape.js';\nimport { defaultTreeAdapter } from '../tree-adapters/default.js';\n// Sets\nconst VOID_ELEMENTS = new Set([\n $.AREA,\n $.BASE,\n $.BASEFONT,\n $.BGSOUND,\n $.BR,\n $.COL,\n $.EMBED,\n $.FRAME,\n $.HR,\n $.IMG,\n $.INPUT,\n $.KEYGEN,\n $.LINK,\n $.META,\n $.PARAM,\n $.SOURCE,\n $.TRACK,\n $.WBR,\n]);\nfunction isVoidElement(node, options) {\n return (options.treeAdapter.isElementNode(node) &&\n options.treeAdapter.getNamespaceURI(node) === NS.HTML &&\n VOID_ELEMENTS.has(options.treeAdapter.getTagName(node)));\n}\nconst defaultOpts = { treeAdapter: defaultTreeAdapter, scriptingEnabled: true };\n/**\n * Serializes an AST node to an HTML string.\n *\n * @example\n *\n * ```js\n * const parse5 = require('parse5');\n *\n * const document = parse5.parse('Hi there!');\n *\n * // Serializes a document.\n * const html = parse5.serialize(document);\n *\n * // Serializes the element content.\n * const str = parse5.serialize(document.childNodes[1]);\n *\n * console.log(str); //> 'Hi there!'\n * ```\n *\n * @param node Node to serialize.\n * @param options Serialization options.\n */\nexport function serialize(node, options) {\n const opts = { ...defaultOpts, ...options };\n if (isVoidElement(node, opts)) {\n return '';\n }\n return serializeChildNodes(node, opts);\n}\n/**\n * Serializes an AST element node to an HTML string, including the element node.\n *\n * @example\n *\n * ```js\n * const parse5 = require('parse5');\n *\n * const document = parse5.parseFragment('Hello, world!
');\n *\n * // Serializes the element.\n * const html = parse5.serializeOuter(document.childNodes[0]);\n *\n * console.log(str); //> '
Hello, world!
'\n * ```\n *\n * @param node Node to serialize.\n * @param options Serialization options.\n */\nexport function serializeOuter(node, options) {\n const opts = { ...defaultOpts, ...options };\n return serializeNode(node, opts);\n}\nfunction serializeChildNodes(parentNode, options) {\n let html = '';\n // Get container of the child nodes\n const container = options.treeAdapter.isElementNode(parentNode) &&\n options.treeAdapter.getTagName(parentNode) === $.TEMPLATE &&\n options.treeAdapter.getNamespaceURI(parentNode) === NS.HTML\n ? options.treeAdapter.getTemplateContent(parentNode)\n : parentNode;\n const childNodes = options.treeAdapter.getChildNodes(container);\n if (childNodes) {\n for (const currentNode of childNodes) {\n html += serializeNode(currentNode, options);\n }\n }\n return html;\n}\nfunction serializeNode(node, options) {\n if (options.treeAdapter.isElementNode(node)) {\n return serializeElement(node, options);\n }\n if (options.treeAdapter.isTextNode(node)) {\n return serializeTextNode(node, options);\n }\n if (options.treeAdapter.isCommentNode(node)) {\n return serializeCommentNode(node, options);\n }\n if (options.treeAdapter.isDocumentTypeNode(node)) {\n return serializeDocumentTypeNode(node, options);\n }\n // Return an empty string for unknown nodes\n return '';\n}\nfunction serializeElement(node, options) {\n const tn = options.treeAdapter.getTagName(node);\n return `<${tn}${serializeAttributes(node, options)}>${isVoidElement(node, options) ? '' : `${serializeChildNodes(node, options)}${tn}>`}`;\n}\nfunction serializeAttributes(node, { treeAdapter }) {\n let html = '';\n for (const attr of treeAdapter.getAttrList(node)) {\n html += ' ';\n if (!attr.namespace) {\n html += attr.name;\n }\n else\n switch (attr.namespace) {\n case NS.XML: {\n html += `xml:${attr.name}`;\n break;\n }\n case NS.XMLNS: {\n if (attr.name !== 'xmlns') {\n html += 'xmlns:';\n }\n html += attr.name;\n break;\n }\n case NS.XLINK: {\n html += `xlink:${attr.name}`;\n break;\n }\n default: {\n html += `${attr.prefix}:${attr.name}`;\n }\n }\n html += `=\"${escapeAttribute(attr.value)}\"`;\n }\n return html;\n}\nfunction serializeTextNode(node, options) {\n const { treeAdapter } = options;\n const content = treeAdapter.getTextNodeContent(node);\n const parent = treeAdapter.getParentNode(node);\n const parentTn = parent && treeAdapter.isElementNode(parent) && treeAdapter.getTagName(parent);\n return parentTn &&\n treeAdapter.getNamespaceURI(parent) === NS.HTML &&\n hasUnescapedText(parentTn, options.scriptingEnabled)\n ? content\n : escapeText(content);\n}\nfunction serializeCommentNode(node, { treeAdapter }) {\n return ``;\n}\nfunction serializeDocumentTypeNode(node, { treeAdapter }) {\n return ``;\n}\n","import { Parser } from './parser/index.js';\nexport { defaultTreeAdapter } from './tree-adapters/default.js';\nexport { /** @internal */ Parser } from './parser/index.js';\nexport { serialize, serializeOuter } from './serializer/index.js';\nexport { ERR as ErrorCodes } from './common/error-codes.js';\n/** @internal */\nexport * as foreignContent from './common/foreign-content.js';\n/** @internal */\nexport * as html from './common/html.js';\n/** @internal */\nexport * as Token from './common/token.js';\n/** @internal */\nexport { Tokenizer, TokenizerMode } from './tokenizer/index.js';\n// Shorthands\n/**\n * Parses an HTML string.\n *\n * @param html Input HTML string.\n * @param options Parsing options.\n * @returns Document\n *\n * @example\n *\n * ```js\n * const parse5 = require('parse5');\n *\n * const document = parse5.parse('Hi there!');\n *\n * console.log(document.childNodes[1].tagName); //> 'html'\n *```\n */\nexport function parse(html, options) {\n return Parser.parse(html, options);\n}\nexport function parseFragment(fragmentContext, html, options) {\n if (typeof fragmentContext === 'string') {\n options = html;\n html = fragmentContext;\n fragmentContext = null;\n }\n const parser = Parser.getFragmentParser(fragmentContext, options);\n parser.tokenizer.write(html, true);\n return parser.getFragment();\n}\n","import EventEmitter from 'events';\nimport { parse, parseFragment, serialize } from 'parse5';\n\n/**\n * @typedef {import('./index').default} Ultraviolet\n */\n\nclass HTML extends EventEmitter {\n /**\n *\n * @param {Ultraviolet} ctx\n */\n constructor(ctx) {\n super();\n this.ctx = ctx;\n this.rewriteUrl = ctx.rewriteUrl;\n this.sourceUrl = ctx.sourceUrl;\n }\n rewrite(str, options = {}) {\n if (!str) return str;\n return this.recast(\n str,\n (node) => {\n if (node.tagName) this.emit('element', node, 'rewrite');\n if (node.attr) this.emit('attr', node, 'rewrite');\n if (node.nodeName === '#text')\n this.emit('text', node, 'rewrite');\n },\n options\n );\n }\n source(str, options = {}) {\n if (!str) return str;\n return this.recast(\n str,\n (node) => {\n if (node.tagName) this.emit('element', node, 'source');\n if (node.attr) this.emit('attr', node, 'source');\n if (node.nodeName === '#text')\n this.emit('text', node, 'source');\n },\n options\n );\n }\n recast(str, fn, options = {}) {\n try {\n const ast = (options.document ? parse : parseFragment)(\n new String(str).toString()\n );\n this.iterate(ast, fn, options);\n return serialize(ast);\n } catch (e) {\n return str;\n }\n }\n iterate(ast, fn, fnOptions) {\n if (!ast) return ast;\n\n if (ast.tagName) {\n const element = new P5Element(ast, false, fnOptions);\n fn(element);\n if (ast.attrs) {\n for (const attr of ast.attrs) {\n if (!attr.skip)\n fn(new AttributeEvent(element, attr, fnOptions));\n }\n }\n }\n\n if (ast.childNodes) {\n for (const child of ast.childNodes) {\n if (!child.skip) this.iterate(child, fn, fnOptions);\n }\n }\n\n if (ast.nodeName === '#text') {\n fn(\n new TextEvent(\n ast,\n new P5Element(ast.parentNode),\n false,\n fnOptions\n )\n );\n }\n\n return ast;\n }\n wrapSrcset(str, meta = this.ctx.meta) {\n return str\n .split(',')\n .map((src) => {\n const parts = src.trimStart().split(' ');\n if (parts[0]) parts[0] = this.ctx.rewriteUrl(parts[0], meta);\n return parts.join(' ');\n })\n .join(', ');\n }\n unwrapSrcset(str, meta = this.ctx.meta) {\n return str\n .split(',')\n .map((src) => {\n const parts = src.trimStart().split(' ');\n if (parts[0]) parts[0] = this.ctx.sourceUrl(parts[0], meta);\n return parts.join(' ');\n })\n .join(', ');\n }\n static parse = parse;\n static parseFragment = parseFragment;\n static serialize = serialize;\n}\n\nclass P5Element extends EventEmitter {\n constructor(node, stream = false, options = {}) {\n super();\n this.stream = stream;\n this.node = node;\n this.options = options;\n }\n setAttribute(name, value) {\n for (const attr of this.attrs) {\n if (attr.name === name) {\n attr.value = value;\n return true;\n }\n }\n\n this.attrs.push({\n name,\n value,\n });\n }\n getAttribute(name) {\n const attr = this.attrs.find((attr) => attr.name === name) || {};\n return attr.value;\n }\n hasAttribute(name) {\n return !!this.attrs.find((attr) => attr.name === name);\n }\n removeAttribute(name) {\n const i = this.attrs.findIndex((attr) => attr.name === name);\n if (typeof i !== 'undefined') this.attrs.splice(i, 1);\n }\n get tagName() {\n return this.node.tagName;\n }\n set tagName(val) {\n this.node.tagName = val;\n }\n get childNodes() {\n return !this.stream ? this.node.childNodes : null;\n }\n get innerHTML() {\n return !this.stream\n ? serialize({\n nodeName: '#document-fragment',\n childNodes: this.childNodes,\n })\n : null;\n }\n set innerHTML(val) {\n if (!this.stream) this.node.childNodes = parseFragment(val).childNodes;\n }\n get outerHTML() {\n return !this.stream\n ? serialize({\n nodeName: '#document-fragment',\n childNodes: [this],\n })\n : null;\n }\n set outerHTML(val) {\n if (!this.stream)\n this.parentNode.childNodes.splice(\n this.parentNode.childNodes.findIndex(\n (node) => node === this.node\n ),\n 1,\n ...parseFragment(val).childNodes\n );\n }\n get textContent() {\n if (this.stream) return null;\n\n let str = '';\n this.iterate(this.node, (node) => {\n if (node.nodeName === '#text') str += node.value;\n });\n\n return str;\n }\n set textContent(val) {\n if (!this.stream)\n this.node.childNodes = [\n {\n nodeName: '#text',\n value: val,\n parentNode: this.node,\n },\n ];\n }\n get nodeName() {\n return this.node.nodeName;\n }\n get parentNode() {\n return this.node.parentNode\n ? new P5Element(this.node.parentNode)\n : null;\n }\n get attrs() {\n return this.node.attrs;\n }\n get namespaceURI() {\n return this.node.namespaceURI;\n }\n}\n\nclass AttributeEvent {\n constructor(node, attr, options = {}) {\n this.attr = attr;\n this.attrs = node.attrs;\n this.node = node;\n this.options = options;\n }\n delete() {\n const i = this.attrs.findIndex((attr) => attr === this.attr);\n\n this.attrs.splice(i, 1);\n\n Object.defineProperty(this, 'deleted', {\n get: () => true,\n });\n\n return true;\n }\n get name() {\n return this.attr.name;\n }\n\n set name(val) {\n this.attr.name = val;\n }\n get value() {\n return this.attr.value;\n }\n\n set value(val) {\n this.attr.value = val;\n }\n get deleted() {\n return false;\n }\n}\n\nclass TextEvent {\n constructor(node, element, stream = false, options = {}) {\n this.stream = stream;\n this.node = node;\n this.element = element;\n this.options = options;\n }\n get nodeName() {\n return this.node.nodeName;\n }\n get parentNode() {\n return this.element;\n }\n get value() {\n return this.stream ? this.node.text : this.node.value;\n }\n set value(val) {\n if (this.stream) this.node.text = val;\n else this.node.value = val;\n }\n}\n\nexport default HTML;\n","// CSS Syntax Module Level 3\n// https://www.w3.org/TR/css-syntax-3/\nexport const EOF = 0; //
\nexport const Ident = 1; // \nexport const Function = 2; // \nexport const AtKeyword = 3; // \nexport const Hash = 4; // \nexport const String = 5; // \nexport const BadString = 6; // \nexport const Url = 7; // \nexport const BadUrl = 8; // \nexport const Delim = 9; // \nexport const Number = 10; // \nexport const Percentage = 11; // \nexport const Dimension = 12; // \nexport const WhiteSpace = 13; // \nexport const CDO = 14; // \nexport const CDC = 15; // \nexport const Colon = 16; // :\nexport const Semicolon = 17; // ;\nexport const Comma = 18; // ,\nexport const LeftSquareBracket = 19; // <[-token>\nexport const RightSquareBracket = 20; // <]-token>\nexport const LeftParenthesis = 21; // <(-token>\nexport const RightParenthesis = 22; // <)-token>\nexport const LeftCurlyBracket = 23; // <{-token>\nexport const RightCurlyBracket = 24; // <}-token>\nexport const Comment = 25;\n","const EOF = 0;\n\n// https://drafts.csswg.org/css-syntax-3/\n// § 4.2. Definitions\n\n// digit\n// A code point between U+0030 DIGIT ZERO (0) and U+0039 DIGIT NINE (9).\nexport function isDigit(code) {\n return code >= 0x0030 && code <= 0x0039;\n}\n\n// hex digit\n// A digit, or a code point between U+0041 LATIN CAPITAL LETTER A (A) and U+0046 LATIN CAPITAL LETTER F (F),\n// or a code point between U+0061 LATIN SMALL LETTER A (a) and U+0066 LATIN SMALL LETTER F (f).\nexport function isHexDigit(code) {\n return (\n isDigit(code) || // 0 .. 9\n (code >= 0x0041 && code <= 0x0046) || // A .. F\n (code >= 0x0061 && code <= 0x0066) // a .. f\n );\n}\n\n// uppercase letter\n// A code point between U+0041 LATIN CAPITAL LETTER A (A) and U+005A LATIN CAPITAL LETTER Z (Z).\nexport function isUppercaseLetter(code) {\n return code >= 0x0041 && code <= 0x005A;\n}\n\n// lowercase letter\n// A code point between U+0061 LATIN SMALL LETTER A (a) and U+007A LATIN SMALL LETTER Z (z).\nexport function isLowercaseLetter(code) {\n return code >= 0x0061 && code <= 0x007A;\n}\n\n// letter\n// An uppercase letter or a lowercase letter.\nexport function isLetter(code) {\n return isUppercaseLetter(code) || isLowercaseLetter(code);\n}\n\n// non-ASCII code point\n// A code point with a value equal to or greater than U+0080 .\nexport function isNonAscii(code) {\n return code >= 0x0080;\n}\n\n// name-start code point\n// A letter, a non-ASCII code point, or U+005F LOW LINE (_).\nexport function isNameStart(code) {\n return isLetter(code) || isNonAscii(code) || code === 0x005F;\n}\n\n// name code point\n// A name-start code point, a digit, or U+002D HYPHEN-MINUS (-).\nexport function isName(code) {\n return isNameStart(code) || isDigit(code) || code === 0x002D;\n}\n\n// non-printable code point\n// A code point between U+0000 NULL and U+0008 BACKSPACE, or U+000B LINE TABULATION,\n// or a code point between U+000E SHIFT OUT and U+001F INFORMATION SEPARATOR ONE, or U+007F DELETE.\nexport function isNonPrintable(code) {\n return (\n (code >= 0x0000 && code <= 0x0008) ||\n (code === 0x000B) ||\n (code >= 0x000E && code <= 0x001F) ||\n (code === 0x007F)\n );\n}\n\n// newline\n// U+000A LINE FEED. Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are not included in this definition,\n// as they are converted to U+000A LINE FEED during preprocessing.\n// TODO: we doesn't do a preprocessing, so check a code point for U+000D CARRIAGE RETURN and U+000C FORM FEED\nexport function isNewline(code) {\n return code === 0x000A || code === 0x000D || code === 0x000C;\n}\n\n// whitespace\n// A newline, U+0009 CHARACTER TABULATION, or U+0020 SPACE.\nexport function isWhiteSpace(code) {\n return isNewline(code) || code === 0x0020 || code === 0x0009;\n}\n\n// § 4.3.8. Check if two code points are a valid escape\nexport function isValidEscape(first, second) {\n // If the first code point is not U+005C REVERSE SOLIDUS (\\), return false.\n if (first !== 0x005C) {\n return false;\n }\n\n // Otherwise, if the second code point is a newline or EOF, return false.\n if (isNewline(second) || second === EOF) {\n return false;\n }\n\n // Otherwise, return true.\n return true;\n}\n\n// § 4.3.9. Check if three code points would start an identifier\nexport function isIdentifierStart(first, second, third) {\n // Look at the first code point:\n\n // U+002D HYPHEN-MINUS\n if (first === 0x002D) {\n // If the second code point is a name-start code point or a U+002D HYPHEN-MINUS,\n // or the second and third code points are a valid escape, return true. Otherwise, return false.\n return (\n isNameStart(second) ||\n second === 0x002D ||\n isValidEscape(second, third)\n );\n }\n\n // name-start code point\n if (isNameStart(first)) {\n // Return true.\n return true;\n }\n\n // U+005C REVERSE SOLIDUS (\\)\n if (first === 0x005C) {\n // If the first and second code points are a valid escape, return true. Otherwise, return false.\n return isValidEscape(first, second);\n }\n\n // anything else\n // Return false.\n return false;\n}\n\n// § 4.3.10. Check if three code points would start a number\nexport function isNumberStart(first, second, third) {\n // Look at the first code point:\n\n // U+002B PLUS SIGN (+)\n // U+002D HYPHEN-MINUS (-)\n if (first === 0x002B || first === 0x002D) {\n // If the second code point is a digit, return true.\n if (isDigit(second)) {\n return 2;\n }\n\n // Otherwise, if the second code point is a U+002E FULL STOP (.)\n // and the third code point is a digit, return true.\n // Otherwise, return false.\n return second === 0x002E && isDigit(third) ? 3 : 0;\n }\n\n // U+002E FULL STOP (.)\n if (first === 0x002E) {\n // If the second code point is a digit, return true. Otherwise, return false.\n return isDigit(second) ? 2 : 0;\n }\n\n // digit\n if (isDigit(first)) {\n // Return true.\n return 1;\n }\n\n // anything else\n // Return false.\n return 0;\n}\n\n//\n// Misc\n//\n\n// detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark)\nexport function isBOM(code) {\n // UTF-16BE\n if (code === 0xFEFF) {\n return 1;\n }\n\n // UTF-16LE\n if (code === 0xFFFE) {\n return 1;\n }\n\n return 0;\n}\n\n// Fast code category\n// Only ASCII code points has a special meaning, that's why we define a maps for 0..127 codes only\nconst CATEGORY = new Array(0x80);\nexport const EofCategory = 0x80;\nexport const WhiteSpaceCategory = 0x82;\nexport const DigitCategory = 0x83;\nexport const NameStartCategory = 0x84;\nexport const NonPrintableCategory = 0x85;\n\nfor (let i = 0; i < CATEGORY.length; i++) {\n CATEGORY[i] =\n isWhiteSpace(i) && WhiteSpaceCategory ||\n isDigit(i) && DigitCategory ||\n isNameStart(i) && NameStartCategory ||\n isNonPrintable(i) && NonPrintableCategory ||\n i || EofCategory;\n}\n\nexport function charCodeCategory(code) {\n return code < 0x80 ? CATEGORY[code] : NameStartCategory;\n}\n","import {\n isDigit,\n isHexDigit,\n isUppercaseLetter,\n isName,\n isWhiteSpace,\n isValidEscape\n} from './char-code-definitions.js';\n\nfunction getCharCode(source, offset) {\n return offset < source.length ? source.charCodeAt(offset) : 0;\n}\n\nexport function getNewlineLength(source, offset, code) {\n if (code === 13 /* \\r */ && getCharCode(source, offset + 1) === 10 /* \\n */) {\n return 2;\n }\n\n return 1;\n}\n\nexport function cmpChar(testStr, offset, referenceCode) {\n let code = testStr.charCodeAt(offset);\n\n // code.toLowerCase() for A..Z\n if (isUppercaseLetter(code)) {\n code = code | 32;\n }\n\n return code === referenceCode;\n}\n\nexport function cmpStr(testStr, start, end, referenceStr) {\n if (end - start !== referenceStr.length) {\n return false;\n }\n\n if (start < 0 || end > testStr.length) {\n return false;\n }\n\n for (let i = start; i < end; i++) {\n const referenceCode = referenceStr.charCodeAt(i - start);\n let testCode = testStr.charCodeAt(i);\n\n // testCode.toLowerCase() for A..Z\n if (isUppercaseLetter(testCode)) {\n testCode = testCode | 32;\n }\n\n if (testCode !== referenceCode) {\n return false;\n }\n }\n\n return true;\n}\n\nexport function findWhiteSpaceStart(source, offset) {\n for (; offset >= 0; offset--) {\n if (!isWhiteSpace(source.charCodeAt(offset))) {\n break;\n }\n }\n\n return offset + 1;\n}\n\nexport function findWhiteSpaceEnd(source, offset) {\n for (; offset < source.length; offset++) {\n if (!isWhiteSpace(source.charCodeAt(offset))) {\n break;\n }\n }\n\n return offset;\n}\n\nexport function findDecimalNumberEnd(source, offset) {\n for (; offset < source.length; offset++) {\n if (!isDigit(source.charCodeAt(offset))) {\n break;\n }\n }\n\n return offset;\n}\n\n// § 4.3.7. Consume an escaped code point\nexport function consumeEscaped(source, offset) {\n // It assumes that the U+005C REVERSE SOLIDUS (\\) has already been consumed and\n // that the next input code point has already been verified to be part of a valid escape.\n offset += 2;\n\n // hex digit\n if (isHexDigit(getCharCode(source, offset - 1))) {\n // Consume as many hex digits as possible, but no more than 5.\n // Note that this means 1-6 hex digits have been consumed in total.\n for (const maxOffset = Math.min(source.length, offset + 5); offset < maxOffset; offset++) {\n if (!isHexDigit(getCharCode(source, offset))) {\n break;\n }\n }\n\n // If the next input code point is whitespace, consume it as well.\n const code = getCharCode(source, offset);\n if (isWhiteSpace(code)) {\n offset += getNewlineLength(source, offset, code);\n }\n }\n\n return offset;\n}\n\n// §4.3.11. Consume a name\n// Note: This algorithm does not do the verification of the first few code points that are necessary\n// to ensure the returned code points would constitute an . If that is the intended use,\n// ensure that the stream starts with an identifier before calling this algorithm.\nexport function consumeName(source, offset) {\n // Let result initially be an empty string.\n // Repeatedly consume the next input code point from the stream:\n for (; offset < source.length; offset++) {\n const code = source.charCodeAt(offset);\n\n // name code point\n if (isName(code)) {\n // Append the code point to result.\n continue;\n }\n\n // the stream starts with a valid escape\n if (isValidEscape(code, getCharCode(source, offset + 1))) {\n // Consume an escaped code point. Append the returned code point to result.\n offset = consumeEscaped(source, offset) - 1;\n continue;\n }\n\n // anything else\n // Reconsume the current input code point. Return result.\n break;\n }\n\n return offset;\n}\n\n// §4.3.12. Consume a number\nexport function consumeNumber(source, offset) {\n let code = source.charCodeAt(offset);\n\n // 2. If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-),\n // consume it and append it to repr.\n if (code === 0x002B || code === 0x002D) {\n code = source.charCodeAt(offset += 1);\n }\n\n // 3. While the next input code point is a digit, consume it and append it to repr.\n if (isDigit(code)) {\n offset = findDecimalNumberEnd(source, offset + 1);\n code = source.charCodeAt(offset);\n }\n\n // 4. If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then:\n if (code === 0x002E && isDigit(source.charCodeAt(offset + 1))) {\n // 4.1 Consume them.\n // 4.2 Append them to repr.\n offset += 2;\n\n // 4.3 Set type to \"number\".\n // TODO\n\n // 4.4 While the next input code point is a digit, consume it and append it to repr.\n\n offset = findDecimalNumberEnd(source, offset);\n }\n\n // 5. If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E)\n // or U+0065 LATIN SMALL LETTER E (e), ... , followed by a digit, then:\n if (cmpChar(source, offset, 101 /* e */)) {\n let sign = 0;\n code = source.charCodeAt(offset + 1);\n\n // ... optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+) ...\n if (code === 0x002D || code === 0x002B) {\n sign = 1;\n code = source.charCodeAt(offset + 2);\n }\n\n // ... followed by a digit\n if (isDigit(code)) {\n // 5.1 Consume them.\n // 5.2 Append them to repr.\n\n // 5.3 Set type to \"number\".\n // TODO\n\n // 5.4 While the next input code point is a digit, consume it and append it to repr.\n offset = findDecimalNumberEnd(source, offset + 1 + sign + 1);\n }\n }\n\n return offset;\n}\n\n// § 4.3.14. Consume the remnants of a bad url\n// ... its sole use is to consume enough of the input stream to reach a recovery point\n// where normal tokenizing can resume.\nexport function consumeBadUrlRemnants(source, offset) {\n // Repeatedly consume the next input code point from the stream:\n for (; offset < source.length; offset++) {\n const code = source.charCodeAt(offset);\n\n // U+0029 RIGHT PARENTHESIS ())\n // EOF\n if (code === 0x0029) {\n // Return.\n offset++;\n break;\n }\n\n if (isValidEscape(code, getCharCode(source, offset + 1))) {\n // Consume an escaped code point.\n // Note: This allows an escaped right parenthesis (\"\\)\") to be encountered\n // without ending the . This is otherwise identical to\n // the \"anything else\" clause.\n offset = consumeEscaped(source, offset);\n }\n }\n\n return offset;\n}\n\n// § 4.3.7. Consume an escaped code point\n// Note: This algorithm assumes that escaped is valid without leading U+005C REVERSE SOLIDUS (\\)\nexport function decodeEscaped(escaped) {\n // Single char escaped that's not a hex digit\n if (escaped.length === 1 && !isHexDigit(escaped.charCodeAt(0))) {\n return escaped[0];\n }\n\n // Interpret the hex digits as a hexadecimal number.\n let code = parseInt(escaped, 16);\n\n if (\n (code === 0) || // If this number is zero,\n (code >= 0xD800 && code <= 0xDFFF) || // or is for a surrogate,\n (code > 0x10FFFF) // or is greater than the maximum allowed code point\n ) {\n // ... return U+FFFD REPLACEMENT CHARACTER\n code = 0xFFFD;\n }\n\n // Otherwise, return the code point with that value.\n return String.fromCodePoint(code);\n}\n","export default [\n 'EOF-token',\n 'ident-token',\n 'function-token',\n 'at-keyword-token',\n 'hash-token',\n 'string-token',\n 'bad-string-token',\n 'url-token',\n 'bad-url-token',\n 'delim-token',\n 'number-token',\n 'percentage-token',\n 'dimension-token',\n 'whitespace-token',\n 'CDO-token',\n 'CDC-token',\n 'colon-token',\n 'semicolon-token',\n 'comma-token',\n '[-token',\n ']-token',\n '(-token',\n ')-token',\n '{-token',\n '}-token'\n];\n","const MIN_SIZE = 16 * 1024;\n\nexport function adoptBuffer(buffer = null, size) {\n if (buffer === null || buffer.length < size) {\n return new Uint32Array(Math.max(size + 1024, MIN_SIZE));\n }\n\n return buffer;\n};\n","import { adoptBuffer } from './adopt-buffer.js';\nimport { isBOM } from './char-code-definitions.js';\n\nconst N = 10;\nconst F = 12;\nconst R = 13;\n\nfunction computeLinesAndColumns(host) {\n const source = host.source;\n const sourceLength = source.length;\n const startOffset = source.length > 0 ? isBOM(source.charCodeAt(0)) : 0;\n const lines = adoptBuffer(host.lines, sourceLength);\n const columns = adoptBuffer(host.columns, sourceLength);\n let line = host.startLine;\n let column = host.startColumn;\n\n for (let i = startOffset; i < sourceLength; i++) {\n const code = source.charCodeAt(i);\n\n lines[i] = line;\n columns[i] = column++;\n\n if (code === N || code === R || code === F) {\n if (code === R && i + 1 < sourceLength && source.charCodeAt(i + 1) === N) {\n i++;\n lines[i] = line;\n columns[i] = column;\n }\n\n line++;\n column = 1;\n }\n }\n\n lines[sourceLength] = line;\n columns[sourceLength] = column;\n\n host.lines = lines;\n host.columns = columns;\n host.computed = true;\n}\n\nexport class OffsetToLocation {\n constructor() {\n this.lines = null;\n this.columns = null;\n this.computed = false;\n }\n setSource(source, startOffset = 0, startLine = 1, startColumn = 1) {\n this.source = source;\n this.startOffset = startOffset;\n this.startLine = startLine;\n this.startColumn = startColumn;\n this.computed = false;\n }\n getLocation(offset, filename) {\n if (!this.computed) {\n computeLinesAndColumns(this);\n }\n\n return {\n source: filename,\n offset: this.startOffset + offset,\n line: this.lines[offset],\n column: this.columns[offset]\n };\n }\n getLocationRange(start, end, filename) {\n if (!this.computed) {\n computeLinesAndColumns(this);\n }\n\n return {\n source: filename,\n start: {\n offset: this.startOffset + start,\n line: this.lines[start],\n column: this.columns[start]\n },\n end: {\n offset: this.startOffset + end,\n line: this.lines[end],\n column: this.columns[end]\n }\n };\n }\n};\n","import { adoptBuffer } from './adopt-buffer.js';\nimport { cmpStr } from './utils.js';\nimport tokenNames from './names.js';\nimport {\n WhiteSpace,\n Comment,\n Delim,\n EOF,\n Function as FunctionToken,\n LeftParenthesis,\n RightParenthesis,\n LeftSquareBracket,\n RightSquareBracket,\n LeftCurlyBracket,\n RightCurlyBracket\n} from './types.js';\n\nconst OFFSET_MASK = 0x00FFFFFF;\nconst TYPE_SHIFT = 24;\nconst balancePair = new Map([\n [FunctionToken, RightParenthesis],\n [LeftParenthesis, RightParenthesis],\n [LeftSquareBracket, RightSquareBracket],\n [LeftCurlyBracket, RightCurlyBracket]\n]);\n\nexport class TokenStream {\n constructor(source, tokenize) {\n this.setSource(source, tokenize);\n }\n reset() {\n this.eof = false;\n this.tokenIndex = -1;\n this.tokenType = 0;\n this.tokenStart = this.firstCharOffset;\n this.tokenEnd = this.firstCharOffset;\n }\n setSource(source = '', tokenize = () => {}) {\n source = String(source || '');\n\n const sourceLength = source.length;\n const offsetAndType = adoptBuffer(this.offsetAndType, source.length + 1); // +1 because of eof-token\n const balance = adoptBuffer(this.balance, source.length + 1);\n let tokenCount = 0;\n let balanceCloseType = 0;\n let balanceStart = 0;\n let firstCharOffset = -1;\n\n // capture buffers\n this.offsetAndType = null;\n this.balance = null;\n\n tokenize(source, (type, start, end) => {\n switch (type) {\n default:\n balance[tokenCount] = sourceLength;\n break;\n\n case balanceCloseType: {\n let balancePrev = balanceStart & OFFSET_MASK;\n balanceStart = balance[balancePrev];\n balanceCloseType = balanceStart >> TYPE_SHIFT;\n balance[tokenCount] = balancePrev;\n balance[balancePrev++] = tokenCount;\n for (; balancePrev < tokenCount; balancePrev++) {\n if (balance[balancePrev] === sourceLength) {\n balance[balancePrev] = tokenCount;\n }\n }\n break;\n }\n\n case LeftParenthesis:\n case FunctionToken:\n case LeftSquareBracket:\n case LeftCurlyBracket:\n balance[tokenCount] = balanceStart;\n balanceCloseType = balancePair.get(type);\n balanceStart = (balanceCloseType << TYPE_SHIFT) | tokenCount;\n break;\n }\n\n offsetAndType[tokenCount++] = (type << TYPE_SHIFT) | end;\n if (firstCharOffset === -1) {\n firstCharOffset = start;\n }\n });\n\n // finalize buffers\n offsetAndType[tokenCount] = (EOF << TYPE_SHIFT) | sourceLength; // \n balance[tokenCount] = sourceLength;\n balance[sourceLength] = sourceLength; // prevents false positive balance match with any token\n while (balanceStart !== 0) {\n const balancePrev = balanceStart & OFFSET_MASK;\n balanceStart = balance[balancePrev];\n balance[balancePrev] = sourceLength;\n }\n\n this.source = source;\n this.firstCharOffset = firstCharOffset === -1 ? 0 : firstCharOffset;\n this.tokenCount = tokenCount;\n this.offsetAndType = offsetAndType;\n this.balance = balance;\n\n this.reset();\n this.next();\n }\n\n lookupType(offset) {\n offset += this.tokenIndex;\n\n if (offset < this.tokenCount) {\n return this.offsetAndType[offset] >> TYPE_SHIFT;\n }\n\n return EOF;\n }\n lookupOffset(offset) {\n offset += this.tokenIndex;\n\n if (offset < this.tokenCount) {\n return this.offsetAndType[offset - 1] & OFFSET_MASK;\n }\n\n return this.source.length;\n }\n lookupValue(offset, referenceStr) {\n offset += this.tokenIndex;\n\n if (offset < this.tokenCount) {\n return cmpStr(\n this.source,\n this.offsetAndType[offset - 1] & OFFSET_MASK,\n this.offsetAndType[offset] & OFFSET_MASK,\n referenceStr\n );\n }\n\n return false;\n }\n getTokenStart(tokenIndex) {\n if (tokenIndex === this.tokenIndex) {\n return this.tokenStart;\n }\n\n if (tokenIndex > 0) {\n return tokenIndex < this.tokenCount\n ? this.offsetAndType[tokenIndex - 1] & OFFSET_MASK\n : this.offsetAndType[this.tokenCount] & OFFSET_MASK;\n }\n\n return this.firstCharOffset;\n }\n substrToCursor(start) {\n return this.source.substring(start, this.tokenStart);\n }\n\n isBalanceEdge(pos) {\n return this.balance[this.tokenIndex] < pos;\n }\n isDelim(code, offset) {\n if (offset) {\n return (\n this.lookupType(offset) === Delim &&\n this.source.charCodeAt(this.lookupOffset(offset)) === code\n );\n }\n\n return (\n this.tokenType === Delim &&\n this.source.charCodeAt(this.tokenStart) === code\n );\n }\n\n skip(tokenCount) {\n let next = this.tokenIndex + tokenCount;\n\n if (next < this.tokenCount) {\n this.tokenIndex = next;\n this.tokenStart = this.offsetAndType[next - 1] & OFFSET_MASK;\n next = this.offsetAndType[next];\n this.tokenType = next >> TYPE_SHIFT;\n this.tokenEnd = next & OFFSET_MASK;\n } else {\n this.tokenIndex = this.tokenCount;\n this.next();\n }\n }\n next() {\n let next = this.tokenIndex + 1;\n\n if (next < this.tokenCount) {\n this.tokenIndex = next;\n this.tokenStart = this.tokenEnd;\n next = this.offsetAndType[next];\n this.tokenType = next >> TYPE_SHIFT;\n this.tokenEnd = next & OFFSET_MASK;\n } else {\n this.eof = true;\n this.tokenIndex = this.tokenCount;\n this.tokenType = EOF;\n this.tokenStart = this.tokenEnd = this.source.length;\n }\n }\n skipSC() {\n while (this.tokenType === WhiteSpace || this.tokenType === Comment) {\n this.next();\n }\n }\n skipUntilBalanced(startToken, stopConsume) {\n let cursor = startToken;\n let balanceEnd;\n let offset;\n\n loop:\n for (; cursor < this.tokenCount; cursor++) {\n balanceEnd = this.balance[cursor];\n\n // stop scanning on balance edge that points to offset before start token\n if (balanceEnd < startToken) {\n break loop;\n }\n\n offset = cursor > 0 ? this.offsetAndType[cursor - 1] & OFFSET_MASK : this.firstCharOffset;\n\n // check stop condition\n switch (stopConsume(this.source.charCodeAt(offset))) {\n case 1: // just stop\n break loop;\n\n case 2: // stop & included\n cursor++;\n break loop;\n\n default:\n // fast forward to the end of balanced block\n if (this.balance[balanceEnd] === cursor) {\n cursor = balanceEnd;\n }\n }\n }\n\n this.skip(cursor - this.tokenIndex);\n }\n\n forEachToken(fn) {\n for (let i = 0, offset = this.firstCharOffset; i < this.tokenCount; i++) {\n const start = offset;\n const item = this.offsetAndType[i];\n const end = item & OFFSET_MASK;\n const type = item >> TYPE_SHIFT;\n\n offset = end;\n\n fn(type, start, end, i);\n }\n }\n dump() {\n const tokens = new Array(this.tokenCount);\n\n this.forEachToken((type, start, end, index) => {\n tokens[index] = {\n idx: index,\n type: tokenNames[type],\n chunk: this.source.substring(start, end),\n balance: this.balance[index]\n };\n });\n\n return tokens;\n }\n};\n","import * as TYPE from './types.js';\nimport {\n isNewline,\n isName,\n isValidEscape,\n isNumberStart,\n isIdentifierStart,\n isBOM,\n charCodeCategory,\n WhiteSpaceCategory,\n DigitCategory,\n NameStartCategory,\n NonPrintableCategory\n} from './char-code-definitions.js';\nimport {\n cmpStr,\n getNewlineLength,\n findWhiteSpaceEnd,\n consumeEscaped,\n consumeName,\n consumeNumber,\n consumeBadUrlRemnants\n} from './utils.js';\n\nexport function tokenize(source, onToken) {\n function getCharCode(offset) {\n return offset < sourceLength ? source.charCodeAt(offset) : 0;\n }\n\n // § 4.3.3. Consume a numeric token\n function consumeNumericToken() {\n // Consume a number and let number be the result.\n offset = consumeNumber(source, offset);\n\n // If the next 3 input code points would start an identifier, then:\n if (isIdentifierStart(getCharCode(offset), getCharCode(offset + 1), getCharCode(offset + 2))) {\n // Create a with the same value and type flag as number, and a unit set initially to the empty string.\n // Consume a name. Set the ’s unit to the returned value.\n // Return the .\n type = TYPE.Dimension;\n offset = consumeName(source, offset);\n return;\n }\n\n // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it.\n if (getCharCode(offset) === 0x0025) {\n // Create a with the same value as number, and return it.\n type = TYPE.Percentage;\n offset++;\n return;\n }\n\n // Otherwise, create a with the same value and type flag as number, and return it.\n type = TYPE.Number;\n }\n\n // § 4.3.4. Consume an ident-like token\n function consumeIdentLikeToken() {\n const nameStartOffset = offset;\n\n // Consume a name, and let string be the result.\n offset = consumeName(source, offset);\n\n // If string’s value is an ASCII case-insensitive match for \"url\",\n // and the next input code point is U+0028 LEFT PARENTHESIS ((), consume it.\n if (cmpStr(source, nameStartOffset, offset, 'url') && getCharCode(offset) === 0x0028) {\n // While the next two input code points are whitespace, consume the next input code point.\n offset = findWhiteSpaceEnd(source, offset + 1);\n\n // If the next one or two input code points are U+0022 QUOTATION MARK (\"), U+0027 APOSTROPHE ('),\n // or whitespace followed by U+0022 QUOTATION MARK (\") or U+0027 APOSTROPHE ('),\n // then create a with its value set to string and return it.\n if (getCharCode(offset) === 0x0022 ||\n getCharCode(offset) === 0x0027) {\n type = TYPE.Function;\n offset = nameStartOffset + 4;\n return;\n }\n\n // Otherwise, consume a url token, and return it.\n consumeUrlToken();\n return;\n }\n\n // Otherwise, if the next input code point is U+0028 LEFT PARENTHESIS ((), consume it.\n // Create a with its value set to string and return it.\n if (getCharCode(offset) === 0x0028) {\n type = TYPE.Function;\n offset++;\n return;\n }\n\n // Otherwise, create an with its value set to string and return it.\n type = TYPE.Ident;\n }\n\n // § 4.3.5. Consume a string token\n function consumeStringToken(endingCodePoint) {\n // This algorithm may be called with an ending code point, which denotes the code point\n // that ends the string. If an ending code point is not specified,\n // the current input code point is used.\n if (!endingCodePoint) {\n endingCodePoint = getCharCode(offset++);\n }\n\n // Initially create a with its value set to the empty string.\n type = TYPE.String;\n\n // Repeatedly consume the next input code point from the stream:\n for (; offset < source.length; offset++) {\n const code = source.charCodeAt(offset);\n\n switch (charCodeCategory(code)) {\n // ending code point\n case endingCodePoint:\n // Return the .\n offset++;\n return;\n\n // EOF\n // case EofCategory:\n // This is a parse error. Return the .\n // return;\n\n // newline\n case WhiteSpaceCategory:\n if (isNewline(code)) {\n // This is a parse error. Reconsume the current input code point,\n // create a , and return it.\n offset += getNewlineLength(source, offset, code);\n type = TYPE.BadString;\n return;\n }\n break;\n\n // U+005C REVERSE SOLIDUS (\\)\n case 0x005C:\n // If the next input code point is EOF, do nothing.\n if (offset === source.length - 1) {\n break;\n }\n\n const nextCode = getCharCode(offset + 1);\n\n // Otherwise, if the next input code point is a newline, consume it.\n if (isNewline(nextCode)) {\n offset += getNewlineLength(source, offset + 1, nextCode);\n } else if (isValidEscape(code, nextCode)) {\n // Otherwise, (the stream starts with a valid escape) consume\n // an escaped code point and append the returned code point to\n // the ’s value.\n offset = consumeEscaped(source, offset) - 1;\n }\n break;\n\n // anything else\n // Append the current input code point to the ’s value.\n }\n }\n }\n\n // § 4.3.6. Consume a url token\n // Note: This algorithm assumes that the initial \"url(\" has already been consumed.\n // This algorithm also assumes that it’s being called to consume an \"unquoted\" value, like url(foo).\n // A quoted value, like url(\"foo\"), is parsed as a . Consume an ident-like token\n // automatically handles this distinction; this algorithm shouldn’t be called directly otherwise.\n function consumeUrlToken() {\n // Initially create a with its value set to the empty string.\n type = TYPE.Url;\n\n // Consume as much whitespace as possible.\n offset = findWhiteSpaceEnd(source, offset);\n\n // Repeatedly consume the next input code point from the stream:\n for (; offset < source.length; offset++) {\n const code = source.charCodeAt(offset);\n\n switch (charCodeCategory(code)) {\n // U+0029 RIGHT PARENTHESIS ())\n case 0x0029:\n // Return the .\n offset++;\n return;\n\n // EOF\n // case EofCategory:\n // This is a parse error. Return the .\n // return;\n\n // whitespace\n case WhiteSpaceCategory:\n // Consume as much whitespace as possible.\n offset = findWhiteSpaceEnd(source, offset);\n\n // If the next input code point is U+0029 RIGHT PARENTHESIS ()) or EOF,\n // consume it and return the \n // (if EOF was encountered, this is a parse error);\n if (getCharCode(offset) === 0x0029 || offset >= source.length) {\n if (offset < source.length) {\n offset++;\n }\n return;\n }\n\n // otherwise, consume the remnants of a bad url, create a ,\n // and return it.\n offset = consumeBadUrlRemnants(source, offset);\n type = TYPE.BadUrl;\n return;\n\n // U+0022 QUOTATION MARK (\")\n // U+0027 APOSTROPHE (')\n // U+0028 LEFT PARENTHESIS (()\n // non-printable code point\n case 0x0022:\n case 0x0027:\n case 0x0028:\n case NonPrintableCategory:\n // This is a parse error. Consume the remnants of a bad url,\n // create a , and return it.\n offset = consumeBadUrlRemnants(source, offset);\n type = TYPE.BadUrl;\n return;\n\n // U+005C REVERSE SOLIDUS (\\)\n case 0x005C:\n // If the stream starts with a valid escape, consume an escaped code point and\n // append the returned code point to the ’s value.\n if (isValidEscape(code, getCharCode(offset + 1))) {\n offset = consumeEscaped(source, offset) - 1;\n break;\n }\n\n // Otherwise, this is a parse error. Consume the remnants of a bad url,\n // create a , and return it.\n offset = consumeBadUrlRemnants(source, offset);\n type = TYPE.BadUrl;\n return;\n\n // anything else\n // Append the current input code point to the ’s value.\n }\n }\n }\n\n // ensure source is a string\n source = String(source || '');\n\n const sourceLength = source.length;\n let start = isBOM(getCharCode(0));\n let offset = start;\n let type;\n\n // https://drafts.csswg.org/css-syntax-3/#consume-token\n // § 4.3.1. Consume a token\n while (offset < sourceLength) {\n const code = source.charCodeAt(offset);\n\n switch (charCodeCategory(code)) {\n // whitespace\n case WhiteSpaceCategory:\n // Consume as much whitespace as possible. Return a .\n type = TYPE.WhiteSpace;\n offset = findWhiteSpaceEnd(source, offset + 1);\n break;\n\n // U+0022 QUOTATION MARK (\")\n case 0x0022:\n // Consume a string token and return it.\n consumeStringToken();\n break;\n\n // U+0023 NUMBER SIGN (#)\n case 0x0023:\n // If the next input code point is a name code point or the next two input code points are a valid escape, then:\n if (isName(getCharCode(offset + 1)) || isValidEscape(getCharCode(offset + 1), getCharCode(offset + 2))) {\n // Create a .\n type = TYPE.Hash;\n\n // If the next 3 input code points would start an identifier, set the ’s type flag to \"id\".\n // if (isIdentifierStart(getCharCode(offset + 1), getCharCode(offset + 2), getCharCode(offset + 3))) {\n // // TODO: set id flag\n // }\n\n // Consume a name, and set the ’s value to the returned string.\n offset = consumeName(source, offset + 1);\n\n // Return the .\n } else {\n // Otherwise, return a with its value set to the current input code point.\n type = TYPE.Delim;\n offset++;\n }\n\n break;\n\n // U+0027 APOSTROPHE (')\n case 0x0027:\n // Consume a string token and return it.\n consumeStringToken();\n break;\n\n // U+0028 LEFT PARENTHESIS (()\n case 0x0028:\n // Return a <(-token>.\n type = TYPE.LeftParenthesis;\n offset++;\n break;\n\n // U+0029 RIGHT PARENTHESIS ())\n case 0x0029:\n // Return a <)-token>.\n type = TYPE.RightParenthesis;\n offset++;\n break;\n\n // U+002B PLUS SIGN (+)\n case 0x002B:\n // If the input stream starts with a number, ...\n if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) {\n // ... reconsume the current input code point, consume a numeric token, and return it.\n consumeNumericToken();\n } else {\n // Otherwise, return a with its value set to the current input code point.\n type = TYPE.Delim;\n offset++;\n }\n break;\n\n // U+002C COMMA (,)\n case 0x002C:\n // Return a .\n type = TYPE.Comma;\n offset++;\n break;\n\n // U+002D HYPHEN-MINUS (-)\n case 0x002D:\n // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.\n if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) {\n consumeNumericToken();\n } else {\n // Otherwise, if the next 2 input code points are U+002D HYPHEN-MINUS U+003E GREATER-THAN SIGN (->), consume them and return a .\n if (getCharCode(offset + 1) === 0x002D &&\n getCharCode(offset + 2) === 0x003E) {\n type = TYPE.CDC;\n offset = offset + 3;\n } else {\n // Otherwise, if the input stream starts with an identifier, ...\n if (isIdentifierStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) {\n // ... reconsume the current input code point, consume an ident-like token, and return it.\n consumeIdentLikeToken();\n } else {\n // Otherwise, return a with its value set to the current input code point.\n type = TYPE.Delim;\n offset++;\n }\n }\n }\n break;\n\n // U+002E FULL STOP (.)\n case 0x002E:\n // If the input stream starts with a number, ...\n if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) {\n // ... reconsume the current input code point, consume a numeric token, and return it.\n consumeNumericToken();\n } else {\n // Otherwise, return a with its value set to the current input code point.\n type = TYPE.Delim;\n offset++;\n }\n\n break;\n\n // U+002F SOLIDUS (/)\n case 0x002F:\n // If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A ASTERISK (*),\n if (getCharCode(offset + 1) === 0x002A) {\n // ... consume them and all following code points up to and including the first U+002A ASTERISK (*)\n // followed by a U+002F SOLIDUS (/), or up to an EOF code point.\n type = TYPE.Comment;\n offset = source.indexOf('*/', offset + 2);\n offset = offset === -1 ? source.length : offset + 2;\n } else {\n type = TYPE.Delim;\n offset++;\n }\n break;\n\n // U+003A COLON (:)\n case 0x003A:\n // Return a .\n type = TYPE.Colon;\n offset++;\n break;\n\n // U+003B SEMICOLON (;)\n case 0x003B:\n // Return a .\n type = TYPE.Semicolon;\n offset++;\n break;\n\n // U+003C LESS-THAN SIGN (<)\n case 0x003C:\n // If the next 3 input code points are U+0021 EXCLAMATION MARK U+002D HYPHEN-MINUS U+002D HYPHEN-MINUS (!--), ...\n if (getCharCode(offset + 1) === 0x0021 &&\n getCharCode(offset + 2) === 0x002D &&\n getCharCode(offset + 3) === 0x002D) {\n // ... consume them and return a .\n type = TYPE.CDO;\n offset = offset + 4;\n } else {\n // Otherwise, return a with its value set to the current input code point.\n type = TYPE.Delim;\n offset++;\n }\n\n break;\n\n // U+0040 COMMERCIAL AT (@)\n case 0x0040:\n // If the next 3 input code points would start an identifier, ...\n if (isIdentifierStart(getCharCode(offset + 1), getCharCode(offset + 2), getCharCode(offset + 3))) {\n // ... consume a name, create an with its value set to the returned value, and return it.\n type = TYPE.AtKeyword;\n offset = consumeName(source, offset + 1);\n } else {\n // Otherwise, return a with its value set to the current input code point.\n type = TYPE.Delim;\n offset++;\n }\n\n break;\n\n // U+005B LEFT SQUARE BRACKET ([)\n case 0x005B:\n // Return a <[-token>.\n type = TYPE.LeftSquareBracket;\n offset++;\n break;\n\n // U+005C REVERSE SOLIDUS (\\)\n case 0x005C:\n // If the input stream starts with a valid escape, ...\n if (isValidEscape(code, getCharCode(offset + 1))) {\n // ... reconsume the current input code point, consume an ident-like token, and return it.\n consumeIdentLikeToken();\n } else {\n // Otherwise, this is a parse error. Return a with its value set to the current input code point.\n type = TYPE.Delim;\n offset++;\n }\n break;\n\n // U+005D RIGHT SQUARE BRACKET (])\n case 0x005D:\n // Return a <]-token>.\n type = TYPE.RightSquareBracket;\n offset++;\n break;\n\n // U+007B LEFT CURLY BRACKET ({)\n case 0x007B:\n // Return a <{-token>.\n type = TYPE.LeftCurlyBracket;\n offset++;\n break;\n\n // U+007D RIGHT CURLY BRACKET (})\n case 0x007D:\n // Return a <}-token>.\n type = TYPE.RightCurlyBracket;\n offset++;\n break;\n\n // digit\n case DigitCategory:\n // Reconsume the current input code point, consume a numeric token, and return it.\n consumeNumericToken();\n break;\n\n // name-start code point\n case NameStartCategory:\n // Reconsume the current input code point, consume an ident-like token, and return it.\n consumeIdentLikeToken();\n break;\n\n // EOF\n // case EofCategory:\n // Return an .\n // break;\n\n // anything else\n default:\n // Return a with its value set to the current input code point.\n type = TYPE.Delim;\n offset++;\n }\n\n // put token to stream\n onToken(type, start, start = offset);\n }\n}\n\nexport * from './types.js';\nexport * as tokenTypes from './types.js';\nexport { default as tokenNames } from './names.js';\nexport * from './char-code-definitions.js';\nexport * from './utils.js';\nexport * from './OffsetToLocation.js';\nexport * from './TokenStream.js';\n","//\n// list\n// ┌──────â”\n// ┌──────────────┼─head │\n// │ │ tail─┼──────────────â”\n// │ └──────┘ │\n// â–¼ â–¼\n// item item item item\n// ┌──────┠┌──────┠┌──────┠┌──────â”\n// null ◀──┼─prev │◀───┼─prev │◀───┼─prev │◀───┼─prev │\n// │ next─┼───▶│ next─┼───▶│ next─┼───▶│ next─┼──▶ null\n// ├──────┤ ├──────┤ ├──────┤ ├──────┤\n// │ data │ │ data │ │ data │ │ data │\n// └──────┘ └──────┘ └──────┘ └──────┘\n//\n\nlet releasedCursors = null;\n\nexport class List {\n static createItem(data) {\n return {\n prev: null,\n next: null,\n data\n };\n }\n\n constructor() {\n this.head = null;\n this.tail = null;\n this.cursor = null;\n }\n createItem(data) {\n return List.createItem(data);\n }\n\n // cursor helpers\n allocateCursor(prev, next) {\n let cursor;\n\n if (releasedCursors !== null) {\n cursor = releasedCursors;\n releasedCursors = releasedCursors.cursor;\n cursor.prev = prev;\n cursor.next = next;\n cursor.cursor = this.cursor;\n } else {\n cursor = {\n prev,\n next,\n cursor: this.cursor\n };\n }\n\n this.cursor = cursor;\n\n return cursor;\n }\n releaseCursor() {\n const { cursor } = this;\n\n this.cursor = cursor.cursor;\n cursor.prev = null;\n cursor.next = null;\n cursor.cursor = releasedCursors;\n releasedCursors = cursor;\n }\n updateCursors(prevOld, prevNew, nextOld, nextNew) {\n let { cursor } = this;\n\n while (cursor !== null) {\n if (cursor.prev === prevOld) {\n cursor.prev = prevNew;\n }\n\n if (cursor.next === nextOld) {\n cursor.next = nextNew;\n }\n\n cursor = cursor.cursor;\n }\n }\n *[Symbol.iterator]() {\n for (let cursor = this.head; cursor !== null; cursor = cursor.next) {\n yield cursor.data;\n }\n }\n\n // getters\n get size() {\n let size = 0;\n\n for (let cursor = this.head; cursor !== null; cursor = cursor.next) {\n size++;\n }\n\n return size;\n }\n get isEmpty() {\n return this.head === null;\n }\n get first() {\n return this.head && this.head.data;\n }\n get last() {\n return this.tail && this.tail.data;\n }\n\n // convertors\n fromArray(array) {\n let cursor = null;\n this.head = null;\n\n for (let data of array) {\n const item = List.createItem(data);\n\n if (cursor !== null) {\n cursor.next = item;\n } else {\n this.head = item;\n }\n\n item.prev = cursor;\n cursor = item;\n }\n\n this.tail = cursor;\n return this;\n }\n toArray() {\n return [...this];\n }\n toJSON() {\n return [...this];\n }\n\n // array-like methods\n forEach(fn, thisArg = this) {\n // push cursor\n const cursor = this.allocateCursor(null, this.head);\n\n while (cursor.next !== null) {\n const item = cursor.next;\n cursor.next = item.next;\n fn.call(thisArg, item.data, item, this);\n }\n\n // pop cursor\n this.releaseCursor();\n }\n forEachRight(fn, thisArg = this) {\n // push cursor\n const cursor = this.allocateCursor(this.tail, null);\n\n while (cursor.prev !== null) {\n const item = cursor.prev;\n cursor.prev = item.prev;\n fn.call(thisArg, item.data, item, this);\n }\n\n // pop cursor\n this.releaseCursor();\n }\n reduce(fn, initialValue, thisArg = this) {\n // push cursor\n let cursor = this.allocateCursor(null, this.head);\n let acc = initialValue;\n let item;\n\n while (cursor.next !== null) {\n item = cursor.next;\n cursor.next = item.next;\n\n acc = fn.call(thisArg, acc, item.data, item, this);\n }\n\n // pop cursor\n this.releaseCursor();\n\n return acc;\n }\n reduceRight(fn, initialValue, thisArg = this) {\n // push cursor\n let cursor = this.allocateCursor(this.tail, null);\n let acc = initialValue;\n let item;\n\n while (cursor.prev !== null) {\n item = cursor.prev;\n cursor.prev = item.prev;\n\n acc = fn.call(thisArg, acc, item.data, item, this);\n }\n\n // pop cursor\n this.releaseCursor();\n\n return acc;\n }\n some(fn, thisArg = this) {\n for (let cursor = this.head; cursor !== null; cursor = cursor.next) {\n if (fn.call(thisArg, cursor.data, cursor, this)) {\n return true;\n }\n }\n\n return false;\n }\n map(fn, thisArg = this) {\n const result = new List();\n\n for (let cursor = this.head; cursor !== null; cursor = cursor.next) {\n result.appendData(fn.call(thisArg, cursor.data, cursor, this));\n }\n\n return result;\n }\n filter(fn, thisArg = this) {\n const result = new List();\n\n for (let cursor = this.head; cursor !== null; cursor = cursor.next) {\n if (fn.call(thisArg, cursor.data, cursor, this)) {\n result.appendData(cursor.data);\n }\n }\n\n return result;\n }\n\n nextUntil(start, fn, thisArg = this) {\n if (start === null) {\n return;\n }\n\n // push cursor\n const cursor = this.allocateCursor(null, start);\n\n while (cursor.next !== null) {\n const item = cursor.next;\n cursor.next = item.next;\n if (fn.call(thisArg, item.data, item, this)) {\n break;\n }\n }\n\n // pop cursor\n this.releaseCursor();\n }\n prevUntil(start, fn, thisArg = this) {\n if (start === null) {\n return;\n }\n\n // push cursor\n const cursor = this.allocateCursor(start, null);\n\n while (cursor.prev !== null) {\n const item = cursor.prev;\n cursor.prev = item.prev;\n if (fn.call(thisArg, item.data, item, this)) {\n break;\n }\n }\n\n // pop cursor\n this.releaseCursor();\n }\n\n // mutation\n clear() {\n this.head = null;\n this.tail = null;\n }\n copy() {\n const result = new List();\n\n for (let data of this) {\n result.appendData(data);\n }\n\n return result;\n }\n prepend(item) {\n // head\n // ^\n // item\n this.updateCursors(null, item, this.head, item);\n\n // insert to the beginning of the list\n if (this.head !== null) {\n // new item <- first item\n this.head.prev = item;\n // new item -> first item\n item.next = this.head;\n } else {\n // if list has no head, then it also has no tail\n // in this case tail points to the new item\n this.tail = item;\n }\n\n // head always points to new item\n this.head = item;\n return this;\n }\n prependData(data) {\n return this.prepend(List.createItem(data));\n }\n append(item) {\n return this.insert(item);\n }\n appendData(data) {\n return this.insert(List.createItem(data));\n }\n insert(item, before = null) {\n if (before !== null) {\n // prev before\n // ^\n // item\n this.updateCursors(before.prev, item, before, item);\n\n if (before.prev === null) {\n // insert to the beginning of list\n if (this.head !== before) {\n throw new Error('before doesn\\'t belong to list');\n }\n // since head points to before therefore list doesn't empty\n // no need to check tail\n this.head = item;\n before.prev = item;\n item.next = before;\n this.updateCursors(null, item);\n } else {\n // insert between two items\n before.prev.next = item;\n item.prev = before.prev;\n before.prev = item;\n item.next = before;\n }\n } else {\n // tail\n // ^\n // item\n this.updateCursors(this.tail, item, null, item);\n\n // insert to the ending of the list\n if (this.tail !== null) {\n // last item -> new item\n this.tail.next = item;\n // last item <- new item\n item.prev = this.tail;\n } else {\n // if list has no tail, then it also has no head\n // in this case head points to new item\n this.head = item;\n }\n\n // tail always points to new item\n this.tail = item;\n }\n\n return this;\n }\n insertData(data, before) {\n return this.insert(List.createItem(data), before);\n }\n remove(item) {\n // item\n // ^\n // prev next\n this.updateCursors(item, item.prev, item, item.next);\n\n if (item.prev !== null) {\n item.prev.next = item.next;\n } else {\n if (this.head !== item) {\n throw new Error('item doesn\\'t belong to list');\n }\n\n this.head = item.next;\n }\n\n if (item.next !== null) {\n item.next.prev = item.prev;\n } else {\n if (this.tail !== item) {\n throw new Error('item doesn\\'t belong to list');\n }\n\n this.tail = item.prev;\n }\n\n item.prev = null;\n item.next = null;\n\n return item;\n }\n push(data) {\n this.insert(List.createItem(data));\n }\n pop() {\n return this.tail !== null ? this.remove(this.tail) : null;\n }\n unshift(data) {\n this.prepend(List.createItem(data));\n }\n shift() {\n return this.head !== null ? this.remove(this.head) : null;\n }\n prependList(list) {\n return this.insertList(list, this.head);\n }\n appendList(list) {\n return this.insertList(list);\n }\n insertList(list, before) {\n // ignore empty lists\n if (list.head === null) {\n return this;\n }\n\n if (before !== undefined && before !== null) {\n this.updateCursors(before.prev, list.tail, before, list.head);\n\n // insert in the middle of dist list\n if (before.prev !== null) {\n // before.prev <-> list.head\n before.prev.next = list.head;\n list.head.prev = before.prev;\n } else {\n this.head = list.head;\n }\n\n before.prev = list.tail;\n list.tail.next = before;\n } else {\n this.updateCursors(this.tail, list.tail, null, list.head);\n\n // insert to end of the list\n if (this.tail !== null) {\n // if destination list has a tail, then it also has a head,\n // but head doesn't change\n // dest tail -> source head\n this.tail.next = list.head;\n // dest tail <- source head\n list.head.prev = this.tail;\n } else {\n // if list has no a tail, then it also has no a head\n // in this case points head to new item\n this.head = list.head;\n }\n\n // tail always start point to new item\n this.tail = list.tail;\n }\n\n list.head = null;\n list.tail = null;\n return this;\n }\n replace(oldItem, newItemOrList) {\n if ('head' in newItemOrList) {\n this.insertList(newItemOrList, oldItem);\n } else {\n this.insert(newItemOrList, oldItem);\n }\n\n this.remove(oldItem);\n }\n}\n","export function createCustomError(name, message) {\n // use Object.create(), because some VMs prevent setting line/column otherwise\n // (iOS Safari 10 even throws an exception)\n const error = Object.create(SyntaxError.prototype);\n const errorStack = new Error();\n\n return Object.assign(error, {\n name,\n message,\n get stack() {\n return (errorStack.stack || '').replace(/^(.+\\n){1,3}/, `${name}: ${message}\\n`);\n }\n });\n};\n","import { createCustomError } from '../utils/create-custom-error.js';\n\nconst MAX_LINE_LENGTH = 100;\nconst OFFSET_CORRECTION = 60;\nconst TAB_REPLACEMENT = ' ';\n\nfunction sourceFragment({ source, line, column }, extraLines) {\n function processLines(start, end) {\n return lines\n .slice(start, end)\n .map((line, idx) =>\n String(start + idx + 1).padStart(maxNumLength) + ' |' + line\n ).join('\\n');\n }\n\n const lines = source.split(/\\r\\n?|\\n|\\f/);\n const startLine = Math.max(1, line - extraLines) - 1;\n const endLine = Math.min(line + extraLines, lines.length + 1);\n const maxNumLength = Math.max(4, String(endLine).length) + 1;\n let cutLeft = 0;\n\n // column correction according to replaced tab before column\n column += (TAB_REPLACEMENT.length - 1) * (lines[line - 1].substr(0, column - 1).match(/\\t/g) || []).length;\n\n if (column > MAX_LINE_LENGTH) {\n cutLeft = column - OFFSET_CORRECTION + 3;\n column = OFFSET_CORRECTION - 2;\n }\n\n for (let i = startLine; i <= endLine; i++) {\n if (i >= 0 && i < lines.length) {\n lines[i] = lines[i].replace(/\\t/g, TAB_REPLACEMENT);\n lines[i] =\n (cutLeft > 0 && lines[i].length > cutLeft ? '\\u2026' : '') +\n lines[i].substr(cutLeft, MAX_LINE_LENGTH - 2) +\n (lines[i].length > cutLeft + MAX_LINE_LENGTH - 1 ? '\\u2026' : '');\n }\n }\n\n return [\n processLines(startLine, line),\n new Array(column + maxNumLength + 2).join('-') + '^',\n processLines(line, endLine)\n ].filter(Boolean).join('\\n');\n}\n\nexport function SyntaxError(message, source, offset, line, column) {\n const error = Object.assign(createCustomError('SyntaxError', message), {\n source,\n offset,\n line,\n column,\n sourceFragment(extraLines) {\n return sourceFragment({ source, line, column }, isNaN(extraLines) ? 0 : extraLines);\n },\n get formattedMessage() {\n return (\n `Parse error: ${message}\\n` +\n sourceFragment({ source, line, column }, 2)\n );\n }\n });\n\n return error;\n}\n","import { WhiteSpace, Comment } from '../tokenizer/index.js';\n\nexport function readSequence(recognizer) {\n const children = this.createList();\n let space = false;\n const context = {\n recognizer\n };\n\n while (!this.eof) {\n switch (this.tokenType) {\n case Comment:\n this.next();\n continue;\n\n case WhiteSpace:\n space = true;\n this.next();\n continue;\n }\n\n let child = recognizer.getNode.call(this, context);\n\n if (child === undefined) {\n break;\n }\n\n if (space) {\n if (recognizer.onWhiteSpace) {\n recognizer.onWhiteSpace.call(this, child, children, context);\n }\n space = false;\n }\n\n children.push(child);\n }\n\n if (space && recognizer.onWhiteSpace) {\n recognizer.onWhiteSpace.call(this, null, children, context);\n }\n\n return children;\n};\n","import { List } from '../utils/List.js';\nimport { SyntaxError } from './SyntaxError.js';\nimport {\n tokenize,\n OffsetToLocation,\n TokenStream,\n tokenNames,\n\n consumeNumber,\n findWhiteSpaceStart,\n cmpChar,\n cmpStr,\n\n WhiteSpace,\n Comment,\n Ident,\n Function as FunctionToken,\n Url,\n Hash,\n Percentage,\n Number as NumberToken\n} from '../tokenizer/index.js';\nimport { readSequence } from './sequence.js';\n\nconst NOOP = () => {};\nconst EXCLAMATIONMARK = 0x0021; // U+0021 EXCLAMATION MARK (!)\nconst NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#)\nconst SEMICOLON = 0x003B; // U+003B SEMICOLON (;)\nconst LEFTCURLYBRACKET = 0x007B; // U+007B LEFT CURLY BRACKET ({)\nconst NULL = 0;\n\nfunction createParseContext(name) {\n return function() {\n return this[name]();\n };\n}\n\nfunction fetchParseValues(dict) {\n const result = Object.create(null);\n\n for (const name in dict) {\n const item = dict[name];\n const fn = item.parse || item;\n\n if (fn) {\n result[name] = fn;\n }\n }\n\n return result;\n}\n\nfunction processConfig(config) {\n const parseConfig = {\n context: Object.create(null),\n scope: Object.assign(Object.create(null), config.scope),\n atrule: fetchParseValues(config.atrule),\n pseudo: fetchParseValues(config.pseudo),\n node: fetchParseValues(config.node)\n };\n\n for (const name in config.parseContext) {\n switch (typeof config.parseContext[name]) {\n case 'function':\n parseConfig.context[name] = config.parseContext[name];\n break;\n\n case 'string':\n parseConfig.context[name] = createParseContext(config.parseContext[name]);\n break;\n }\n }\n\n return {\n config: parseConfig,\n ...parseConfig,\n ...parseConfig.node\n };\n}\n\nexport function createParser(config) {\n let source = '';\n let filename = '';\n let needPositions = false;\n let onParseError = NOOP;\n let onParseErrorThrow = false;\n\n const locationMap = new OffsetToLocation();\n const parser = Object.assign(new TokenStream(), processConfig(config || {}), {\n parseAtrulePrelude: true,\n parseRulePrelude: true,\n parseValue: true,\n parseCustomProperty: false,\n\n readSequence,\n\n consumeUntilBalanceEnd: () => 0,\n consumeUntilLeftCurlyBracket(code) {\n return code === LEFTCURLYBRACKET ? 1 : 0;\n },\n consumeUntilLeftCurlyBracketOrSemicolon(code) {\n return code === LEFTCURLYBRACKET || code === SEMICOLON ? 1 : 0;\n },\n consumeUntilExclamationMarkOrSemicolon(code) {\n return code === EXCLAMATIONMARK || code === SEMICOLON ? 1 : 0;\n },\n consumeUntilSemicolonIncluded(code) {\n return code === SEMICOLON ? 2 : 0;\n },\n\n createList() {\n return new List();\n },\n createSingleNodeList(node) {\n return new List().appendData(node);\n },\n getFirstListNode(list) {\n return list && list.first;\n },\n getLastListNode(list) {\n return list && list.last;\n },\n\n parseWithFallback(consumer, fallback) {\n const startToken = this.tokenIndex;\n\n try {\n return consumer.call(this);\n } catch (e) {\n if (onParseErrorThrow) {\n throw e;\n }\n\n const fallbackNode = fallback.call(this, startToken);\n\n onParseErrorThrow = true;\n onParseError(e, fallbackNode);\n onParseErrorThrow = false;\n\n return fallbackNode;\n }\n },\n\n lookupNonWSType(offset) {\n let type;\n\n do {\n type = this.lookupType(offset++);\n if (type !== WhiteSpace) {\n return type;\n }\n } while (type !== NULL);\n\n return NULL;\n },\n\n charCodeAt(offset) {\n return offset >= 0 && offset < source.length ? source.charCodeAt(offset) : 0;\n },\n substring(offsetStart, offsetEnd) {\n return source.substring(offsetStart, offsetEnd);\n },\n substrToCursor(start) {\n return this.source.substring(start, this.tokenStart);\n },\n\n cmpChar(offset, charCode) {\n return cmpChar(source, offset, charCode);\n },\n cmpStr(offsetStart, offsetEnd, str) {\n return cmpStr(source, offsetStart, offsetEnd, str);\n },\n\n consume(tokenType) {\n const start = this.tokenStart;\n\n this.eat(tokenType);\n\n return this.substrToCursor(start);\n },\n consumeFunctionName() {\n const name = source.substring(this.tokenStart, this.tokenEnd - 1);\n\n this.eat(FunctionToken);\n\n return name;\n },\n consumeNumber(type) {\n const number = source.substring(this.tokenStart, consumeNumber(source, this.tokenStart));\n\n this.eat(type);\n\n return number;\n },\n\n eat(tokenType) {\n if (this.tokenType !== tokenType) {\n const tokenName = tokenNames[tokenType].slice(0, -6).replace(/-/g, ' ').replace(/^./, m => m.toUpperCase());\n let message = `${/[[\\](){}]/.test(tokenName) ? `\"${tokenName}\"` : tokenName} is expected`;\n let offset = this.tokenStart;\n\n // tweak message and offset\n switch (tokenType) {\n case Ident:\n // when identifier is expected but there is a function or url\n if (this.tokenType === FunctionToken || this.tokenType === Url) {\n offset = this.tokenEnd - 1;\n message = 'Identifier is expected but function found';\n } else {\n message = 'Identifier is expected';\n }\n break;\n\n case Hash:\n if (this.isDelim(NUMBERSIGN)) {\n this.next();\n offset++;\n message = 'Name is expected';\n }\n break;\n\n case Percentage:\n if (this.tokenType === NumberToken) {\n offset = this.tokenEnd;\n message = 'Percent sign is expected';\n }\n break;\n }\n\n this.error(message, offset);\n }\n\n this.next();\n },\n eatIdent(name) {\n if (this.tokenType !== Ident || this.lookupValue(0, name) === false) {\n this.error(`Identifier \"${name}\" is expected`);\n }\n\n this.next();\n },\n eatDelim(code) {\n if (!this.isDelim(code)) {\n this.error(`Delim \"${String.fromCharCode(code)}\" is expected`);\n }\n\n this.next();\n },\n\n getLocation(start, end) {\n if (needPositions) {\n return locationMap.getLocationRange(\n start,\n end,\n filename\n );\n }\n\n return null;\n },\n getLocationFromList(list) {\n if (needPositions) {\n const head = this.getFirstListNode(list);\n const tail = this.getLastListNode(list);\n return locationMap.getLocationRange(\n head !== null ? head.loc.start.offset - locationMap.startOffset : this.tokenStart,\n tail !== null ? tail.loc.end.offset - locationMap.startOffset : this.tokenStart,\n filename\n );\n }\n\n return null;\n },\n\n error(message, offset) {\n const location = typeof offset !== 'undefined' && offset < source.length\n ? locationMap.getLocation(offset)\n : this.eof\n ? locationMap.getLocation(findWhiteSpaceStart(source, source.length - 1))\n : locationMap.getLocation(this.tokenStart);\n\n throw new SyntaxError(\n message || 'Unexpected input',\n source,\n location.offset,\n location.line,\n location.column\n );\n }\n });\n\n const parse = function(source_, options) {\n source = source_;\n options = options || {};\n\n parser.setSource(source, tokenize);\n locationMap.setSource(\n source,\n options.offset,\n options.line,\n options.column\n );\n\n filename = options.filename || '';\n needPositions = Boolean(options.positions);\n onParseError = typeof options.onParseError === 'function' ? options.onParseError : NOOP;\n onParseErrorThrow = false;\n\n parser.parseAtrulePrelude = 'parseAtrulePrelude' in options ? Boolean(options.parseAtrulePrelude) : true;\n parser.parseRulePrelude = 'parseRulePrelude' in options ? Boolean(options.parseRulePrelude) : true;\n parser.parseValue = 'parseValue' in options ? Boolean(options.parseValue) : true;\n parser.parseCustomProperty = 'parseCustomProperty' in options ? Boolean(options.parseCustomProperty) : false;\n\n const { context = 'default', onComment } = options;\n\n if (context in parser.context === false) {\n throw new Error('Unknown context `' + context + '`');\n }\n\n if (typeof onComment === 'function') {\n parser.forEachToken((type, start, end) => {\n if (type === Comment) {\n const loc = parser.getLocation(start, end);\n const value = cmpStr(source, end - 2, end, '*/')\n ? source.slice(start + 2, end - 2)\n : source.slice(start + 2, end);\n\n onComment(value, loc);\n }\n });\n }\n\n const ast = parser.context[context].call(parser, options);\n\n if (!parser.eof) {\n parser.error();\n }\n\n return ast;\n };\n\n return Object.assign(parse, {\n SyntaxError,\n config: parser.config\n });\n};\n","import { SourceMapGenerator } from 'source-map-js/lib/source-map-generator.js';\n\nconst trackNodes = new Set(['Atrule', 'Selector', 'Declaration']);\n\nexport function generateSourceMap(handlers) {\n const map = new SourceMapGenerator();\n const generated = {\n line: 1,\n column: 0\n };\n const original = {\n line: 0, // should be zero to add first mapping\n column: 0\n };\n const activatedGenerated = {\n line: 1,\n column: 0\n };\n const activatedMapping = {\n generated: activatedGenerated\n };\n let line = 1;\n let column = 0;\n let sourceMappingActive = false;\n\n const origHandlersNode = handlers.node;\n handlers.node = function(node) {\n if (node.loc && node.loc.start && trackNodes.has(node.type)) {\n const nodeLine = node.loc.start.line;\n const nodeColumn = node.loc.start.column - 1;\n\n if (original.line !== nodeLine ||\n original.column !== nodeColumn) {\n original.line = nodeLine;\n original.column = nodeColumn;\n\n generated.line = line;\n generated.column = column;\n\n if (sourceMappingActive) {\n sourceMappingActive = false;\n if (generated.line !== activatedGenerated.line ||\n generated.column !== activatedGenerated.column) {\n map.addMapping(activatedMapping);\n }\n }\n\n sourceMappingActive = true;\n map.addMapping({\n source: node.loc.source,\n original,\n generated\n });\n }\n }\n\n origHandlersNode.call(this, node);\n\n if (sourceMappingActive && trackNodes.has(node.type)) {\n activatedGenerated.line = line;\n activatedGenerated.column = column;\n }\n };\n\n const origHandlersEmit = handlers.emit;\n handlers.emit = function(value, type, auto) {\n for (let i = 0; i < value.length; i++) {\n if (value.charCodeAt(i) === 10) { // \\n\n line++;\n column = 0;\n } else {\n column++;\n }\n }\n\n origHandlersEmit(value, type, auto);\n };\n\n const origHandlersResult = handlers.result;\n handlers.result = function() {\n if (sourceMappingActive) {\n map.addMapping(activatedMapping);\n }\n\n return {\n css: origHandlersResult(),\n map\n };\n };\n\n return handlers;\n};\n","import {\n WhiteSpace,\n Delim,\n Ident,\n Function as FunctionToken,\n Url,\n BadUrl,\n AtKeyword,\n Hash,\n Percentage,\n Dimension,\n Number as NumberToken,\n String as StringToken,\n Colon,\n LeftParenthesis,\n RightParenthesis,\n CDC\n} from '../tokenizer/index.js';\n\nconst PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)\nconst HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-)\n\nconst code = (type, value) => {\n if (type === Delim) {\n type = value;\n }\n\n if (typeof type === 'string') {\n const charCode = type.charCodeAt(0);\n return charCode > 0x7F ? 0x8000 : charCode << 8;\n }\n\n return type;\n};\n\n// https://www.w3.org/TR/css-syntax-3/#serialization\n// The only requirement for serialization is that it must \"round-trip\" with parsing,\n// that is, parsing the stylesheet must produce the same data structures as parsing,\n// serializing, and parsing again, except for consecutive s,\n// which may be collapsed into a single token.\n\nconst specPairs = [\n [Ident, Ident],\n [Ident, FunctionToken],\n [Ident, Url],\n [Ident, BadUrl],\n [Ident, '-'],\n [Ident, NumberToken],\n [Ident, Percentage],\n [Ident, Dimension],\n [Ident, CDC],\n [Ident, LeftParenthesis],\n\n [AtKeyword, Ident],\n [AtKeyword, FunctionToken],\n [AtKeyword, Url],\n [AtKeyword, BadUrl],\n [AtKeyword, '-'],\n [AtKeyword, NumberToken],\n [AtKeyword, Percentage],\n [AtKeyword, Dimension],\n [AtKeyword, CDC],\n\n [Hash, Ident],\n [Hash, FunctionToken],\n [Hash, Url],\n [Hash, BadUrl],\n [Hash, '-'],\n [Hash, NumberToken],\n [Hash, Percentage],\n [Hash, Dimension],\n [Hash, CDC],\n\n [Dimension, Ident],\n [Dimension, FunctionToken],\n [Dimension, Url],\n [Dimension, BadUrl],\n [Dimension, '-'],\n [Dimension, NumberToken],\n [Dimension, Percentage],\n [Dimension, Dimension],\n [Dimension, CDC],\n\n ['#', Ident],\n ['#', FunctionToken],\n ['#', Url],\n ['#', BadUrl],\n ['#', '-'],\n ['#', NumberToken],\n ['#', Percentage],\n ['#', Dimension],\n ['#', CDC], // https://github.com/w3c/csswg-drafts/pull/6874\n\n ['-', Ident],\n ['-', FunctionToken],\n ['-', Url],\n ['-', BadUrl],\n ['-', '-'],\n ['-', NumberToken],\n ['-', Percentage],\n ['-', Dimension],\n ['-', CDC], // https://github.com/w3c/csswg-drafts/pull/6874\n\n [NumberToken, Ident],\n [NumberToken, FunctionToken],\n [NumberToken, Url],\n [NumberToken, BadUrl],\n [NumberToken, NumberToken],\n [NumberToken, Percentage],\n [NumberToken, Dimension],\n [NumberToken, '%'],\n [NumberToken, CDC], // https://github.com/w3c/csswg-drafts/pull/6874\n\n ['@', Ident],\n ['@', FunctionToken],\n ['@', Url],\n ['@', BadUrl],\n ['@', '-'],\n ['@', CDC], // https://github.com/w3c/csswg-drafts/pull/6874\n\n ['.', NumberToken],\n ['.', Percentage],\n ['.', Dimension],\n\n ['+', NumberToken],\n ['+', Percentage],\n ['+', Dimension],\n\n ['/', '*']\n];\n// validate with scripts/generate-safe\nconst safePairs = specPairs.concat([\n [Ident, Hash],\n\n [Dimension, Hash],\n\n [Hash, Hash],\n\n [AtKeyword, LeftParenthesis],\n [AtKeyword, StringToken],\n [AtKeyword, Colon],\n\n [Percentage, Percentage],\n [Percentage, Dimension],\n [Percentage, FunctionToken],\n [Percentage, '-'],\n\n [RightParenthesis, Ident],\n [RightParenthesis, FunctionToken],\n [RightParenthesis, Percentage],\n [RightParenthesis, Dimension],\n [RightParenthesis, Hash],\n [RightParenthesis, '-']\n]);\n\nfunction createMap(pairs) {\n const isWhiteSpaceRequired = new Set(\n pairs.map(([prev, next]) => (code(prev) << 16 | code(next)))\n );\n\n return function(prevCode, type, value) {\n const nextCode = code(type, value);\n const nextCharCode = value.charCodeAt(0);\n const emitWs =\n (nextCharCode === HYPHENMINUS &&\n type !== Ident &&\n type !== FunctionToken &&\n type !== CDC) ||\n (nextCharCode === PLUSSIGN)\n ? isWhiteSpaceRequired.has(prevCode << 16 | nextCharCode << 8)\n : isWhiteSpaceRequired.has(prevCode << 16 | nextCode);\n\n if (emitWs) {\n this.emit(' ', WhiteSpace, true);\n }\n\n return nextCode;\n };\n}\n\nexport const spec = createMap(specPairs);\nexport const safe = createMap(safePairs);\n","import { tokenize, Delim, WhiteSpace } from '../tokenizer/index.js';\nimport { generateSourceMap } from './sourceMap.js';\nimport * as tokenBefore from './token-before.js';\n\nconst REVERSESOLIDUS = 0x005c; // U+005C REVERSE SOLIDUS (\\)\n\nfunction processChildren(node, delimeter) {\n if (typeof delimeter === 'function') {\n let prev = null;\n\n node.children.forEach(node => {\n if (prev !== null) {\n delimeter.call(this, prev);\n }\n\n this.node(node);\n prev = node;\n });\n\n return;\n }\n\n node.children.forEach(this.node, this);\n}\n\nfunction processChunk(chunk) {\n tokenize(chunk, (type, start, end) => {\n this.token(type, chunk.slice(start, end));\n });\n}\n\nexport function createGenerator(config) {\n const types = new Map();\n\n for (let name in config.node) {\n const item = config.node[name];\n const fn = item.generate || item;\n\n if (typeof fn === 'function') {\n types.set(name, item.generate || item);\n }\n }\n\n return function(node, options) {\n let buffer = '';\n let prevCode = 0;\n let handlers = {\n node(node) {\n if (types.has(node.type)) {\n types.get(node.type).call(publicApi, node);\n } else {\n throw new Error('Unknown node type: ' + node.type);\n }\n },\n tokenBefore: tokenBefore.safe,\n token(type, value) {\n prevCode = this.tokenBefore(prevCode, type, value);\n\n this.emit(value, type, false);\n\n if (type === Delim && value.charCodeAt(0) === REVERSESOLIDUS) {\n this.emit('\\n', WhiteSpace, true);\n }\n },\n emit(value) {\n buffer += value;\n },\n result() {\n return buffer;\n }\n };\n\n if (options) {\n if (typeof options.decorator === 'function') {\n handlers = options.decorator(handlers);\n }\n\n if (options.sourceMap) {\n handlers = generateSourceMap(handlers);\n }\n\n if (options.mode in tokenBefore) {\n handlers.tokenBefore = tokenBefore[options.mode];\n }\n }\n\n const publicApi = {\n node: (node) => handlers.node(node),\n children: processChildren,\n token: (type, value) => handlers.token(type, value),\n tokenize: processChunk\n };\n\n handlers.node(node);\n\n return handlers.result();\n };\n};\n","const { hasOwnProperty } = Object.prototype;\nconst noop = function() {};\n\nfunction ensureFunction(value) {\n return typeof value === 'function' ? value : noop;\n}\n\nfunction invokeForType(fn, type) {\n return function(node, item, list) {\n if (node.type === type) {\n fn.call(this, node, item, list);\n }\n };\n}\n\nfunction getWalkersFromStructure(name, nodeType) {\n const structure = nodeType.structure;\n const walkers = [];\n\n for (const key in structure) {\n if (hasOwnProperty.call(structure, key) === false) {\n continue;\n }\n\n let fieldTypes = structure[key];\n const walker = {\n name: key,\n type: false,\n nullable: false\n };\n\n if (!Array.isArray(fieldTypes)) {\n fieldTypes = [fieldTypes];\n }\n\n for (const fieldType of fieldTypes) {\n if (fieldType === null) {\n walker.nullable = true;\n } else if (typeof fieldType === 'string') {\n walker.type = 'node';\n } else if (Array.isArray(fieldType)) {\n walker.type = 'list';\n }\n }\n\n if (walker.type) {\n walkers.push(walker);\n }\n }\n\n if (walkers.length) {\n return {\n context: nodeType.walkContext,\n fields: walkers\n };\n }\n\n return null;\n}\n\nfunction getTypesFromConfig(config) {\n const types = {};\n\n for (const name in config.node) {\n if (hasOwnProperty.call(config.node, name)) {\n const nodeType = config.node[name];\n\n if (!nodeType.structure) {\n throw new Error('Missed `structure` field in `' + name + '` node type definition');\n }\n\n types[name] = getWalkersFromStructure(name, nodeType);\n }\n }\n\n return types;\n}\n\nfunction createTypeIterator(config, reverse) {\n const fields = config.fields.slice();\n const contextName = config.context;\n const useContext = typeof contextName === 'string';\n\n if (reverse) {\n fields.reverse();\n }\n\n return function(node, context, walk, walkReducer) {\n let prevContextValue;\n\n if (useContext) {\n prevContextValue = context[contextName];\n context[contextName] = node;\n }\n\n for (const field of fields) {\n const ref = node[field.name];\n\n if (!field.nullable || ref) {\n if (field.type === 'list') {\n const breakWalk = reverse\n ? ref.reduceRight(walkReducer, false)\n : ref.reduce(walkReducer, false);\n\n if (breakWalk) {\n return true;\n }\n } else if (walk(ref)) {\n return true;\n }\n }\n }\n\n if (useContext) {\n context[contextName] = prevContextValue;\n }\n };\n}\n\nfunction createFastTraveralMap({\n StyleSheet,\n Atrule,\n Rule,\n Block,\n DeclarationList\n}) {\n return {\n Atrule: {\n StyleSheet,\n Atrule,\n Rule,\n Block\n },\n Rule: {\n StyleSheet,\n Atrule,\n Rule,\n Block\n },\n Declaration: {\n StyleSheet,\n Atrule,\n Rule,\n Block,\n DeclarationList\n }\n };\n}\n\nexport function createWalker(config) {\n const types = getTypesFromConfig(config);\n const iteratorsNatural = {};\n const iteratorsReverse = {};\n const breakWalk = Symbol('break-walk');\n const skipNode = Symbol('skip-node');\n\n for (const name in types) {\n if (hasOwnProperty.call(types, name) && types[name] !== null) {\n iteratorsNatural[name] = createTypeIterator(types[name], false);\n iteratorsReverse[name] = createTypeIterator(types[name], true);\n }\n }\n\n const fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural);\n const fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse);\n\n const walk = function(root, options) {\n function walkNode(node, item, list) {\n const enterRet = enter.call(context, node, item, list);\n\n if (enterRet === breakWalk) {\n return true;\n }\n\n if (enterRet === skipNode) {\n return false;\n }\n\n if (iterators.hasOwnProperty(node.type)) {\n if (iterators[node.type](node, context, walkNode, walkReducer)) {\n return true;\n }\n }\n\n if (leave.call(context, node, item, list) === breakWalk) {\n return true;\n }\n\n return false;\n }\n\n let enter = noop;\n let leave = noop;\n let iterators = iteratorsNatural;\n let walkReducer = (ret, data, item, list) => ret || walkNode(data, item, list);\n const context = {\n break: breakWalk,\n skip: skipNode,\n\n root,\n stylesheet: null,\n atrule: null,\n atrulePrelude: null,\n rule: null,\n selector: null,\n block: null,\n declaration: null,\n function: null\n };\n\n if (typeof options === 'function') {\n enter = options;\n } else if (options) {\n enter = ensureFunction(options.enter);\n leave = ensureFunction(options.leave);\n\n if (options.reverse) {\n iterators = iteratorsReverse;\n }\n\n if (options.visit) {\n if (fastTraversalIteratorsNatural.hasOwnProperty(options.visit)) {\n iterators = options.reverse\n ? fastTraversalIteratorsReverse[options.visit]\n : fastTraversalIteratorsNatural[options.visit];\n } else if (!types.hasOwnProperty(options.visit)) {\n throw new Error('Bad value `' + options.visit + '` for `visit` option (should be: ' + Object.keys(types).sort().join(', ') + ')');\n }\n\n enter = invokeForType(enter, options.visit);\n leave = invokeForType(leave, options.visit);\n }\n }\n\n if (enter === noop && leave === noop) {\n throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\\'t a function');\n }\n\n walkNode(root);\n };\n\n walk.break = breakWalk;\n walk.skip = skipNode;\n\n walk.find = function(ast, fn) {\n let found = null;\n\n walk(ast, function(node, item, list) {\n if (fn.call(this, node, item, list)) {\n found = node;\n return breakWalk;\n }\n });\n\n return found;\n };\n\n walk.findLast = function(ast, fn) {\n let found = null;\n\n walk(ast, {\n reverse: true,\n enter(node, item, list) {\n if (fn.call(this, node, item, list)) {\n found = node;\n return breakWalk;\n }\n }\n });\n\n return found;\n };\n\n walk.findAll = function(ast, fn) {\n const found = [];\n\n walk(ast, function(node, item, list) {\n if (fn.call(this, node, item, list)) {\n found.push(node);\n }\n });\n\n return found;\n };\n\n return walk;\n};\n","function noop(value) {\n return value;\n}\n\nfunction generateMultiplier(multiplier) {\n const { min, max, comma } = multiplier;\n\n if (min === 0 && max === 0) {\n return comma ? '#?' : '*';\n }\n\n if (min === 0 && max === 1) {\n return '?';\n }\n\n if (min === 1 && max === 0) {\n return comma ? '#' : '+';\n }\n\n if (min === 1 && max === 1) {\n return '';\n }\n\n return (\n (comma ? '#' : '') +\n (min === max\n ? '{' + min + '}'\n : '{' + min + ',' + (max !== 0 ? max : '') + '}'\n )\n );\n}\n\nfunction generateTypeOpts(node) {\n switch (node.type) {\n case 'Range':\n return (\n ' [' +\n (node.min === null ? '-∞' : node.min) +\n ',' +\n (node.max === null ? '∞' : node.max) +\n ']'\n );\n\n default:\n throw new Error('Unknown node type `' + node.type + '`');\n }\n}\n\nfunction generateSequence(node, decorate, forceBraces, compact) {\n const combinator = node.combinator === ' ' || compact ? node.combinator : ' ' + node.combinator + ' ';\n const result = node.terms\n .map(term => internalGenerate(term, decorate, forceBraces, compact))\n .join(combinator);\n\n if (node.explicit || forceBraces) {\n return (compact || result[0] === ',' ? '[' : '[ ') + result + (compact ? ']' : ' ]');\n }\n\n return result;\n}\n\nfunction internalGenerate(node, decorate, forceBraces, compact) {\n let result;\n\n switch (node.type) {\n case 'Group':\n result =\n generateSequence(node, decorate, forceBraces, compact) +\n (node.disallowEmpty ? '!' : '');\n break;\n\n case 'Multiplier':\n // return since node is a composition\n return (\n internalGenerate(node.term, decorate, forceBraces, compact) +\n decorate(generateMultiplier(node), node)\n );\n\n case 'Type':\n result = '<' + node.name + (node.opts ? decorate(generateTypeOpts(node.opts), node.opts) : '') + '>';\n break;\n\n case 'Property':\n result = '<\\'' + node.name + '\\'>';\n break;\n\n case 'Keyword':\n result = node.name;\n break;\n\n case 'AtKeyword':\n result = '@' + node.name;\n break;\n\n case 'Function':\n result = node.name + '(';\n break;\n\n case 'String':\n case 'Token':\n result = node.value;\n break;\n\n case 'Comma':\n result = ',';\n break;\n\n default:\n throw new Error('Unknown node type `' + node.type + '`');\n }\n\n return decorate(result, node);\n}\n\nexport function generate(node, options) {\n let decorate = noop;\n let forceBraces = false;\n let compact = false;\n\n if (typeof options === 'function') {\n decorate = options;\n } else if (options) {\n forceBraces = Boolean(options.forceBraces);\n compact = Boolean(options.compact);\n if (typeof options.decorate === 'function') {\n decorate = options.decorate;\n }\n }\n\n return internalGenerate(node, decorate, forceBraces, compact);\n};\n","import { createCustomError } from '../utils/create-custom-error.js';\nimport { generate } from '../definition-syntax/generate.js';\n\nconst defaultLoc = { offset: 0, line: 1, column: 1 };\n\nfunction locateMismatch(matchResult, node) {\n const tokens = matchResult.tokens;\n const longestMatch = matchResult.longestMatch;\n const mismatchNode = longestMatch < tokens.length ? tokens[longestMatch].node || null : null;\n const badNode = mismatchNode !== node ? mismatchNode : null;\n let mismatchOffset = 0;\n let mismatchLength = 0;\n let entries = 0;\n let css = '';\n let start;\n let end;\n\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i].value;\n\n if (i === longestMatch) {\n mismatchLength = token.length;\n mismatchOffset = css.length;\n }\n\n if (badNode !== null && tokens[i].node === badNode) {\n if (i <= longestMatch) {\n entries++;\n } else {\n entries = 0;\n }\n }\n\n css += token;\n }\n\n if (longestMatch === tokens.length || entries > 1) { // last\n start = fromLoc(badNode || node, 'end') || buildLoc(defaultLoc, css);\n end = buildLoc(start);\n } else {\n start = fromLoc(badNode, 'start') ||\n buildLoc(fromLoc(node, 'start') || defaultLoc, css.slice(0, mismatchOffset));\n end = fromLoc(badNode, 'end') ||\n buildLoc(start, css.substr(mismatchOffset, mismatchLength));\n }\n\n return {\n css,\n mismatchOffset,\n mismatchLength,\n start,\n end\n };\n}\n\nfunction fromLoc(node, point) {\n const value = node && node.loc && node.loc[point];\n\n if (value) {\n return 'line' in value ? buildLoc(value) : value;\n }\n\n return null;\n}\n\nfunction buildLoc({ offset, line, column }, extra) {\n const loc = {\n offset,\n line,\n column\n };\n\n if (extra) {\n const lines = extra.split(/\\n|\\r\\n?|\\f/);\n\n loc.offset += extra.length;\n loc.line += lines.length - 1;\n loc.column = lines.length === 1 ? loc.column + extra.length : lines.pop().length + 1;\n }\n\n return loc;\n}\n\nexport const SyntaxReferenceError = function(type, referenceName) {\n const error = createCustomError(\n 'SyntaxReferenceError',\n type + (referenceName ? ' `' + referenceName + '`' : '')\n );\n\n error.reference = referenceName;\n\n return error;\n};\n\nexport const SyntaxMatchError = function(message, syntax, node, matchResult) {\n const error = createCustomError('SyntaxMatchError', message);\n const {\n css,\n mismatchOffset,\n mismatchLength,\n start,\n end\n } = locateMismatch(matchResult, node);\n\n error.rawMessage = message;\n error.syntax = syntax ? generate(syntax) : '';\n error.css = css;\n error.mismatchOffset = mismatchOffset;\n error.mismatchLength = mismatchLength;\n error.message = message + '\\n' +\n ' syntax: ' + error.syntax + '\\n' +\n ' value: ' + (css || '') + '\\n' +\n ' --------' + new Array(error.mismatchOffset + 1).join('-') + '^';\n\n Object.assign(error, start);\n error.loc = {\n source: (node && node.loc && node.loc.source) || '',\n start,\n end\n };\n\n return error;\n};\n","const keywords = new Map();\nconst properties = new Map();\nconst HYPHENMINUS = 45; // '-'.charCodeAt()\n\nexport const keyword = getKeywordDescriptor;\nexport const property = getPropertyDescriptor;\nexport const vendorPrefix = getVendorPrefix;\nexport function isCustomProperty(str, offset) {\n offset = offset || 0;\n\n return str.length - offset >= 2 &&\n str.charCodeAt(offset) === HYPHENMINUS &&\n str.charCodeAt(offset + 1) === HYPHENMINUS;\n}\n\nfunction getVendorPrefix(str, offset) {\n offset = offset || 0;\n\n // verdor prefix should be at least 3 chars length\n if (str.length - offset >= 3) {\n // vendor prefix starts with hyper minus following non-hyper minus\n if (str.charCodeAt(offset) === HYPHENMINUS &&\n str.charCodeAt(offset + 1) !== HYPHENMINUS) {\n // vendor prefix should contain a hyper minus at the ending\n const secondDashIndex = str.indexOf('-', offset + 2);\n\n if (secondDashIndex !== -1) {\n return str.substring(offset, secondDashIndex + 1);\n }\n }\n }\n\n return '';\n}\n\nfunction getKeywordDescriptor(keyword) {\n if (keywords.has(keyword)) {\n return keywords.get(keyword);\n }\n\n const name = keyword.toLowerCase();\n let descriptor = keywords.get(name);\n\n if (descriptor === undefined) {\n const custom = isCustomProperty(name, 0);\n const vendor = !custom ? getVendorPrefix(name, 0) : '';\n descriptor = Object.freeze({\n basename: name.substr(vendor.length),\n name,\n prefix: vendor,\n vendor,\n custom\n });\n }\n\n keywords.set(keyword, descriptor);\n\n return descriptor;\n}\n\nfunction getPropertyDescriptor(property) {\n if (properties.has(property)) {\n return properties.get(property);\n }\n\n let name = property;\n let hack = property[0];\n\n if (hack === '/') {\n hack = property[1] === '/' ? '//' : '/';\n } else if (hack !== '_' &&\n hack !== '*' &&\n hack !== '$' &&\n hack !== '#' &&\n hack !== '+' &&\n hack !== '&') {\n hack = '';\n }\n\n const custom = isCustomProperty(name, hack.length);\n\n // re-use result when possible (the same as for lower case)\n if (!custom) {\n name = name.toLowerCase();\n if (properties.has(name)) {\n const descriptor = properties.get(name);\n properties.set(property, descriptor);\n return descriptor;\n }\n }\n\n const vendor = !custom ? getVendorPrefix(name, hack.length) : '';\n const prefix = name.substr(0, hack.length + vendor.length);\n const descriptor = Object.freeze({\n basename: name.substr(prefix.length),\n name: name.substr(hack.length),\n hack,\n vendor,\n prefix,\n custom\n });\n\n properties.set(property, descriptor);\n\n return descriptor;\n}\n","// https://drafts.csswg.org/css-cascade-5/\nexport const cssWideKeywords = [\n 'initial',\n 'inherit',\n 'unset',\n 'revert',\n 'revert-layer'\n];\n","import {\n isDigit,\n cmpChar,\n Delim,\n WhiteSpace,\n Comment,\n Ident,\n Number as NumberToken,\n Dimension\n} from '../tokenizer/index.js';\n\nconst PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)\nconst HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-)\nconst N = 0x006E; // U+006E LATIN SMALL LETTER N (n)\nconst DISALLOW_SIGN = true;\nconst ALLOW_SIGN = false;\n\nfunction isDelim(token, code) {\n return token !== null && token.type === Delim && token.value.charCodeAt(0) === code;\n}\n\nfunction skipSC(token, offset, getNextToken) {\n while (token !== null && (token.type === WhiteSpace || token.type === Comment)) {\n token = getNextToken(++offset);\n }\n\n return offset;\n}\n\nfunction checkInteger(token, valueOffset, disallowSign, offset) {\n if (!token) {\n return 0;\n }\n\n const code = token.value.charCodeAt(valueOffset);\n\n if (code === PLUSSIGN || code === HYPHENMINUS) {\n if (disallowSign) {\n // Number sign is not allowed\n return 0;\n }\n valueOffset++;\n }\n\n for (; valueOffset < token.value.length; valueOffset++) {\n if (!isDigit(token.value.charCodeAt(valueOffset))) {\n // Integer is expected\n return 0;\n }\n }\n\n return offset + 1;\n}\n\n// ... \n// ... ['+' | '-'] \nfunction consumeB(token, offset_, getNextToken) {\n let sign = false;\n let offset = skipSC(token, offset_, getNextToken);\n\n token = getNextToken(offset);\n\n if (token === null) {\n return offset_;\n }\n\n if (token.type !== NumberToken) {\n if (isDelim(token, PLUSSIGN) || isDelim(token, HYPHENMINUS)) {\n sign = true;\n offset = skipSC(getNextToken(++offset), offset, getNextToken);\n token = getNextToken(offset);\n\n if (token === null || token.type !== NumberToken) {\n return 0;\n }\n } else {\n return offset_;\n }\n }\n\n if (!sign) {\n const code = token.value.charCodeAt(0);\n if (code !== PLUSSIGN && code !== HYPHENMINUS) {\n // Number sign is expected\n return 0;\n }\n }\n\n return checkInteger(token, sign ? 0 : 1, sign, offset);\n}\n\n// An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb\nexport default function anPlusB(token, getNextToken) {\n /* eslint-disable brace-style*/\n let offset = 0;\n\n if (!token) {\n return 0;\n }\n\n // \n if (token.type === NumberToken) {\n return checkInteger(token, 0, ALLOW_SIGN, offset); // b\n }\n\n // -n\n // -n \n // -n ['+' | '-'] \n // -n- \n // \n else if (token.type === Ident && token.value.charCodeAt(0) === HYPHENMINUS) {\n // expect 1st char is N\n if (!cmpChar(token.value, 1, N)) {\n return 0;\n }\n\n switch (token.value.length) {\n // -n\n // -n \n // -n ['+' | '-'] \n case 2:\n return consumeB(getNextToken(++offset), offset, getNextToken);\n\n // -n- \n case 3:\n if (token.value.charCodeAt(2) !== HYPHENMINUS) {\n return 0;\n }\n\n offset = skipSC(getNextToken(++offset), offset, getNextToken);\n token = getNextToken(offset);\n\n return checkInteger(token, 0, DISALLOW_SIGN, offset);\n\n // \n default:\n if (token.value.charCodeAt(2) !== HYPHENMINUS) {\n return 0;\n }\n\n return checkInteger(token, 3, DISALLOW_SIGN, offset);\n }\n }\n\n // '+'? n\n // '+'? n \n // '+'? n ['+' | '-'] \n // '+'? n- \n // '+'? \n else if (token.type === Ident || (isDelim(token, PLUSSIGN) && getNextToken(offset + 1).type === Ident)) {\n // just ignore a plus\n if (token.type !== Ident) {\n token = getNextToken(++offset);\n }\n\n if (token === null || !cmpChar(token.value, 0, N)) {\n return 0;\n }\n\n switch (token.value.length) {\n // '+'? n\n // '+'? n \n // '+'? n ['+' | '-'] \n case 1:\n return consumeB(getNextToken(++offset), offset, getNextToken);\n\n // '+'? n- \n case 2:\n if (token.value.charCodeAt(1) !== HYPHENMINUS) {\n return 0;\n }\n\n offset = skipSC(getNextToken(++offset), offset, getNextToken);\n token = getNextToken(offset);\n\n return checkInteger(token, 0, DISALLOW_SIGN, offset);\n\n // '+'? \n default:\n if (token.value.charCodeAt(1) !== HYPHENMINUS) {\n return 0;\n }\n\n return checkInteger(token, 2, DISALLOW_SIGN, offset);\n }\n }\n\n // \n // \n // \n // \n // ['+' | '-'] \n else if (token.type === Dimension) {\n let code = token.value.charCodeAt(0);\n let sign = code === PLUSSIGN || code === HYPHENMINUS ? 1 : 0;\n let i = sign;\n\n for (; i < token.value.length; i++) {\n if (!isDigit(token.value.charCodeAt(i))) {\n break;\n }\n }\n\n if (i === sign) {\n // Integer is expected\n return 0;\n }\n\n if (!cmpChar(token.value, i, N)) {\n return 0;\n }\n\n // \n // \n // ['+' | '-'] \n if (i + 1 === token.value.length) {\n return consumeB(getNextToken(++offset), offset, getNextToken);\n } else {\n if (token.value.charCodeAt(i + 1) !== HYPHENMINUS) {\n return 0;\n }\n\n // \n if (i + 2 === token.value.length) {\n offset = skipSC(getNextToken(++offset), offset, getNextToken);\n token = getNextToken(offset);\n\n return checkInteger(token, 0, DISALLOW_SIGN, offset);\n }\n // \n else {\n return checkInteger(token, i + 2, DISALLOW_SIGN, offset);\n }\n }\n }\n\n return 0;\n};\n","import {\n isHexDigit,\n cmpChar,\n Ident,\n Delim,\n Number as NumberToken,\n Dimension\n} from '../tokenizer/index.js';\n\nconst PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)\nconst HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-)\nconst QUESTIONMARK = 0x003F; // U+003F QUESTION MARK (?)\nconst U = 0x0075; // U+0075 LATIN SMALL LETTER U (u)\n\nfunction isDelim(token, code) {\n return token !== null && token.type === Delim && token.value.charCodeAt(0) === code;\n}\n\nfunction startsWith(token, code) {\n return token.value.charCodeAt(0) === code;\n}\n\nfunction hexSequence(token, offset, allowDash) {\n let hexlen = 0;\n\n for (let pos = offset; pos < token.value.length; pos++) {\n const code = token.value.charCodeAt(pos);\n\n if (code === HYPHENMINUS && allowDash && hexlen !== 0) {\n hexSequence(token, offset + hexlen + 1, false);\n return 6; // dissallow following question marks\n }\n\n if (!isHexDigit(code)) {\n return 0; // not a hex digit\n }\n\n if (++hexlen > 6) {\n return 0; // too many hex digits\n };\n }\n\n return hexlen;\n}\n\nfunction withQuestionMarkSequence(consumed, length, getNextToken) {\n if (!consumed) {\n return 0; // nothing consumed\n }\n\n while (isDelim(getNextToken(length), QUESTIONMARK)) {\n if (++consumed > 6) {\n return 0; // too many question marks\n }\n\n length++;\n }\n\n return length;\n}\n\n// https://drafts.csswg.org/css-syntax/#urange\n// Informally, the production has three forms:\n// U+0001\n// Defines a range consisting of a single code point, in this case the code point \"1\".\n// U+0001-00ff\n// Defines a range of codepoints between the first and the second value, in this case\n// the range between \"1\" and \"ff\" (255 in decimal) inclusive.\n// U+00??\n// Defines a range of codepoints where the \"?\" characters range over all hex digits,\n// in this case defining the same as the value U+0000-00ff.\n// In each form, a maximum of 6 digits is allowed for each hexadecimal number (if you treat \"?\" as a hexadecimal digit).\n//\n// =\n// u '+' '?'* |\n// u '?'* |\n// u '?'* |\n// u |\n// u |\n// u '+' '?'+\nexport default function urange(token, getNextToken) {\n let length = 0;\n\n // should start with `u` or `U`\n if (token === null || token.type !== Ident || !cmpChar(token.value, 0, U)) {\n return 0;\n }\n\n token = getNextToken(++length);\n if (token === null) {\n return 0;\n }\n\n // u '+' '?'*\n // u '+' '?'+\n if (isDelim(token, PLUSSIGN)) {\n token = getNextToken(++length);\n if (token === null) {\n return 0;\n }\n\n if (token.type === Ident) {\n // u '+' '?'*\n return withQuestionMarkSequence(hexSequence(token, 0, true), ++length, getNextToken);\n }\n\n if (isDelim(token, QUESTIONMARK)) {\n // u '+' '?'+\n return withQuestionMarkSequence(1, ++length, getNextToken);\n }\n\n // Hex digit or question mark is expected\n return 0;\n }\n\n // u '?'*\n // u \n // u \n if (token.type === NumberToken) {\n const consumedHexLength = hexSequence(token, 1, true);\n if (consumedHexLength === 0) {\n return 0;\n }\n\n token = getNextToken(++length);\n if (token === null) {\n // u \n return length;\n }\n\n if (token.type === Dimension || token.type === NumberToken) {\n // u \n // u \n if (!startsWith(token, HYPHENMINUS) || !hexSequence(token, 1, false)) {\n return 0;\n }\n\n return length + 1;\n }\n\n // u '?'*\n return withQuestionMarkSequence(consumedHexLength, length, getNextToken);\n }\n\n // u '?'*\n if (token.type === Dimension) {\n return withQuestionMarkSequence(hexSequence(token, 1, true), ++length, getNextToken);\n }\n\n return 0;\n};\n","import { cssWideKeywords } from './generic-const.js';\nimport anPlusB from './generic-an-plus-b.js';\nimport urange from './generic-urange.js';\nimport {\n isIdentifierStart,\n isHexDigit,\n isDigit,\n cmpStr,\n consumeNumber,\n\n Ident,\n Function as FunctionToken,\n AtKeyword,\n Hash,\n String as StringToken,\n BadString,\n Url,\n BadUrl,\n Delim,\n Number as NumberToken,\n Percentage,\n Dimension,\n WhiteSpace,\n CDO,\n CDC,\n Colon,\n Semicolon,\n Comma,\n LeftSquareBracket,\n RightSquareBracket,\n LeftParenthesis,\n RightParenthesis,\n LeftCurlyBracket,\n RightCurlyBracket\n} from '../tokenizer/index.js';\n\nconst calcFunctionNames = ['calc(', '-moz-calc(', '-webkit-calc('];\nconst balancePair = new Map([\n [FunctionToken, RightParenthesis],\n [LeftParenthesis, RightParenthesis],\n [LeftSquareBracket, RightSquareBracket],\n [LeftCurlyBracket, RightCurlyBracket]\n]);\n\n// units\nconst LENGTH = [\n // absolute length units https://www.w3.org/TR/css-values-3/#lengths\n 'cm', 'mm', 'q', 'in', 'pt', 'pc', 'px',\n // font-relative length units https://drafts.csswg.org/css-values-4/#font-relative-lengths\n 'em', 'rem',\n 'ex', 'rex',\n 'cap', 'rcap',\n 'ch', 'rch',\n 'ic', 'ric',\n 'lh', 'rlh',\n // viewport-percentage lengths https://drafts.csswg.org/css-values-4/#viewport-relative-lengths\n 'vw', 'svw', 'lvw', 'dvw',\n 'vh', 'svh', 'lvh', 'dvh',\n 'vi', 'svi', 'lvi', 'dvi',\n 'vb', 'svb', 'lvb', 'dvb',\n 'vmin', 'svmin', 'lvmin', 'dvmin',\n 'vmax', 'svmax', 'lvmax', 'dvmax',\n // container relative lengths https://drafts.csswg.org/css-contain-3/#container-lengths\n 'cqw', 'cqh', 'cqi', 'cqb', 'cqmin', 'cqmax'\n];\nconst ANGLE = ['deg', 'grad', 'rad', 'turn']; // https://www.w3.org/TR/css-values-3/#angles\nconst TIME = ['s', 'ms']; // https://www.w3.org/TR/css-values-3/#time\nconst FREQUENCY = ['hz', 'khz']; // https://www.w3.org/TR/css-values-3/#frequency\nconst RESOLUTION = ['dpi', 'dpcm', 'dppx', 'x']; // https://www.w3.org/TR/css-values-3/#resolution\nconst FLEX = ['fr']; // https://drafts.csswg.org/css-grid/#fr-unit\nconst DECIBEL = ['db']; // https://www.w3.org/TR/css3-speech/#mixing-props-voice-volume\nconst SEMITONES = ['st']; // https://www.w3.org/TR/css3-speech/#voice-props-voice-pitch\n\n// safe char code getter\nfunction charCodeAt(str, index) {\n return index < str.length ? str.charCodeAt(index) : 0;\n}\n\nfunction eqStr(actual, expected) {\n return cmpStr(actual, 0, actual.length, expected);\n}\n\nfunction eqStrAny(actual, expected) {\n for (let i = 0; i < expected.length; i++) {\n if (eqStr(actual, expected[i])) {\n return true;\n }\n }\n\n return false;\n}\n\n// IE postfix hack, i.e. 123\\0 or 123px\\9\nfunction isPostfixIeHack(str, offset) {\n if (offset !== str.length - 2) {\n return false;\n }\n\n return (\n charCodeAt(str, offset) === 0x005C && // U+005C REVERSE SOLIDUS (\\)\n isDigit(charCodeAt(str, offset + 1))\n );\n}\n\nfunction outOfRange(opts, value, numEnd) {\n if (opts && opts.type === 'Range') {\n const num = Number(\n numEnd !== undefined && numEnd !== value.length\n ? value.substr(0, numEnd)\n : value\n );\n\n if (isNaN(num)) {\n return true;\n }\n\n // FIXME: when opts.min is a string it's a dimension, skip a range validation\n // for now since it requires a type covertation which is not implmented yet\n if (opts.min !== null && num < opts.min && typeof opts.min !== 'string') {\n return true;\n }\n\n // FIXME: when opts.max is a string it's a dimension, skip a range validation\n // for now since it requires a type covertation which is not implmented yet\n if (opts.max !== null && num > opts.max && typeof opts.max !== 'string') {\n return true;\n }\n }\n\n return false;\n}\n\nfunction consumeFunction(token, getNextToken) {\n let balanceCloseType = 0;\n let balanceStash = [];\n let length = 0;\n\n // balanced token consuming\n scan:\n do {\n switch (token.type) {\n case RightCurlyBracket:\n case RightParenthesis:\n case RightSquareBracket:\n if (token.type !== balanceCloseType) {\n break scan;\n }\n\n balanceCloseType = balanceStash.pop();\n\n if (balanceStash.length === 0) {\n length++;\n break scan;\n }\n\n break;\n\n case FunctionToken:\n case LeftParenthesis:\n case LeftSquareBracket:\n case LeftCurlyBracket:\n balanceStash.push(balanceCloseType);\n balanceCloseType = balancePair.get(token.type);\n break;\n }\n\n length++;\n } while (token = getNextToken(length));\n\n return length;\n}\n\n// TODO: implement\n// can be used wherever , , ,