123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165/** * Render markup tag. * @param tagName (required) * @param attributes (optional) * @param children (optional) * @return rendered tag * * Examples: * * ```ts * import { tag } from "https://deno.land/x/markup_tag/mod.ts"; * import { assertEquals } from "https://deno.land/std/testing/asserts.ts" * * // common usage * assertEquals( * tag("div", { id: "foo", class: "bar" }, "Hello world!"), * `<div id="foo" class="bar">Hello world!</div>`, * ); * * // void (no-close) tag * assertEquals( * tag("meta", { charset: "utf-8" }), * `<meta charset="utf-8">`, * ); * * // nested tags * assertEquals( * tag( "ul", { class: "nav" }, tag("li", "first"), tag("li", "second")), * `<ul class="nav"><li>first</li><li>second</li></ul>`, * ); * * // boolean attributes * assertEquals( * tag("button", { type: "button", disabled: true }, "disabled"), * `<button type="button" disabled>disabled</button>`, * );
* // skip attributes * assertEquals( * tag("input", { type: "text", readonly: false }), * `<input type="text">`, * ); * ``` */export function tag( tagName: string, attributesOrFirstChild?: Record<string, string | number | boolean> | string, ...children: Array<string>): string { if (!tagName) { throw new Error("tagName is empty."); }
if (/\s/.test(tagName)) { throw new Error("tagName has whitespace characters."); }
const isVoidTag = [ "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr", ].includes(tagName);
const attrs: Array<string> = []; if (typeof attributesOrFirstChild === "string") { children.unshift(attributesOrFirstChild); } else if (attributesOrFirstChild != null) { Object.entries(attributesOrFirstChild) .forEach(([k, v]) => { if (typeof v !== "boolean") { // add the pair of key and value when the attribute is string or number attrs.push(` ${k}="${v}"`); } else if (v) { // add just key key when the attribute is true attrs.push(` ${k}`); } // skip when the attribute is false }); }
const close = isVoidTag ? "" : `${children.join("")}</${tagName}>`;
return `<${tagName}${attrs.join("")}>${close}`;}
/** * character reference to no-break space */export const NBSP = " ";
/** * character reference to `<` */export const LT = "<";
/** * character reference to `>` */export const GT = ">";
/** * character reference to `&` */export const AMP = "&";
/** * character reference to `"` */export const QUOT = """;
/** * Sanitize `&`, `<`, `>` and `"` in string. * @param str (optional) * @param SanitizeOption (optional) * @return sanitized string * * Use SanitizeOption like `{ amp: false }` to ignore sanitizing. * * Examples: * * ```ts * import { sanitize } from "https://deno.land/x/markup_tag/mod.ts"; * import { assertEquals } from "https://deno.land/std/testing/asserts.ts" * * // common usage * assertEquals( * sanitize(`<img src="https://www.example.com?width=10&height=10">`), * "<img src="https://www.example.com?width=10&height=10">", * ); * * // ignore sanitizing specific characters * assertEquals(sanitize("<br>", { lt: false, gt: false }), "<br>"); * ``` */export function sanitize( str = "", { amp = true, lt = true, gt = true, quot = true }: Record<string, boolean> = {},): string { if (amp) { str = str.replaceAll("&", AMP); } if (lt) { str = str.replaceAll("<", LT); } if (gt) { str = str.replaceAll(">", GT); } if (quot) { str = str.replaceAll(`"`, QUOT); } return str;}