UNPKG

@asimojs/lml

Version:

LML - List Markup Language

273 lines (272 loc) 9.62 kB
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 };