import * as PropertySymbol from '../PropertySymbol.js';
import NamespaceURI from '../config/NamespaceURI.js';
import Node from '../nodes/node/Node.js';
import XMLEncodeUtility from '../utilities/XMLEncodeUtility.js';
import NodeFactory from '../nodes/NodeFactory.js';
/**
 * Markup RegExp.
 *
 * Group 1: Beginning of start tag (e.g. "div" in "<div").
 * Group 2: End tag (e.g. "div" in "</div>").
 * Group 3: Comment with ending "--" (e.g. " Comment 1 " in "<!-- Comment 1 -->").
 * Group 4: Comment without ending "--" (e.g. " Comment 1 " in "<!-- Comment 1 >").
 * Group 5: Exclamation mark comment (e.g. "DOCTYPE html" in "<!DOCTYPE html>").
 * Group 6: Processing instruction (e.g. "xml version="1.0"?" in "<?xml version="1.0"?>").
 * Group 7: End of self closing start tag (e.g. "/>" in "<img/>").
 * Group 8: End of start tag (e.g. ">" in "<div>").
 */
const MARKUP_REGEXP = /<([^\s/!>?]+)|<\/([^\s/!>?]+)\s*>|(<!--)|(-->)|(<!)|(<\?)|(\/>)|(>)/gm;
/**
 * Attribute RegExp.
 *
 * Group 1: Attribute name when the attribute has a value using double apostrophe (e.g. "name" in "<div name="value">").
 * Group 2: Attribute value when the attribute has a value using double apostrophe (e.g. "value" in "<div name="value">").
 * Group 3: Attribute end apostrophe when the attribute has a value using double apostrophe (e.g. '"' in "<div name="value">").
 * Group 4: Attribute name when the attribute has a value using single apostrophe (e.g. "name" in "<div name='value'>").
 * Group 5: Attribute value when the attribute has a value using single apostrophe (e.g. "value" in "<div name='value'>").
 * Group 6: Attribute end apostrophe when the attribute has a value using single apostrophe (e.g. "'" in "<div name='value'>").
 * Group 7: Attribute name when the attribute has no value (e.g. "disabled" in "<div disabled>").
 */
const ATTRIBUTE_REGEXP = /\s*([a-zA-Z0-9-_:]+)\s*=\s*"([^"]*)("{0,1})|\s*([a-zA-Z0-9-_:]+)\s*=\s*'([^']*)('{0,1})/gm;
/**
 * Attribute without value RegExp.
 */
const ATTRIBUTE_WITHOUT_VALUE_REGEXP = /^\s*([a-zA-Z0-9-_:]+)$/;
/**
 * XML processing instruction version RegExp.
 */
const XML_PROCESSING_INSTRUCTION_VERSION_REGEXP = /version="[^"]+"/;
/**
 * Document type attribute RegExp.
 *
 * Group 1: Attribute value.
 */
const DOCUMENT_TYPE_ATTRIBUTE_REGEXP = /"([^"]+)"/gm;
/**
 * Space RegExp.
 */
const SPACE_REGEXP = /\s+/;
/**
 * New line RegExp.
 */
const NEW_LINE_REGEXP = /\n/g;
/**
 * Markup read state (which state the parser is in).
 */
var MarkupReadStateEnum;
(function (MarkupReadStateEnum) {
    MarkupReadStateEnum["any"] = "any";
    MarkupReadStateEnum["startTag"] = "startTag";
    MarkupReadStateEnum["comment"] = "comment";
    MarkupReadStateEnum["documentType"] = "documentType";
    MarkupReadStateEnum["processingInstruction"] = "processingInstruction";
    MarkupReadStateEnum["error"] = "error";
})(MarkupReadStateEnum || (MarkupReadStateEnum = {}));
const NAMESPACE_URIS = Object.values(NamespaceURI);
/**
 * XML parser.
 */
