vue-email
Version:
💌 Write email templates with Vue
1,468 lines (1,454 loc) • 539 kB
JavaScript
'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