import Node from '../nodes/node/Node'; import Element from '../nodes/element/Element'; import IDocument from '../nodes/document/IDocument'; import VoidElements from '../config/VoidElements'; import UnnestableElements from '../config/UnnestableElements'; import ChildLessElements from '../config/ChildLessElements'; import { decode } from 'he'; import NamespaceURI from '../config/NamespaceURI'; import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement'; import INode from '../nodes/node/INode'; import IElement from '../nodes/element/IElement'; import HTMLLinkElement from '../nodes/html-link-element/HTMLLinkElement'; const MARKUP_REGEXP = /<(\/?)([a-z][-.0-9_a-z]*)\s*([^>]*?)(\/?)>/gi; const COMMENT_REGEXP = /|<([!?])([^>]*)>/gi; const DOCUMENT_TYPE_ATTRIBUTE_REGEXP = /"([^"]+)"/gm; const ATTRIBUTE_REGEXP = /([^\s=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))/gms; /** * XML parser. */ export default class XMLParser { /** * Parses XML/HTML and returns a root element. * * @param document Document. * @param data HTML data. * @param [evaluateScripts = false] Set to "true" to enable script execution. * @returns Root element. */ public static parse(document: IDocument, data: string, evaluateScripts = false): IElement { const root = document.createElement('root'); const stack = [root]; const markupRegexp = new RegExp(MARKUP_REGEXP, 'gi'); let parent = root; let parentUnnestableTagName = null; let lastTextIndex = 0; let match: RegExpExecArray; while ((match = markupRegexp.exec(data))) { const tagName = match[2].toLowerCase(); const isStartTag = !match[1]; if (parent && match.index !== lastTextIndex) { const text = data.substring(lastTextIndex, match.index); this.appendTextAndCommentNodes(document, parent, text); } if (isStartTag) { const namespaceURI = tagName === 'svg' ? NamespaceURI.svg : parent.namespaceURI; const newElement = document.createElementNS(namespaceURI, tagName); // Scripts are not allowed to be executed when they are parsed using innerHTML, outerHTML, replaceWith() etc. // However, they are allowed to be executed when document.write() is used. // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement if (tagName === 'script') { (newElement)._evaluateScript = evaluateScripts; } // An assumption that the same rule should be applied for the HTMLLinkElement is made here. if (tagName === 'link') { (newElement)._evaluateCSS = evaluateScripts; } this.setAttributes(newElement, match[3]); if (!match[4] && !VoidElements.includes(tagName)) { // Some elements are not allowed to be nested (e.g. "" is not allowed.). // Therefore we will auto-close the tag. if (parentUnnestableTagName === tagName) { stack.pop(); parent = parent.parentNode || root; } parent = parent.appendChild(newElement); parentUnnestableTagName = this.getUnnestableTagName(parent); stack.push(parent); } else { parent.appendChild(newElement); } lastTextIndex = markupRegexp.lastIndex; // Tags which contain non-parsed content // For example: