export const Fragment = "FRAGMENT"; export type Tag = { name: string; attrs?: Record; body: Tag[] | string; } | string; function htmlEscape(s: string): string { s = s.replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/\n/g, "
"); let oldS = s; do { oldS = s; s = s.replace(/ /g, "  "); } while (s !== oldS); return s; } export function renderHtml(t: Tag | null): string { if (!t) { return ""; } if (typeof t === "string") { return htmlEscape(t); } const attrs = t.attrs ? " " + Object.entries(t.attrs) .filter(([, value]) => value !== undefined) .map(([k, v]) => `${k}="${htmlEscape(v!)}"`).join( " ", ) : ""; const body = typeof t.body === "string" ? htmlEscape(t.body) : t.body.map(renderHtml).join(""); if (t.name === Fragment) { return body; } return `<${t.name}${attrs}>${body}`; }