@asimojs/lml
Version:
LML - List Markup Language
273 lines (272 loc) • 9.62 kB
JavaScript
function v(r) {
if (Array.isArray(r)) {
if (r.length === 0)
return "fragment";
const c = r[0];
if (typeof c == "string" && c.length > 1) {
const p = c[0];
if (p === S)
return "element";
if (p === P)
return "component";
if (p === L || p === $)
return "invalid";
}
return "fragment";
} else if (typeof r == "string")
return "text";
return "invalid";
}
const S = "#", P = "*", L = "@", $ = "!", U = "!", j = ".", X = /^(\#|\*|\!|\@)(\w+\:)?([\w\-]+)(\+[\w\-]+)?(\.[\.\w\-]+)*(\!.+)?$/;
function O(r, c) {
if (r == null)
return "";
const p = v(r);
if (p === "text")
return i(r);
if (p === "invalid")
return e(`Invalid LML node: ${JSON.stringify(r)}`);
const u = p === "fragment", y = r, d = y.length;
if (d === 0)
return "";
if (!u) {
const s = y[0].match(X);
if (s) {
const o = s[1], n = s[2], f = s[3], h = s[4], g = s[5], A = s[6], T = n ? n.slice(0, -1) : "", x = h ? h.slice(1) : "";
let E;
if (g) {
const m = g.split(j);
E = [];
for (const N of m)
N != "" && E.push(N);
}
let b = 1, a;
const w = y[b];
if (w && typeof w == "object" && !Array.isArray(w)) {
a = w, b++, (x || E || A) && (x && (a.type = x), E && (a.class && typeof a.class == "string" ? a.class = E.join(" ") + " " + a.class : a.class = E.join(" ")), A && (a.keyValue = a.key = A.slice(1)), y[0] = `${o}${n || ""}${f}`);
const m = a.key;
m && m !== a.keyValue && (a.keyValue = a.key);
} else {
if (A) {
const m = A.slice(1);
a = {
key: m,
keyValue: m
};
}
(E || x) && (a = a || {}, x && (a.type = x), E && (a.class = E.join(" ")));
}
let R;
if (d > b) {
R = [];
for (let m = b; m < d; m++)
R.push(O(y[m], c));
}
if (o === S || o === P || o === L) {
let m = o === S ? "element" : "component";
o === L && (m = "decorator");
const N = {
type: m,
tagName: f,
ns: T
};
return c.format(N, a, R);
} else
e(`Unsupported node prefix: ${o}`);
return "";
}
}
const l = [];
for (const t of y)
if (typeof t == "string" || Array.isArray(t)) {
const s = O(t, c);
if (s)
if (Array.isArray(s))
for (let o of s)
l.push(o);
else
l.push(s);
} else
e(`Invalid LML node: ${JSON.stringify(t)}`);
return l.length > 0 ? l : "";
function i(t) {
return c.format({
type: "text",
value: t
});
}
function e(t) {
return c.error ? (t.length > 100 && (t = t.slice(0, 100) + "..."), c.error(t)) : console.error("[LML Scan Error] " + t), "";
}
}
const _ = {
/**
* Allowed tags - img + tags from https://github.com/apostrophecms/sanitize-html
* Note: form and input are not in the list
*/
allowedElements: /* @__PURE__ */ new Set(["address", "article", "aside", "footer", "header", "h1", "h2", "h3", "h4", "h5", "h6", "hgroup", "main", "nav", "section", "blockquote", "dd", "div", "dl", "dt", "figcaption", "figure", "hr", "li", "main", "ol", "p", "pre", "ul", "a", "abbr", "b", "bdi", "bdo", "br", "cite", "code", "data", "dfn", "em", "i", "kbd", "mark", "q", "rb", "rp", "rt", "rtc", "ruby", "s", "samp", "small", "span", "strong", "sub", "sup", "time", "u", "var", "wbr", "caption", "col", "colgroup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "img"]),
/** Forbid style, srcset and event handler attributes */
forbiddenElementAttributes: /* @__PURE__ */ new Set(["style", "srcset"]),
/** Tell if elemeent event handlers attributes must be discarded */
forbidEventHandlers: !0,
/**
* URL attributes used in allowedElements, will be checked against allowedUrlPrefixes
* as per https://stackoverflow.com/questions/2725156/complete-list-of-html-tag-attributes-which-have-a-url-value
*/
urlAttributes: /* @__PURE__ */ new Set(["href", "src", "cite", "action", "profile", "longdesc", "usemap", "formaction", "icon", "poster", "background", "codebase", "data", "classid", "manifest"]),
/** Allowed URLs - DO NOT PUT "data:text" -> data:text/html can contain malicious scripts */
allowedUrlPrefixes: ["/", "./", "http://", "https://", "mailto://", "tel://", "data:image/"]
};
function I(r, c, p, u, y) {
u = u || ((i) => console.error("[lm2JSX Error] " + i));
const d = y || _;
let l = null;
if (d.allowedUrlPrefixes.length) {
const i = `^(${d.allowedUrlPrefixes.join("|")})`;
l = new RegExp(i, "i");
}
return O(r, {
format: (i, e, t) => {
const s = i.type;
if (s === "text")
return i.value;
if (s === "element" || s === "component") {
if (s === "element") {
if (!d.allowedElements.has(i.tagName))
return u(`Unauthorized element: ${i.tagName}`), "";
if (e) {
for (const n of Object.getOwnPropertyNames(e))
if (d.forbiddenElementAttributes.has(n))
u(`Unauthorized element attribute: ${n}`), delete e[n];
else if (d.forbidEventHandlers && n.match(/^on/i))
u(`Unauthorized event handler: ${n}`), delete e[n];
else if (l && d.urlAttributes.has(n)) {
const f = e[n];
(typeof f != "string" || !f.match(l)) && (u(`Unauthorized URL: ${n}="${f}"`), delete e[n]);
}
}
}
e && e.class && !e.className && (e.className = e.class);
let o = i.tagName;
if (s === "component") {
const n = p && p(o, i.ns || "") || null;
if (!n)
return u("Invalid component: " + (i.ns ? i.ns + ":" : "") + o), "";
o = n;
}
return t ? c(o, e || null, ...t) : c(o, e || null);
}
return u("Unsupported node type: " + s), "";
},
error: u
});
}
function M(r, c) {
let p = v(r) === "fragment" ? r : [r];
const u = /* @__PURE__ */ new Set();
for (const l of c)
l.node && u.add(l.node);
const y = /* @__PURE__ */ new Map(), d = u.size;
if (d > 0) {
let l = 0;
k(r, (i, e, t, s) => (u.has(i) && (l++, y.set(i, {
node: e,
parent: t,
parentRef: s
})), l < d), p, 0);
}
for (const l of c) {
const i = l.action;
let e = p, t = null, s = "", o = "";
if (l.node) {
let n = y.get(l.node);
if (n) {
if (t = n.parent, s = n.parentRef, e = n.node, o = l.path || "", o === "") {
const f = v(e);
f !== "invalid" && f !== "text" && (i === "append" || i === "prepend") && (o = "children");
}
} else
continue;
}
if (o === "children") {
if (e.length > 1) {
let n = 1;
if (typeof e[n] != "string" && !Array.isArray(e[n]) && n++, i === "delete")
e.splice(n, e.length - n);
else {
const f = l.content, h = v(f);
if (h !== "invalid") {
const g = h === "fragment";
i === "insertBefore" || i === "prepend" ? g ? e.splice.apply(e, [n, 0, ...f]) : e.splice(n, 0, f) : i === "insertAfter" || i === "append" ? g ? e.push.apply(e, f) : e.push(f) : i === "replace" && (g ? e.splice.apply(e, [n, e.length - n, ...f]) : e.splice(n, e.length - n, f));
}
}
}
continue;
} else if (o) {
const n = o.split("/");
if (Array.isArray(e) && e.length > 1)
e = e[1];
else
continue;
for (const f of n)
t = e, s = f, e = t[s];
}
if (e)
if (i === "delete")
Array.isArray(t) ? typeof s == "number" && t.splice(s, 1) : t ? t[s] = [] : p = [];
else {
const n = l.content, f = v(n);
if (f !== "invalid") {
const h = f === "fragment";
if (i === "insertBefore" || i === "insertAfter" || i === "replace")
if (Array.isArray(t)) {
if (typeof s == "number") {
let g = s, A = 0;
i === "insertAfter" ? g++ : i === "replace" && A++, h ? t.splice.apply(t, [g, A, ...n]) : t.splice(g, A, n);
}
} else
t ? i === "insertBefore" ? h ? t[s] = [...n, e] : t[s] = [n, e] : i === "insertAfter" ? h ? t[s] = [e, ...n] : t[s] = [e, n] : i === "replace" && (t[s] = n) : i === "replace" && (p = h ? n : [n]);
else
(i === "prepend" || i === "append") && v(e) === "fragment" && (i === "append" ? h ? e.push.apply(e, n) : e.push(n) : i === "prepend" && (h ? e.splice.apply(e, [0, 0, ...n]) : e.splice(0, 0, n)));
}
}
}
return p.length === 1 ? p[0] : p;
}
function k(r, c, p, u) {
if (!r)
return !0;
const y = v(r);
if (Array.isArray(r) && r.length) {
if (y === "component" || y === "element") {
let l = "";
const e = r[0].indexOf(U);
if (e > -1)
l = r[0].slice(e + 1);
else {
const t = r[1];
t && typeof t == "object" && !Array.isArray(t) && (l = t.key);
}
if (l && c(l, r, p, u) === !1)
return !1;
for (let t = 1; r.length > t; t++)
if (!k(r[t], c, r, t))
return !1;
} else
for (let l = 0; r.length > l; l++)
if (!k(r[l], c, r, l))
return !1;
} else if (typeof r == "object") {
for (const d of Object.getOwnPropertyNames(r))
if (!k(r[d], c, r, d))
return !1;
}
return !0;
}
export {
_ as defaultSanitizationRules,
I as lml2jsx,
v as nodeType,
O as processJSX,
M as updateLML
};