export default class XMLParser {
    window;
    rootNode = null;
    nodeStack = [];
    tagNameStack = [];
    defaultNamespaceStack = null;
    namespacePrefixStack = null;
    startTagIndex = 0;
    markupRegExp = null;
    lastIndex = 0;
    errorIndex = 0;
    nextElement = null;
    nextTagName = null;
    currentNode = null;
    readState = MarkupReadStateEnum.any;
    errorMessage = null;
    /**
     * Constructor.
     *
     * @param window Window.
     */
    constructor(window) {
        this.window = window;
    }
    /**
     * Parses XML and returns an XML document containing nodes found.
     *
     * @param xml XML string.
     * @returns XML document.
     */
    parse(xml) {
        this.rootNode = new this.window.XMLDocument();
        this.nodeStack = [this.rootNode];
        this.tagNameStack = [null];
        this.currentNode = this.rootNode;
        this.readState = MarkupReadStateEnum.any;
        this.defaultNamespaceStack = [null];
        this.namespacePrefixStack = [null];
        this.startTagIndex = 0;
        this.errorIndex = 0;
        this.errorMessage = null;
        this.markupRegExp = new RegExp(MARKUP_REGEXP, 'gm');
        this.lastIndex = 0;
        let match;
        this.rootNode[PropertySymbol.defaultView] = this.window;
        xml = String(xml);
        while ((match = this.markupRegExp.exec(xml))) {
            switch (this.readState) {
                case MarkupReadStateEnum.any:
                    if (match.index !== this.lastIndex &&
                        (match[1] || match[2] || match[3] || match[4] || match[5] !== undefined || match[6])) {
                        // Plain text between tags.
                        this.parsePlainText(xml.substring(this.lastIndex, match.index));
                    }
                    if (match[1]) {
                        // Start tag.
                        this.parseStartTag(match[1]);
                    }
                    else if (match[2]) {
                        // End tag.
                        if (!this.parseEndTag(match[2])) {
                            this.errorMessage = `Opening and ending tag mismatch: ${this.tagNameStack[this.tagNameStack.length - 1]} line ${xml.substring(0, this.startTagIndex).split('\n').length} and ${match[2]}\n`;
                            this.errorIndex = this.markupRegExp.lastIndex;
                            this.readState = MarkupReadStateEnum.error;
                            this.removeOverflowingTextNodes();
                        }
                    }
                    else if (match[3]) {
                        // Comment.
                        this.startTagIndex = this.markupRegExp.lastIndex;
                        this.readState = MarkupReadStateEnum.comment;
                    }
                    else if (match[5] !== undefined) {
                        // Document type
                        this.startTagIndex = this.markupRegExp.lastIndex;
                        this.readState = MarkupReadStateEnum.documentType;
                    }
                    else if (match[6]) {
                        // Processing instruction.
                        this.startTagIndex = this.markupRegExp.lastIndex;
                        this.readState = MarkupReadStateEnum.processingInstruction;
                    }
                    else {
                        // Plain text between tags, including the matched tag as it is not a valid start or end tag.
                        this.parsePlainText(xml.substring(this.lastIndex, this.markupRegExp.lastIndex));
                    }
                    break;
                case MarkupReadStateEnum.startTag:
                    // End of start tag
                    // match[7] is matching "/>" (e.g. "<img/>").
                    // match[8] is matching ">" (e.g. "<div>").
                    if (match[7] || match[8]) {
                        const attributeString = xml.substring(this.startTagIndex, match[2] ? this.markupRegExp.lastIndex - 1 : match.index);
                        const isSelfClosed = !!match[7];
                        this.parseEndOfStartTag(attributeString, isSelfClosed);
                    }
                    else {
                        this.errorMessage =
                            match[2] && this.lastIndex !== this.startTagIndex
                                ? `Unescaped '&lt;' not allowed in attributes values\n`
                                : 'error parsing attribute name\n';
                        this.errorIndex = match.index;
                        this.readState = MarkupReadStateEnum.error;
                        this.removeOverflowingTextNodes();
                    }
                    break;
                case MarkupReadStateEnum.comment:
                    // Comment end tag.
                    if (match[4]) {
                        this.parseComment(xml.substring(this.startTagIndex, match.index));
                    }
                    break;
                case MarkupReadStateEnum.documentType:
                    // Document type end tag.
                    if (match[7] || match[8]) {
                        this.parseDocumentType(xml.substring(this.startTagIndex, match.index));
                    }
                    break;
                case MarkupReadStateEnum.processingInstruction:
                    // Processing instruction end tag.
                    if (match[7] || match[8]) {
                        this.parseProcessingInstruction(xml.substring(this.startTagIndex, match.index));
                    }
                    break;
                case MarkupReadStateEnum.error:
                    this.parseError(xml.slice(0, this.errorIndex), this.errorMessage);
                    return this.rootNode;
            }
            this.lastIndex = this.markupRegExp.lastIndex;
        }
        if (this.readState === MarkupReadStateEnum.error) {
            this.parseError(xml.slice(0, this.errorIndex), this.errorMessage);
            return this.rootNode;
        }
        if (this.readState === MarkupReadStateEnum.comment) {
            this.parseError(xml, 'Comment not terminated\n');
            this.removeOverflowingTextNodes();
            return this.rootNode;
        }
        // Missing start tag (e.g. when parsing just a string like "Test").
        if (this.rootNode[PropertySymbol.elementArray].length === 0) {
            this.parseError('', `Start tag expected, '&lt;' not found`);
            return this.rootNode;
        }
        // Plain text after tags.
        if (this.lastIndex !== xml.length && this.currentNode) {
            this.parsePlainText(xml.substring(this.lastIndex));
        }
        // Missing end tag.
        if (this.nodeStack.length !== 1) {
            this.parseError(xml, this.nextElement
                ? 'attributes construct error\n'
                : 'Premature end of data in tag article line 1\n');
            return this.rootNode;
        }
        return this.rootNode;
    }
    /**
     * Parses plain text.
     *
     * @param text Text.
     */
    parsePlainText(text) {
        if (this.currentNode === this.rootNode) {
            const xmlText = text.replace(SPACE_REGEXP, '');
            if (xmlText) {
                this.errorMessage = 'Extra content at the end of the document\n';
                this.errorIndex = this.lastIndex;
                this.readState = MarkupReadStateEnum.error;
            }
        }
        else if (text.includes('&nbsp;')) {
            this.errorMessage = `Entity 'nbsp' not defined\n`;
            this.errorIndex = this.lastIndex + text.indexOf('&nbsp;') + 6;
            this.readState = MarkupReadStateEnum.error;
        }
        else {
            this.currentNode[PropertySymbol.appendChild](this.rootNode.createTextNode(XMLEncodeUtility.decodeXMLEntities(text)), true);
        }
    }
    /**
     * Parses processing instruction.
     *
     * @param text Text.
     */
    parseProcessingInstruction(text) {
        const markupRegExp = this.markupRegExp;
        const parts = text.split(SPACE_REGEXP);
        const endsWithQuestionMark = text[text.length - 1] === '?';
        if (parts[0] === 'xml') {
            if (this.currentNode !== this.rootNode ||
                this.rootNode[PropertySymbol.nodeArray].length !== 0 ||
                parts.length === 1) {
                this.errorMessage = 'XML declaration allowed only at the start of the document\n';
                this.errorIndex = markupRegExp.lastIndex - text.length + 2;
                this.readState = MarkupReadStateEnum.error;
                this.removeOverflowingTextNodes();
            }
            else if (!XML_PROCESSING_INSTRUCTION_VERSION_REGEXP.test(parts[1])) {
                this.errorMessage = 'Malformed declaration expecting version\n';
                this.errorIndex = markupRegExp.lastIndex - text.length + 3;
                this.readState = MarkupReadStateEnum.error;
            }
            else if (!endsWithQuestionMark) {
                this.errorMessage = 'Blank needed here\n';
                this.errorIndex = markupRegExp.lastIndex - 1;
                this.readState = MarkupReadStateEnum.error;
            }
            else {
                // When the processing instruction has "xml" as target, we should not add it as a child node.
                // Instead we will store the state on the root node, so that it is added when serializing the document with XMLSerializer.
                // TODO: We need to handle validation of encoding.
                const name = parts[0];
                // We need to remove the ending "?".
                const content = parts.slice(1).join(' ').slice(0, -1);
                this.rootNode[PropertySymbol.xmlProcessingInstruction] =
                    this.rootNode.createProcessingInstruction(name, content);
                this.readState = MarkupReadStateEnum.any;
            }
        }
        else {
            if (parts.length === 1 && !endsWithQuestionMark) {
                this.errorMessage = 'ParsePI: PI processing-instruction space expected\n';
                this.errorIndex = markupRegExp.lastIndex - 1;
                this.readState = MarkupReadStateEnum.error;
            }
            else if (parts.length > 1 && !endsWithQuestionMark) {
                this.errorMessage = 'ParsePI: PI processing-instruction never end ...\n';
                this.errorIndex = markupRegExp.lastIndex - 1;
                this.readState = MarkupReadStateEnum.error;
            }
            else {
                const name = parts[0];
                // We need to remove the ending "?".
                const content = parts.slice(1).join(' ').slice(0, -1);
                this.currentNode[PropertySymbol.appendChild](this.rootNode.createProcessingInstruction(name, content), true);
                this.readState = MarkupReadStateEnum.any;
            }
        }
    }
    /**
     * Parses comment.
     *
     * @param comment Comment.
     */
    parseComment(comment) {
        // Comments are not allowed in the root when parsing XML.
        if (this.currentNode !== this.rootNode) {
            this.currentNode[PropertySymbol.appendChild](this.rootNode.createComment(XMLEncodeUtility.decodeXMLEntities(comment)), true);
        }
        this.readState = MarkupReadStateEnum.any;
    }
    /**
     * Parses document type.
     *
     * @param text Text.
     */
    parseDocumentType(text) {
        const markupRegExp = this.markupRegExp;
        if (this.currentNode === this.rootNode &&
            this.rootNode[PropertySymbol.nodeArray].length === 0) {
            const documentType = this.getDocumentType(XMLEncodeUtility.decodeXMLEntities(text));
            if (documentType?.name) {
                this.rootNode[PropertySymbol.appendChild](this.window.document.implementation.createDocumentType(documentType.name, documentType.publicId, documentType.systemId), true);
                this.readState = MarkupReadStateEnum.any;
            }
            else if (documentType) {
                this.errorMessage = 'xmlParseDocTypeDecl : no DOCTYPE name\n';
                this.errorIndex = markupRegExp.lastIndex - text.length - 2;
                this.readState = MarkupReadStateEnum.error;
            }
            else {
                this.errorMessage = 'StartTag: invalid element name\n';
                this.errorIndex = markupRegExp.lastIndex - text.length - 2;
                this.readState = MarkupReadStateEnum.error;
            }
        }
        else if (this.currentNode === this.rootNode &&
            this.rootNode[PropertySymbol.elementArray].length === 1) {
            this.errorMessage = 'Extra content at the end of the document\n';
            this.errorIndex = markupRegExp.lastIndex - text.length - 2;
            this.readState = MarkupReadStateEnum.error;
        }
        else {
            this.errorMessage = 'StartTag: invalid element name\n';
            this.errorIndex = markupRegExp.lastIndex - text.length - 2;
            this.readState = MarkupReadStateEnum.error;
        }
    }
    /**
     * Parses start tag.
     *
     * @param tagName Tag name.
     */
    parseStartTag(tagName) {
        const parts = tagName.split(':');
        const namespacePrefixStack = this.namespacePrefixStack;
        const defaultNamespaceStack = this.defaultNamespaceStack;
        if (parts.length > 1) {
            this.nextElement = this.rootNode.createElementNS(namespacePrefixStack[namespacePrefixStack.length - 1]?.get(parts[0]) || null, tagName);
        }
        else {
            this.nextElement = this.rootNode.createElementNS(defaultNamespaceStack[defaultNamespaceStack.length - 1] || null, tagName);
        }
        namespacePrefixStack.push(new Map(namespacePrefixStack[namespacePrefixStack.length - 1]));
        this.nextTagName = tagName;
        this.startTagIndex = this.markupRegExp.lastIndex;
        this.readState = MarkupReadStateEnum.startTag;
    }
    /**
     * Parses end of start tag.
     *
     * @param attributeString Attribute string.
     * @param isSelfClosed Is self closed.
     */
    parseEndOfStartTag(attributeString, isSelfClosed) {
        const namespacePrefix = this.namespacePrefixStack[this.namespacePrefixStack.length - 1];
        if (attributeString) {
            const attributeRegexp = new RegExp(ATTRIBUTE_REGEXP, 'gm');
            let attributeMatch;
            let lastIndex = 0;
            while ((attributeMatch = attributeRegexp.exec(attributeString))) {
                const textBetweenAttributes = attributeString
                    .substring(lastIndex, attributeMatch.index)
                    .replace(SPACE_REGEXP, '');
                // If there is text between attributes, the text did not match a valid attribute.
                if (textBetweenAttributes.length) {
                    const match = textBetweenAttributes.match(ATTRIBUTE_WITHOUT_VALUE_REGEXP);
                    this.errorMessage = match
                        ? `Specification mandates value for attribute ${match[1]}\n`
                        : 'attributes construct error\n';
                    this.errorIndex = this.startTagIndex;
                    this.readState = MarkupReadStateEnum.error;
                    this.removeOverflowingTextNodes();
                    return;
                }
                if ((attributeMatch[1] && attributeMatch[3] === '"') ||
                    (attributeMatch[4] && attributeMatch[6] === "'")) {
                    // Valid attribute name and value.
                    const name = attributeMatch[1] ?? attributeMatch[4];
                    const rawValue = attributeMatch[2] ?? attributeMatch[5];
                    // In XML, new line characters should be replaced with a space.
                    const value = rawValue
                        ? XMLEncodeUtility.decodeXMLAttributeValue(rawValue.replace(NEW_LINE_REGEXP, ' '))
                        : '';
                    const attributes = this.nextElement[PropertySymbol.attributes];
                    const nameParts = name.split(':');
                    if (nameParts.length > 2 ||
                        (nameParts.length === 2 && (!nameParts[0] || !nameParts[1]))) {
                        this.errorMessage = `Failed to parse QName '${name}'\n`;
                        this.errorIndex =
                            this.startTagIndex + attributeMatch.index + attributeMatch[0].split('=')[0].length;
                        this.readState = MarkupReadStateEnum.error;
                        return;
                    }
                    let namespaceURI = null;
                    // In the SVG namespace, the attribute "xmlns" should be set to the "http://www.w3.org/2000/xmlns/" namespace and "xlink" to the "http://www.w3.org/1999/xlink" namespace.
                    switch (nameParts[0]) {
                        case 'xmlns':
                            namespaceURI = NamespaceURI.xmlns;
                            break;
                        case 'xlink':
                            namespaceURI = NamespaceURI.xlink;
                            break;
                    }
                    if (!attributes.getNamedItemNS(namespaceURI, nameParts[1] ?? name)) {
                        const attribute = NodeFactory.createNode(this.rootNode, this.window.Attr);
                        attribute[PropertySymbol.namespaceURI] = namespaceURI;
                        attribute[PropertySymbol.name] = name;
                        attribute[PropertySymbol.localName] =
                            namespaceURI && nameParts[1] ? nameParts[1] : name;
                        attribute[PropertySymbol.prefix] = namespaceURI && nameParts[1] ? nameParts[0] : null;
                        attribute[PropertySymbol.value] = value;
                        attributes[PropertySymbol.setNamedItem](attribute);
                        // Attributes prefixed with "xmlns:" should be added to the namespace prefix map, so that the prefix can be added as namespaceURI to elements using the prefix.
                        if (attribute[PropertySymbol.prefix] === 'xmlns') {
                            namespacePrefix.set(attribute[PropertySymbol.localName], value);
                            // If the prefix matches the current element, we should set the namespace URI of the element to the value of the attribute.
                            // We don't need to upgrade the element, as there are no defined element types using a prefix.
                            if (this.nextElement[PropertySymbol.prefix] === attribute[PropertySymbol.localName]) {
                                this.nextElement[PropertySymbol.namespaceURI] = value;
                            }
                        }
                        // If the attribute is "xmlns", we should upgrade the element to an element created using the namespace URI.
                        else if (name === 'xmlns' && !this.nextElement[PropertySymbol.prefix]) {
                            // We only need to create a new instance if it is a known namespace URI.
                            if (NAMESPACE_URIS.includes(value)) {
                                this.nextElement = this.rootNode.createElementNS(value, this.nextElement[PropertySymbol.tagName]);
                                this.nextElement[PropertySymbol.attributes] = attributes;
                                attributes[PropertySymbol.ownerElement] = this.nextElement;
                                for (const item of attributes[PropertySymbol.items].values()) {
                                    item[PropertySymbol.ownerElement] = this.nextElement;
                                }
                            }
                            else {
                                this.nextElement[PropertySymbol.namespaceURI] = value;
                            }
                        }
                    }
                    else {
                        this.errorMessage = `Attribute ${name} redefined\n`;
                        this.errorIndex = this.startTagIndex;
                        this.readState = MarkupReadStateEnum.error;
                    }
                    this.startTagIndex += attributeMatch[0].length;
                }
                else if ((attributeMatch[1] && attributeMatch[3] !== '"') ||
                    (attributeMatch[4] && attributeMatch[6] !== "'")) {
                    // End attribute apostrophe is missing (e.g. "attr='value" or 'attr="value').
                    // We should continue to the next end of start tag match.
                    return;
                }
                lastIndex = attributeRegexp.lastIndex;
            }
            const attributeStringEnd = attributeString.substring(lastIndex).replace(SPACE_REGEXP, '');
            if (attributeStringEnd.length) {
                const match = attributeStringEnd.match(ATTRIBUTE_WITHOUT_VALUE_REGEXP);
                if (match) {
                    this.errorMessage = `Specification mandates value for attribute ${match[1]}\n`;
                    this.errorIndex = this.markupRegExp.lastIndex - 2;
                }
                else {
                    this.errorMessage = 'attributes construct error\n';
                    this.errorIndex = this.startTagIndex;
                }
                this.readState = MarkupReadStateEnum.error;
                this.removeOverflowingTextNodes();
                return;
            }
        }
        // Prefixed elements need to have a namespace URI defined by a prefixed "xmlns:" attribute either by a parent or in the current element.
        if (this.nextElement[PropertySymbol.prefix] &&
            !this.nextElement[PropertySymbol.namespaceURI]) {
            this.errorMessage = `Namespace prefix ${this.nextElement[PropertySymbol.prefix]} on name is not defined\n`;
            this.errorIndex = this.lastIndex;
            this.readState = MarkupReadStateEnum.error;
            return;
        }
        // Only one document element is allowed in the document.
        if (this.currentNode === this.rootNode &&
            this.rootNode[PropertySymbol.elementArray].length !== 0) {
            this.errorMessage = 'Extra content at the end of the document\n';
            this.errorIndex = this.lastIndex - this.nextElement[PropertySymbol.tagName].length - 1;
            this.readState = MarkupReadStateEnum.error;
            return;
        }
        this.currentNode[PropertySymbol.appendChild](this.nextElement, true);
        // Sets the new element as the current node.
        // XML nodes can be self closed using "/>"
        if (!isSelfClosed) {
            this.currentNode = this.nextElement;
            this.nodeStack.push(this.currentNode);
            this.tagNameStack.push(this.nextTagName);
            if (this.currentNode[PropertySymbol.namespaceURI] &&
                !this.currentNode[PropertySymbol.prefix]) {
                this.defaultNamespaceStack.push(this.currentNode[PropertySymbol.namespaceURI]);
            }
            else {
                this.defaultNamespaceStack.push(this.defaultNamespaceStack[this.defaultNamespaceStack.length - 1]);
            }
        }
        this.nextElement = null;
        this.nextTagName = null;
        this.readState = MarkupReadStateEnum.any;
        this.startTagIndex = this.markupRegExp.lastIndex;
    }
    /**
     * Parses end tag.
     *
     * @param tagName Tag name.
     * @returns True if the end tag was parsed, false otherwise.
     */
    parseEndTag(tagName) {
        if (this.tagNameStack[this.tagNameStack.length - 1] === tagName) {
            this.nodeStack.pop();
            this.tagNameStack.pop();
            this.namespacePrefixStack.pop();
            this.defaultNamespaceStack.pop();
            this.currentNode = this.nodeStack[this.nodeStack.length - 1] || this.rootNode;
            return true;
        }
        return false;
    }
    /**
     * Parses XML document error.
     *
     * @param readXML XML that has been read.
     * @param errorMessage Error message.
     */
    parseError(readXML, errorMessage) {
        let errorRoot = this.rootNode.documentElement;
        if (!errorRoot) {
            const documentElement = this.rootNode.createElementNS(NamespaceURI.html, 'html');
            const body = this.rootNode.createElementNS(NamespaceURI.html, 'body');
            documentElement.appendChild(body);
            errorRoot = body;
            this.rootNode[PropertySymbol.appendChild](documentElement, true);
        }
        const rows = readXML.split('\n');
        const column = rows[rows.length - 1].length + 1;
        const error = `error on line ${rows.length} at column ${column}: ${errorMessage || 'Unknown error'}`;
        const errorElement = this.rootNode.createElementNS(NamespaceURI.html, 'parsererror');
        errorElement.setAttribute('style', 'display: block; white-space: pre; border: 2px solid #c77; padding: 0 1em 0 1em; margin: 1em; background-color: #fdd; color: black');
        errorElement.innerHTML = `<h3>This page contains the following errors:</h3><div style="font-family:monospace;font-size:12px">${error}</div><h3>Below is a rendering of the page up to the first error.</h3>`;
        errorRoot.insertBefore(errorElement, errorRoot.firstChild);
    }
    /**
     * Removes overflowing text nodes in the current node.
     *
     * This needs to be done for some errors.
     */
    removeOverflowingTextNodes() {
        if (this.currentNode && this.currentNode !== this.rootNode) {
            while (this.currentNode.lastChild?.[PropertySymbol.nodeType] === Node.TEXT_NODE) {
                this.currentNode.removeChild(this.currentNode.lastChild);
            }
        }
    }
    /**
     * Returns document type.
     *
     * @param value Value.
     * @returns Document type.
     */
    getDocumentType(value) {
        if (!value.toUpperCase().startsWith('DOCTYPE')) {
            return null;
        }
        const docTypeSplit = value.split(SPACE_REGEXP);
        if (docTypeSplit.length <= 1) {
            return null;
        }
        const docTypeString = docTypeSplit.slice(1).join(' ');
        const attributes = [];
        const attributeRegExp = new RegExp(DOCUMENT_TYPE_ATTRIBUTE_REGEXP, 'gm');
        const isPublic = docTypeString.toUpperCase().includes('PUBLIC');
        let attributeMatch;
        while ((attributeMatch = attributeRegExp.exec(docTypeString))) {
            attributes.push(attributeMatch[1]);
        }
        const publicId = isPublic ? attributes[0] || '' : '';
        const systemId = isPublic ? attributes[1] || '' : attributes[0] || '';
        return {
            name: docTypeSplit[1].toLowerCase(),
            publicId,
            systemId
        };
    }
}
//# sourceMappingURL=XMLParser.js.map