UNPKG

vue-email

Version:

💌 Write email templates with Vue

1,468 lines (1,454 loc) • 539 kB
'use strict'; const vue = require('vue'); const ufo = require('ufo'); const serverRenderer = require('vue/server-renderer'); const tailwind = require('@vue-email/tailwind'); function isObject$1(item) { return !!item && typeof item === "object" && !Array.isArray(item); } function deepmerge$1(target, ...sources) { if (!sources.length) return target; const source = sources.shift(); if (isObject$1(target) && isObject$1(source)) { for (const key in source) { if (isObject$1(source[key])) { if (!target[key]) Object.assign(target, { [key]: {} }); deepmerge$1(target[key], source[key]); } else { Object.assign(target, { [key]: source[key] }); } } } return deepmerge$1(target, ...sources); } function cleanup(str) { if (!str || typeof str !== "string") return str; return str.replace(/ data-v-inspector="[^"]*"/g, "").replace(/<!--\[-->/g, "").replace(/<!--]-->/g, "").replace(/<template>/g, "").replace(/<template[^>]*>/g, "").replace(/<\/template>/g, "").replace(/<clean-component>/g, "").replace(/<clean-component[^>]*>/g, "").replace(/<\/clean-component>/g, ""); } /** Types of elements found in htmlparser2's DOM */ var ElementType; (function (ElementType) { /** Type for the root element of a document */ ElementType["Root"] = "root"; /** Type for Text */ ElementType["Text"] = "text"; /** Type for <? ... ?> */ ElementType["Directive"] = "directive"; /** Type for <!-- ... --> */ ElementType["Comment"] = "comment"; /** Type for <script> tags */ ElementType["Script"] = "script"; /** Type for <style> tags */ ElementType["Style"] = "style"; /** Type for Any tag */ ElementType["Tag"] = "tag"; /** Type for <![CDATA[ ... ]]> */ ElementType["CDATA"] = "cdata"; /** Type for <!doctype ...> */ ElementType["Doctype"] = "doctype"; })(ElementType || (ElementType = {})); /** * Tests whether an element is a tag or not. * * @param elem Element to test */ function isTag$1(elem) { return (elem.type === ElementType.Tag || elem.type === ElementType.Script || elem.type === ElementType.Style); } // Exports for backwards compatibility /** Type for the root element of a document */ const Root = ElementType.Root; /** Type for Text */ const Text$1 = ElementType.Text; /** Type for <? ... ?> */ const Directive = ElementType.Directive; /** Type for <!-- ... --> */ const Comment$1 = ElementType.Comment; /** Type for <script> tags */ const Script = ElementType.Script; /** Type for <style> tags */ const Style = ElementType.Style; /** Type for Any tag */ const Tag = ElementType.Tag; /** Type for <![CDATA[ ... ]]> */ const CDATA$1 = ElementType.CDATA; /** Type for <!doctype ...> */ const Doctype = ElementType.Doctype; /** * This object will be used as the prototype for Nodes when creating a * DOM-Level-1-compliant structure. */ class Node { constructor() { /** Parent of the node */ this.parent = null; /** Previous sibling */ this.prev = null; /** Next sibling */ this.next = null; /** The start index of the node. Requires `withStartIndices` on the handler to be `true. */ this.startIndex = null; /** The end index of the node. Requires `withEndIndices` on the handler to be `true. */ this.endIndex = null; } // Read-write aliases for properties /** * Same as {@link parent}. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias. */ get parentNode() { return this.parent; } set parentNode(parent) { this.parent = parent; } /** * Same as {@link prev}. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias. */ get previousSibling() { return this.prev; } set previousSibling(prev) { this.prev = prev; } /** * Same as {@link next}. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias. */ get nextSibling() { return this.next; } set nextSibling(next) { this.next = next; } /** * Clone this node, and optionally its children. * * @param recursive Clone child nodes as well. * @returns A clone of the node. */ cloneNode(recursive = false) { return cloneNode(this, recursive); } } /** * A node that contains some data. */ class DataNode extends Node { /** * @param data The content of the data node */ constructor(data) { super(); this.data = data; } /** * Same as {@link data}. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias. */ get nodeValue() { return this.data; } set nodeValue(data) { this.data = data; } } /** * Text within the document. */ class Text extends DataNode { constructor() { super(...arguments); this.type = ElementType.Text; } get nodeType() { return 3; } } /** * Comments within the document. */ class Comment extends DataNode { constructor() { super(...arguments); this.type = ElementType.Comment; } get nodeType() { return 8; } } /** * Processing instructions, including doc types. */ class ProcessingInstruction extends DataNode { constructor(name, data) { super(data); this.name = name; this.type = ElementType.Directive; } get nodeType() { return 1; } } /** * A `Node` that can have children. */ class NodeWithChildren extends Node { /** * @param children Children of the node. Only certain node types can have children. */ constructor(children) { super(); this.children = children; } // Aliases /** First child of the node. */ get firstChild() { var _a; return (_a = this.children[0]) !== null && _a !== void 0 ? _a : null; } /** Last child of the node. */ get lastChild() { return this.children.length > 0 ? this.children[this.children.length - 1] : null; } /** * Same as {@link children}. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias. */ get childNodes() { return this.children; } set childNodes(children) { this.children = children; } } class CDATA extends NodeWithChildren { constructor() { super(...arguments); this.type = ElementType.CDATA; } get nodeType() { return 4; } } /** * The root node of the document. */ class Document extends NodeWithChildren { constructor() { super(...arguments); this.type = ElementType.Root; } get nodeType() { return 9; } } /** * An element within the DOM. */ class Element extends NodeWithChildren { /** * @param name Name of the tag, eg. `div`, `span`. * @param attribs Object mapping attribute names to attribute values. * @param children Children of the node. */ constructor(name, attribs, children = [], type = name === "script" ? ElementType.Script : name === "style" ? ElementType.Style : ElementType.Tag) { super(children); this.name = name; this.attribs = attribs; this.type = type; } get nodeType() { return 1; } // DOM Level 1 aliases /** * Same as {@link name}. * [DOM spec](https://dom.spec.whatwg.org)-compatible alias. */ get tagName() { return this.name; } set tagName(name) { this.name = name; } get attributes() { return Object.keys(this.attribs).map((name) => { var _a, _b; return ({ name, value: this.attribs[name], namespace: (_a = this["x-attribsNamespace"]) === null || _a === void 0 ? void 0 : _a[name], prefix: (_b = this["x-attribsPrefix"]) === null || _b === void 0 ? void 0 : _b[name], }); }); } } /** * @param node Node to check. * @returns `true` if the node is a `Element`, `false` otherwise. */ function isTag(node) { return isTag$1(node); } /** * @param node Node to check. * @returns `true` if the node has the type `CDATA`, `false` otherwise. */ function isCDATA(node) { return node.type === ElementType.CDATA; } /** * @param node Node to check. * @returns `true` if the node has the type `Text`, `false` otherwise. */ function isText(node) { return node.type === ElementType.Text; } /** * @param node Node to check. * @returns `true` if the node has the type `Comment`, `false` otherwise. */ function isComment(node) { return node.type === ElementType.Comment; } /** * @param node Node to check. * @returns `true` if the node has the type `ProcessingInstruction`, `false` otherwise. */ function isDirective(node) { return node.type === ElementType.Directive; } /** * @param node Node to check. * @returns `true` if the node has the type `ProcessingInstruction`, `false` otherwise. */ function isDocument(node) { return node.type === ElementType.Root; } /** * Clone a node, and optionally its children. * * @param recursive Clone child nodes as well. * @returns A clone of the node. */ function cloneNode(node, recursive = false) { let result; if (isText(node)) { result = new Text(node.data); } else if (isComment(node)) { result = new Comment(node.data); } else if (isTag(node)) { const children = recursive ? cloneChildren(node.children) : []; const clone = new Element(node.name, { ...node.attribs }, children); children.forEach((child) => (child.parent = clone)); if (node.namespace != null) { clone.namespace = node.namespace; } if (node["x-attribsNamespace"]) { clone["x-attribsNamespace"] = { ...node["x-attribsNamespace"] }; } if (node["x-attribsPrefix"]) { clone["x-attribsPrefix"] = { ...node["x-attribsPrefix"] }; } result = clone; } else if (isCDATA(node)) { const children = recursive ? cloneChildren(node.children) : []; const clone = new CDATA(children); children.forEach((child) => (child.parent = clone)); result = clone; } else if (isDocument(node)) { const children = recursive ? cloneChildren(node.children) : []; const clone = new Document(children); children.forEach((child) => (child.parent = clone)); if (node["x-mode"]) { clone["x-mode"] = node["x-mode"]; } result = clone; } else if (isDirective(node)) { const instruction = new ProcessingInstruction(node.name, node.data); if (node["x-name"] != null) { instruction["x-name"] = node["x-name"]; instruction["x-publicId"] = node["x-publicId"]; instruction["x-systemId"] = node["x-systemId"]; } result = instruction; } else { throw new Error(`Not implemented yet: ${node.type}`); } result.startIndex = node.startIndex; result.endIndex = node.endIndex; if (node.sourceCodeLocation != null) { result.sourceCodeLocation = node.sourceCodeLocation; } return result; } function cloneChildren(childs) { const children = childs.map((child) => cloneNode(child, true)); for (let i = 1; i < children.length; i++) { children[i].prev = children[i - 1]; children[i - 1].next = children[i]; } return children; } // Default options const defaultOpts = { withStartIndices: false, withEndIndices: false, xmlMode: false, }; class DomHandler { /** * @param callback Called once parsing has completed. * @param options Settings for the handler. * @param elementCB Callback whenever a tag is closed. */ constructor(callback, options, elementCB) { /** The elements of the DOM */ this.dom = []; /** The root element for the DOM */ this.root = new Document(this.dom); /** Indicated whether parsing has been completed. */ this.done = false; /** Stack of open tags. */ this.tagStack = [this.root]; /** A data node that is still being written to. */ this.lastNode = null; /** Reference to the parser instance. Used for location information. */ this.parser = null; // Make it possible to skip arguments, for backwards-compatibility if (typeof options === "function") { elementCB = options; options = defaultOpts; } if (typeof callback === "object") { options = callback; callback = undefined; } this.callback = callback !== null && callback !== void 0 ? callback : null; this.options = options !== null && options !== void 0 ? options : defaultOpts; this.elementCB = elementCB !== null && elementCB !== void 0 ? elementCB : null; } onparserinit(parser) { this.parser = parser; } // Resets the handler back to starting state onreset() { this.dom = []; this.root = new Document(this.dom); this.done = false; this.tagStack = [this.root]; this.lastNode = null; this.parser = null; } // Signals the handler that parsing is done onend() { if (this.done) return; this.done = true; this.parser = null; this.handleCallback(null); } onerror(error) { this.handleCallback(error); } onclosetag() { this.lastNode = null; const elem = this.tagStack.pop(); if (this.options.withEndIndices) { elem.endIndex = this.parser.endIndex; } if (this.elementCB) this.elementCB(elem); } onopentag(name, attribs) { const type = this.options.xmlMode ? ElementType.Tag : undefined; const element = new Element(name, attribs, undefined, type); this.addNode(element); this.tagStack.push(element); } ontext(data) { const { lastNode } = this; if (lastNode && lastNode.type === ElementType.Text) { lastNode.data += data; if (this.options.withEndIndices) { lastNode.endIndex = this.parser.endIndex; } } else { const node = new Text(data); this.addNode(node); this.lastNode = node; } } oncomment(data) { if (this.lastNode && this.lastNode.type === ElementType.Comment) { this.lastNode.data += data; return; } const node = new Comment(data); this.addNode(node); this.lastNode = node; } oncommentend() { this.lastNode = null; } oncdatastart() { const text = new Text(""); const node = new CDATA([text]); this.addNode(node); text.parent = node; this.lastNode = text; } oncdataend() { this.lastNode = null; } onprocessinginstruction(name, data) { const node = new ProcessingInstruction(name, data); this.addNode(node); } handleCallback(error) { if (typeof this.callback === "function") { this.callback(error, this.dom); } else if (error) { throw error; } } addNode(node) { const parent = this.tagStack[this.tagStack.length - 1]; const previousSibling = parent.children[parent.children.length - 1]; if (this.options.withStartIndices) { node.startIndex = this.parser.startIndex; } if (this.options.withEndIndices) { node.endIndex = this.parser.endIndex; } parent.children.push(node); if (previousSibling) { node.prev = previousSibling; previousSibling.next = node; } node.parent = parent; this.lastNode = null; } } const e=/\n/g;function n(n){const o=[...n.matchAll(e)].map((e=>e.index||0));o.unshift(-1);const s=t(o,0,o.length);return e=>r(s,e)}function t(e,n,r){if(r-n==1)return {offset:e[n],index:n+1};const o=Math.ceil((n+r)/2),s=t(e,n,o),l=t(e,o,r);return {offset:s.offset,low:s,high:l}}function r(e,n){return function(e){return Object.prototype.hasOwnProperty.call(e,"index")}(e)?{line:e.index,column:n-e.offset}:r(e.high.offset<n?e.high:e.low,n)}function o(e,t="",r={}){const o="string"!=typeof t?t:r,l="string"==typeof t?t:"",c=e.map(s),f=!!o.lineNumbers;return function(e,t=0){const r=f?n(e):()=>({line:0,column:0});let o=t;const s=[];e:for(;o<e.length;){let n=!1;for(const t of c){t.regex.lastIndex=o;const c=t.regex.exec(e);if(c&&c[0].length>0){if(!t.discard){const e=r(o),n="string"==typeof t.replace?c[0].replace(new RegExp(t.regex.source,t.regex.flags),t.replace):c[0];s.push({state:l,name:t.name,text:n,offset:o,len:c[0].length,line:e.line,column:e.column});}if(o=t.regex.lastIndex,n=!0,t.push){const n=t.push(e,o);s.push(...n.tokens),o=n.offset;}if(t.pop)break e;break}}if(!n)break}return {tokens:s,offset:o,complete:e.length<=o}}}function s(e,n){return {...e,regex:l(e,n)}}function l(e,n){if(0===e.name.length)throw new Error(`Rule #${n} has empty name, which is not allowed.`);if(function(e){return Object.prototype.hasOwnProperty.call(e,"regex")}(e))return function(e){if(e.global)throw new Error(`Regular expression /${e.source}/${e.flags} contains the global flag, which is not allowed.`);return e.sticky?e:new RegExp(e.source,e.flags+"y")}(e.regex);if(function(e){return Object.prototype.hasOwnProperty.call(e,"str")}(e)){if(0===e.str.length)throw new Error(`Rule #${n} ("${e.name}") has empty "str" property, which is not allowed.`);return new RegExp(c(e.str),"y")}return new RegExp(c(e.name),"y")}function c(e){return e.replace(/[-[\]{}()*+!<=:?./\\^$|#\s,]/g,"\\$&")} function token$1( onToken, onEnd) { return (data, i) => { let position = i; let value = undefined; if (i < data.tokens.length) { value = onToken(data.tokens[i], data, i); if (value !== undefined) { position++; } } else { onEnd?.(data, i); } return (value === undefined) ? { matched: false } : { matched: true, position: position, value: value }; }; } function mapInner(r, f) { return (r.matched) ? ({ matched: true, position: r.position, value: f(r.value, r.position) }) : r; } function mapOuter(r, f) { return (r.matched) ? f(r) : r; } function map(p, mapper) { return (data, i) => mapInner(p(data, i), (v, j) => mapper(v, data, i, j)); } function option(p, def) { return (data, i) => { const r = p(data, i); return (r.matched) ? r : { matched: true, position: i, value: def }; }; } function choice(...ps) { return (data, i) => { for (const p of ps) { const result = p(data, i); if (result.matched) { return result; } } return { matched: false }; }; } function otherwise(pa, pb) { return (data, i) => { const r1 = pa(data, i); return (r1.matched) ? r1 : pb(data, i); }; } function takeWhile(p, test) { return (data, i) => { const values = []; let success = true; do { const r = p(data, i); if (r.matched && test(r.value, values.length + 1, data, i, r.position)) { values.push(r.value); i = r.position; } else { success = false; } } while (success); return { matched: true, position: i, value: values }; }; } function many(p) { return takeWhile(p, () => true); } function many1(p) { return ab(p, many(p), (head, tail) => [head, ...tail]); } function ab(pa, pb, join) { return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) => join(ma.value, vb, data, i, j))); } function left(pa, pb) { return ab(pa, pb, (va) => va); } function right(pa, pb) { return ab(pa, pb, (va, vb) => vb); } function abc(pa, pb, pc, join) { return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j) => join(ma.value, mb.value, vc, data, i, j)))); } function middle(pa, pb, pc) { return abc(pa, pb, pc, (ra, rb) => rb); } function all(...ps) { return (data, i) => { const result = []; let position = i; for (const p of ps) { const r1 = p(data, position); if (r1.matched) { result.push(r1.value); position = r1.position; } else { return { matched: false }; } } return { matched: true, position: position, value: result }; }; } function flatten(...ps) { return flatten1(all(...ps)); } function flatten1(p) { return map(p, (vs) => vs.flatMap((v) => v)); } function chainReduce(acc, f) { return (data, i) => { let loop = true; let acc1 = acc; let pos = i; do { const r = f(acc1, data, pos)(data, pos); if (r.matched) { acc1 = r.value; pos = r.position; } else { loop = false; } } while (loop); return { matched: true, position: pos, value: acc1 }; }; } function reduceLeft(acc, p, reducer) { return chainReduce(acc, (acc) => map(p, (v, data, i, j) => reducer(acc, v, data, i, j))); } function leftAssoc2(pLeft, pOper, pRight) { return chain(pLeft, (v0) => reduceLeft(v0, ab(pOper, pRight, (f, y) => [f, y]), (acc, [f, y]) => f(acc, y))); } function chain(p, f) { return (data, i) => mapOuter(p(data, i), (m1) => f(m1.value, data, i, m1.position)(data, m1.position)); } const ws = `(?:[ \\t\\r\\n\\f]*)`; const nl = `(?:\\n|\\r\\n|\\r|\\f)`; const nonascii = `[^\\x00-\\x7F]`; const unicode = `(?:\\\\[0-9a-f]{1,6}(?:\\r\\n|[ \\n\\r\\t\\f])?)`; const escape = `(?:\\\\[^\\n\\r\\f0-9a-f])`; const nmstart = `(?:[_a-z]|${nonascii}|${unicode}|${escape})`; const nmchar = `(?:[_a-z0-9-]|${nonascii}|${unicode}|${escape})`; const name = `(?:${nmchar}+)`; const ident = `(?:[-]?${nmstart}${nmchar}*)`; const string1 = `'([^\\n\\r\\f\\\\']|\\\\${nl}|${nonascii}|${unicode}|${escape})*'`; const string2 = `"([^\\n\\r\\f\\\\"]|\\\\${nl}|${nonascii}|${unicode}|${escape})*"`; const lexSelector = o([ { name: 'ws', regex: new RegExp(ws) }, { name: 'hash', regex: new RegExp(`#${name}`, 'i') }, { name: 'ident', regex: new RegExp(ident, 'i') }, { name: 'str1', regex: new RegExp(string1, 'i') }, { name: 'str2', regex: new RegExp(string2, 'i') }, { name: '*' }, { name: '.' }, { name: ',' }, { name: '[' }, { name: ']' }, { name: '=' }, { name: '>' }, { name: '|' }, { name: '+' }, { name: '~' }, { name: '^' }, { name: '$' }, ]); const lexEscapedString = o([ { name: 'unicode', regex: new RegExp(unicode, 'i') }, { name: 'escape', regex: new RegExp(escape, 'i') }, { name: 'any', regex: new RegExp('[\\s\\S]', 'i') } ]); function sumSpec([a0, a1, a2], [b0, b1, b2]) { return [a0 + b0, a1 + b1, a2 + b2]; } function sumAllSpec(ss) { return ss.reduce(sumSpec, [0, 0, 0]); } const unicodeEscapedSequence_ = token$1((t) => t.name === 'unicode' ? String.fromCodePoint(parseInt(t.text.slice(1), 16)) : undefined); const escapedSequence_ = token$1((t) => t.name === 'escape' ? t.text.slice(1) : undefined); const anyChar_ = token$1((t) => t.name === 'any' ? t.text : undefined); const escapedString_ = map(many(choice(unicodeEscapedSequence_, escapedSequence_, anyChar_)), (cs) => cs.join('')); function unescape(escapedString) { const lexerResult = lexEscapedString(escapedString); const result = escapedString_({ tokens: lexerResult.tokens, options: undefined }, 0); return result.value; } function literal(name) { return token$1((t) => t.name === name ? true : undefined); } const whitespace_ = token$1((t) => t.name === 'ws' ? null : undefined); const optionalWhitespace_ = option(whitespace_, null); function optionallySpaced(parser) { return middle(optionalWhitespace_, parser, optionalWhitespace_); } const identifier_ = token$1((t) => t.name === 'ident' ? unescape(t.text) : undefined); const hashId_ = token$1((t) => t.name === 'hash' ? unescape(t.text.slice(1)) : undefined); const string_ = token$1((t) => t.name.startsWith('str') ? unescape(t.text.slice(1, -1)) : undefined); const namespace_ = left(option(identifier_, ''), literal('|')); const qualifiedName_ = otherwise(ab(namespace_, identifier_, (ns, name) => ({ name: name, namespace: ns })), map(identifier_, (name) => ({ name: name, namespace: null }))); const uniSelector_ = otherwise(ab(namespace_, literal('*'), (ns) => ({ type: 'universal', namespace: ns, specificity: [0, 0, 0] })), map(literal('*'), () => ({ type: 'universal', namespace: null, specificity: [0, 0, 0] }))); const tagSelector_ = map(qualifiedName_, ({ name, namespace }) => ({ type: 'tag', name: name, namespace: namespace, specificity: [0, 0, 1] })); const classSelector_ = ab(literal('.'), identifier_, (fullstop, name) => ({ type: 'class', name: name, specificity: [0, 1, 0] })); const idSelector_ = map(hashId_, (name) => ({ type: 'id', name: name, specificity: [1, 0, 0] })); const attrModifier_ = token$1((t) => { if (t.name === 'ident') { if (t.text === 'i' || t.text === 'I') { return 'i'; } if (t.text === 's' || t.text === 'S') { return 's'; } } return undefined; }); const attrValue_ = otherwise(ab(string_, option(right(optionalWhitespace_, attrModifier_), null), (v, mod) => ({ value: v, modifier: mod })), ab(identifier_, option(right(whitespace_, attrModifier_), null), (v, mod) => ({ value: v, modifier: mod }))); const attrMatcher_ = choice(map(literal('='), () => '='), ab(literal('~'), literal('='), () => '~='), ab(literal('|'), literal('='), () => '|='), ab(literal('^'), literal('='), () => '^='), ab(literal('$'), literal('='), () => '$='), ab(literal('*'), literal('='), () => '*=')); const attrPresenceSelector_ = abc(literal('['), optionallySpaced(qualifiedName_), literal(']'), (lbr, { name, namespace }) => ({ type: 'attrPresence', name: name, namespace: namespace, specificity: [0, 1, 0] })); const attrValueSelector_ = middle(literal('['), abc(optionallySpaced(qualifiedName_), attrMatcher_, optionallySpaced(attrValue_), ({ name, namespace }, matcher, { value, modifier }) => ({ type: 'attrValue', name: name, namespace: namespace, matcher: matcher, value: value, modifier: modifier, specificity: [0, 1, 0] })), literal(']')); const attrSelector_ = otherwise(attrPresenceSelector_, attrValueSelector_); const typeSelector_ = otherwise(uniSelector_, tagSelector_); const subclassSelector_ = choice(idSelector_, classSelector_, attrSelector_); const compoundSelector_ = map(otherwise(flatten(typeSelector_, many(subclassSelector_)), many1(subclassSelector_)), (ss) => { return { type: 'compound', list: ss, specificity: sumAllSpec(ss.map(s => s.specificity)) }; }); const combinator_ = choice(map(literal('>'), () => '>'), map(literal('+'), () => '+'), map(literal('~'), () => '~'), ab(literal('|'), literal('|'), () => '||')); const combinatorSeparator_ = otherwise(optionallySpaced(combinator_), map(whitespace_, () => ' ')); const complexSelector_ = leftAssoc2(compoundSelector_, map(combinatorSeparator_, (c) => (left, right) => ({ type: 'compound', list: [...right.list, { type: 'combinator', combinator: c, left: left, specificity: left.specificity }], specificity: sumSpec(left.specificity, right.specificity) })), compoundSelector_); function parse_(parser, str) { if (!(typeof str === 'string' || str instanceof String)) { throw new Error('Expected a selector string. Actual input is not a string!'); } const lexerResult = lexSelector(str); if (!lexerResult.complete) { throw new Error(`The input "${str}" was only partially tokenized, stopped at offset ${lexerResult.offset}!\n` + prettyPrintPosition(str, lexerResult.offset)); } const result = optionallySpaced(parser)({ tokens: lexerResult.tokens, options: undefined }, 0); if (!result.matched) { throw new Error(`No match for "${str}" input!`); } if (result.position < lexerResult.tokens.length) { const token = lexerResult.tokens[result.position]; throw new Error(`The input "${str}" was only partially parsed, stopped at offset ${token.offset}!\n` + prettyPrintPosition(str, token.offset, token.len)); } return result.value; } function prettyPrintPosition(str, offset, len = 1) { return `${str.replace(/(\t)|(\r)|(\n)/g, (m, t, r) => t ? '\u2409' : r ? '\u240d' : '\u240a')}\n${''.padEnd(offset)}${'^'.repeat(len)}`; } function parse1(str) { return parse_(complexSelector_, str); } function serialize(selector) { if (!selector.type) { throw new Error('This is not an AST node.'); } switch (selector.type) { case 'universal': return _serNs(selector.namespace) + '*'; case 'tag': return _serNs(selector.namespace) + _serIdent(selector.name); case 'class': return '.' + _serIdent(selector.name); case 'id': return '#' + _serIdent(selector.name); case 'attrPresence': return `[${_serNs(selector.namespace)}${_serIdent(selector.name)}]`; case 'attrValue': return `[${_serNs(selector.namespace)}${_serIdent(selector.name)}${selector.matcher}"${_serStr(selector.value)}"${(selector.modifier ? selector.modifier : '')}]`; case 'combinator': return serialize(selector.left) + selector.combinator; case 'compound': return selector.list.reduce((acc, node) => { if (node.type === 'combinator') { return serialize(node) + acc; } else { return acc + serialize(node); } }, ''); case 'list': return selector.list.map(serialize).join(','); } } function _serNs(ns) { return (ns || ns === '') ? _serIdent(ns) + '|' : ''; } function _codePoint(char) { return `\\${char.codePointAt(0).toString(16)} `; } function _serIdent(str) { return str.replace( /(^[0-9])|(^-[0-9])|(^-$)|([-0-9a-zA-Z_]|[^\x00-\x7F])|(\x00)|([\x01-\x1f]|\x7f)|([\s\S])/g, (m, d1, d2, hy, safe, nl, ctrl, other) => d1 ? _codePoint(d1) : d2 ? '-' + _codePoint(d2.slice(1)) : hy ? '\\-' : safe ? safe : nl ? '\ufffd' : ctrl ? _codePoint(ctrl) : '\\' + other); } function _serStr(str) { return str.replace( /(")|(\\)|(\x00)|([\x01-\x1f]|\x7f)/g, (m, dq, bs, nl, ctrl) => dq ? '\\"' : bs ? '\\\\' : nl ? '\ufffd' : _codePoint(ctrl)); } function normalize(selector) { if (!selector.type) { throw new Error('This is not an AST node.'); } switch (selector.type) { case 'compound': { selector.list.forEach(normalize); selector.list.sort((a, b) => _compareArrays(_getSelectorPriority(a), _getSelectorPriority(b))); break; } case 'combinator': { normalize(selector.left); break; } case 'list': { selector.list.forEach(normalize); selector.list.sort((a, b) => (serialize(a) < serialize(b)) ? -1 : 1); break; } } return selector; } function _getSelectorPriority(selector) { switch (selector.type) { case 'universal': return [1]; case 'tag': return [1]; case 'id': return [2]; case 'class': return [3, selector.name]; case 'attrPresence': return [4, serialize(selector)]; case 'attrValue': return [5, serialize(selector)]; case 'combinator': return [15, serialize(selector)]; } } function compareSpecificity(a, b) { return _compareArrays(a, b); } function _compareArrays(a, b) { if (!Array.isArray(a) || !Array.isArray(b)) { throw new Error('Arguments must be arrays.'); } const shorter = (a.length < b.length) ? a.length : b.length; for (let i = 0; i < shorter; i++) { if (a[i] === b[i]) { continue; } return (a[i] < b[i]) ? -1 : 1; } return a.length - b.length; } class DecisionTree { constructor(input) { this.branches = weave(toAstTerminalPairs(input)); } build(builder) { return builder(this.branches); } } function toAstTerminalPairs(array) { const len = array.length; const results = new Array(len); for (let i = 0; i < len; i++) { const [selectorString, val] = array[i]; const ast = preprocess(parse1(selectorString)); results[i] = { ast: ast, terminal: { type: 'terminal', valueContainer: { index: i, value: val, specificity: ast.specificity } } }; } return results; } function preprocess(ast) { reduceSelectorVariants(ast); normalize(ast); return ast; } function reduceSelectorVariants(ast) { const newList = []; ast.list.forEach(sel => { switch (sel.type) { case 'class': newList.push({ matcher: '~=', modifier: null, name: 'class', namespace: null, specificity: sel.specificity, type: 'attrValue', value: sel.name, }); break; case 'id': newList.push({ matcher: '=', modifier: null, name: 'id', namespace: null, specificity: sel.specificity, type: 'attrValue', value: sel.name, }); break; case 'combinator': reduceSelectorVariants(sel.left); newList.push(sel); break; case 'universal': break; default: newList.push(sel); break; } }); ast.list = newList; } function weave(items) { const branches = []; while (items.length) { const topKind = findTopKey(items, (sel) => true, getSelectorKind); const { matches, nonmatches, empty } = breakByKind(items, topKind); items = nonmatches; if (matches.length) { branches.push(branchOfKind(topKind, matches)); } if (empty.length) { branches.push(...terminate(empty)); } } return branches; } function terminate(items) { const results = []; for (const item of items) { const terminal = item.terminal; if (terminal.type === 'terminal') { results.push(terminal); } else { const { matches, rest } = partition(terminal.cont, (node) => node.type === 'terminal'); matches.forEach((node) => results.push(node)); if (rest.length) { terminal.cont = rest; results.push(terminal); } } } return results; } function breakByKind(items, selectedKind) { const matches = []; const nonmatches = []; const empty = []; for (const item of items) { const simpsels = item.ast.list; if (simpsels.length) { const isMatch = simpsels.some(node => getSelectorKind(node) === selectedKind); (isMatch ? matches : nonmatches).push(item); } else { empty.push(item); } } return { matches, nonmatches, empty }; } function getSelectorKind(sel) { switch (sel.type) { case 'attrPresence': return `attrPresence ${sel.name}`; case 'attrValue': return `attrValue ${sel.name}`; case 'combinator': return `combinator ${sel.combinator}`; default: return sel.type; } } function branchOfKind(kind, items) { if (kind === 'tag') { return tagNameBranch(items); } if (kind.startsWith('attrValue ')) { return attrValueBranch(kind.substring(10), items); } if (kind.startsWith('attrPresence ')) { return attrPresenceBranch(kind.substring(13), items); } if (kind === 'combinator >') { return combinatorBranch('>', items); } if (kind === 'combinator +') { return combinatorBranch('+', items); } throw new Error(`Unsupported selector kind: ${kind}`); } function tagNameBranch(items) { const groups = spliceAndGroup(items, (x) => x.type === 'tag', (x) => x.name); const variants = Object.entries(groups).map(([name, group]) => ({ type: 'variant', value: name, cont: weave(group.items) })); return { type: 'tagName', variants: variants }; } function attrPresenceBranch(name, items) { for (const item of items) { spliceSimpleSelector(item, (x) => (x.type === 'attrPresence') && (x.name === name)); } return { type: 'attrPresence', name: name, cont: weave(items) }; } function attrValueBranch(name, items) { const groups = spliceAndGroup(items, (x) => (x.type === 'attrValue') && (x.name === name), (x) => `${x.matcher} ${x.modifier || ''} ${x.value}`); const matchers = []; for (const group of Object.values(groups)) { const sel = group.oneSimpleSelector; const predicate = getAttrPredicate(sel); const continuation = weave(group.items); matchers.push({ type: 'matcher', matcher: sel.matcher, modifier: sel.modifier, value: sel.value, predicate: predicate, cont: continuation }); } return { type: 'attrValue', name: name, matchers: matchers }; } function getAttrPredicate(sel) { if (sel.modifier === 'i') { const expected = sel.value.toLowerCase(); switch (sel.matcher) { case '=': return (actual) => expected === actual.toLowerCase(); case '~=': return (actual) => actual.toLowerCase().split(/[ \t]+/).includes(expected); case '^=': return (actual) => actual.toLowerCase().startsWith(expected); case '$=': return (actual) => actual.toLowerCase().endsWith(expected); case '*=': return (actual) => actual.toLowerCase().includes(expected); case '|=': return (actual) => { const lower = actual.toLowerCase(); return (expected === lower) || (lower.startsWith(expected) && lower[expected.length] === '-'); }; } } else { const expected = sel.value; switch (sel.matcher) { case '=': return (actual) => expected === actual; case '~=': return (actual) => actual.split(/[ \t]+/).includes(expected); case '^=': return (actual) => actual.startsWith(expected); case '$=': return (actual) => actual.endsWith(expected); case '*=': return (actual) => actual.includes(expected); case '|=': return (actual) => (expected === actual) || (actual.startsWith(expected) && actual[expected.length] === '-'); } } } function combinatorBranch(combinator, items) { const groups = spliceAndGroup(items, (x) => (x.type === 'combinator') && (x.combinator === combinator), (x) => serialize(x.left)); const leftItems = []; for (const group of Object.values(groups)) { const rightCont = weave(group.items); const leftAst = group.oneSimpleSelector.left; leftItems.push({ ast: leftAst, terminal: { type: 'popElement', cont: rightCont } }); } return { type: 'pushElement', combinator: combinator, cont: weave(leftItems) }; } function spliceAndGroup(items, predicate, keyCallback) { const groups = {}; while (items.length) { const bestKey = findTopKey(items, predicate, keyCallback); const bestKeyPredicate = (sel) => predicate(sel) && keyCallback(sel) === bestKey; const hasBestKeyPredicate = (item) => item.ast.list.some(bestKeyPredicate); const { matches, rest } = partition1(items, hasBestKeyPredicate); let oneSimpleSelector = null; for (const item of matches) { const splicedNode = spliceSimpleSelector(item, bestKeyPredicate); if (!oneSimpleSelector) { oneSimpleSelector = splicedNode; } } if (oneSimpleSelector == null) { throw new Error('No simple selector is found.'); } groups[bestKey] = { oneSimpleSelector: oneSimpleSelector, items: matches }; items = rest; } return groups; } function spliceSimpleSelector(item, predicate) { const simpsels = item.ast.list; const matches = new Array(simpsels.length); let firstIndex = -1; for (let i = simpsels.length; i-- > 0;) { if (predicate(simpsels[i])) { matches[i] = true; firstIndex = i; } } if (firstIndex == -1) { throw new Error(`Couldn't find the required simple selector.`); } const result = simpsels[firstIndex]; item.ast.list = simpsels.filter((sel, i) => !matches[i]); return result; } function findTopKey(items, predicate, keyCallback) { const candidates = {}; for (const item of items) { const candidates1 = {}; for (const node of item.ast.list.filter(predicate)) { candidates1[keyCallback(node)] = true; } for (const key of Object.keys(candidates1)) { if (candidates[key]) { candidates[key]++; } else { candidates[key] = 1; } } } let topKind = ''; let topCounter = 0; for (const entry of Object.entries(candidates)) { if (entry[1] > topCounter) { topKind = entry[0]; topCounter = entry[1]; } } return topKind; } function partition(src, predicate) { const matches = []; const rest = []; for (const x of src) { if (predicate(x)) { matches.push(x); } else { rest.push(x); } } return { matches, rest }; } function partition1(src, predicate) { const matches = []; const rest = []; for (const x of src) { if (predicate(x)) { matches.push(x); } else { rest.push(x); } } return { matches, rest }; } class Picker { constructor(f) { this.f = f; } pickAll(el) { return this.f(el); } pick1(el, preferFirst = false) { const results = this.f(el); const len = results.length; if (len === 0) { return null; } if (len === 1) { return results[0].value; } const comparator = (preferFirst) ? comparatorPreferFirst : comparatorPreferLast; let result = results[0]; for (let i = 1; i < len; i++) { const next = results[i]; if (comparator(result, next)) { result = next; } } return result.value; } } function comparatorPreferFirst(acc, next) { const diff = compareSpecificity(next.specificity, acc.specificity); return diff > 0 || (diff === 0 && next.index < acc.index); } function comparatorPreferLast(acc, next) { const diff = compareSpecificity(next.specificity, acc.specificity); return diff > 0 || (diff === 0 && next.index > acc.index); } function hp2Builder(nodes) { return new Picker(handleArray(nodes)); } function handleArray(nodes) { const matchers = nodes.map(handleNode); return (el, ...tail) => matchers.flatMap(m => m(el, ...tail)); } function handleNode(node) { switch (node.type) { case 'terminal': { const result = [node.valueContainer]; return (el, ...tail) => result; } case 'tagName': return handleTagName(node); case 'attrValue': return handleAttrValueName(node); case 'attrPresence': return handleAttrPresenceName(node); case 'pushElement': return handlePushElementNode(node); case 'popElement': return handlePopElementNode(node); } } function handleTagName(node) { const variants = {}; for (const variant of node.variants) { variants[variant.value] = handleArray(variant.cont); } return (el, ...tail) => { const continuation = variants[el.name]; return (continuation) ? continuation(el, ...tail) : []; }; } function handleAttrPresenceName(node) { const attrName = node.name; const continuation = handleArray(node.cont); return (el, ...tail) => (Object.prototype.hasOwnProperty.call(el.attribs, attrName)) ? continuation(el, ...tail) : []; } function handleAttrValueName(node) { const callbacks = []; for (const matcher of node.matchers) { const predicate = matcher.predicate; const continuation = handleArray(matcher.cont); callbacks.push((attr, el, ...tail) => (predicate(attr) ? continuation(el, ...tail) : [])); } const attrName = node.name; return (el, ...tail) => { const attr = el.attribs[attrName]; return (attr || attr === '') ? callbacks.flatMap(cb => cb(attr, el, ...tail)) : []; }; } function handlePushElementNode(node) { const continuation = handleArray(node.cont); const leftElementGetter = (node.combinator === '+') ? getPrecedingElement : getParentElement; return (el, ...tail) => { const next = leftElementGetter(el); if (next === null) { return []; } return continuation(next, el, ...tail); }; } const getPrecedingElement = (el) => { const prev = el.prev; if (prev === null) { return null; } return (isTag(prev)) ? prev : getPrecedingElement(prev); }; const getParentElement = (el) => { const parent = el.parent; return (parent && isTag(parent)) ? parent : null; }; function handlePopElementNode(node) { const continuation = handleArray(node.cont); return (el, next, ...tail) => continuation(next, ...tail); } // Generated using scripts/write-decode-map.ts const htmlDecodeTree = new Uint16Array( // prettier-ignore "\u1d41<\xd5\u0131\u028a\u049d\u057b\u05d0\u0675\u06de\u07a2\u07d6\u080f\u0a4a\u0a91\u0da1\u0e6d\u0f09\u0f26\u10ca\u1228\u12e1\u1415\u149d\u14c3\u14df\u1525\0\0\0\0\0\0\u156b\u16cd\u198d\u1c12\u1ddd\u1f7e\u2060\u21b0\u228d\u23c0\u23fb\u2442\u2824\u2912\u2d08\u2e48\u2fce\u3016\u32ba\u3639\u37ac\u38fe\u3a28\u3a71\u3ae0\u3b2e\u0800EMabcfglmnoprstu\\bfms\x7f\x84\x8b\x90\x95\x98\xa6\xb3\xb9\xc8\xcflig\u803b\xc6\u40c6P\u803b&\u4026cute\u803b\xc1\u40c1reve;\u4102\u0100iyx}rc\u803b\xc2\u40c2;\u4410r;\uc000\ud835\udd04rave\u803b\xc0\u40c0pha;\u4391acr;\u4100d;\u6a53\u0100gp\x9d\xa1on;\u4104f;\uc000\ud835\udd38plyFunction;\u6061ing\u803b\xc5\u40c5\u0100cs\xbe\xc3r;\uc000\ud835\udc9cign;\u6254ilde\u803b\xc3\u40c3ml\u803b\xc4\u40c4\u0400aceforsu\xe5\xfb\xfe\u0117\u011c\u0122\u0127\u012a\u0100cr\xea\xf2kslash;\u6216\u0176\xf6\xf8;\u6ae7ed;\u6306y;\u4411\u0180crt\u0105\u010b\u0114ause;\u6235noullis;\u612ca;\u4392r;\uc000\ud835\udd05pf;\uc000\ud835\udd39eve;\u42d8c\xf2\u0113mpeq;\u624e\u0700HOacdefhilorsu\u014d\u0151\u0156\u0180\u019e\u01a2\u01b5\u01b7\u01ba\u01dc\u0215\u0273\u0278\u027ecy;\u4427PY\u803b\xa9\u40a9\u0180cpy\u015d\u0162\u017aute;\u4106\u0100;i\u0167\u0168\u62d2talDifferentialD;\u6145leys;\u612d\u0200aeio\u0189\u018e\u0194\u0198ron;\u410cdil\u803b\xc7\u40c7rc;\u4108nint;\u6230ot;\u410a\u0100dn\u01a7\u01adilla;\u40b8terDot;\u40b7\xf2\u017fi;\u43a7rcle\u0200DMPT\u01c7\u01cb\u01d1\u01d6ot;\u6299inus;\u6296lus;\u6295imes;\u6297o\u0100cs\u01e2\u01f8kwiseContourIntegral;\u6232eCurly\u0100DQ\u0203\u020foubleQuote;\u601duote;\u6019\u0200lnpu\u021e\u0228\u0247\u0255on\u0100;e\u0225\u0226\u6237;\u6a74\u0180git\u022f\u0236\u023aruent;\u6261nt;\u622fourIntegral;\u622e\u0100fr\u024c\u024e;\u6102oduct;\u6210nterClockwiseContourIntegral;\u6233oss;\u6a2fcr;\uc000\ud835\udc9ep\u0100;C\u0284\u0285\u62d3ap;\u624d\u0580DJSZacefios\u02a0\u02ac\u02b0\u02b4\u02b8\u02cb\u02d7\u02e1\u02e6\u0333\u048d\u0100;o\u0179\u02a5trahd;\u6911cy;\u4402cy;\u4405cy;\u440f\u0180grs\u02bf\u02c4\u02c7ger;\u6021r;\u61a1hv;\u6ae4\u0100ay\u02d0\u02d5ron;\u410e;\u4414l\u0100;t\u02dd\u02de\u6207a;\u4394r;\uc000\ud8