//#region src/utils/html.ts /** * @module * HTML utility. */ const HtmlEscapedCallbackPhase = { Stringify: 1, BeforeStream: 2, Stream: 3 }; const raw = (value, callbacks) => { const escapedString = new String(value); escapedString.isEscaped = true; escapedString.callbacks = callbacks; return escapedString; }; const escapeRe = /[&<>'"]/; const stringBufferToString = async (buffer, callbacks) => { let str = ""; callbacks ||= []; const resolvedBuffer = await Promise.all(buffer); for (let i = resolvedBuffer.length - 1;; i--) { str += resolvedBuffer[i]; i--; if (i < 0) break; let r = resolvedBuffer[i]; if (typeof r === "object") callbacks.push(...r.callbacks || []); const isEscaped = r.isEscaped; r = await (typeof r === "object" ? r.toString() : r); if (typeof r === "object") callbacks.push(...r.callbacks || []); if (r.isEscaped ?? isEscaped) str += r; else { const buf = [str]; escapeToBuffer(r, buf); str = buf[0]; } } return raw(str, callbacks); }; const escapeToBuffer = (str, buffer) => { const match = str.search(escapeRe); if (match === -1) { buffer[0] += str; return; } let escape; let index; let lastIndex = 0; for (index = match; index < str.length; index++) { switch (str.charCodeAt(index)) { case 34: escape = """; break; case 39: escape = "'"; break; case 38: escape = "&"; break; case 60: escape = "<"; break; case 62: escape = ">"; break; default: continue; } buffer[0] += str.substring(lastIndex, index) + escape; lastIndex = index + 1; } buffer[0] += str.substring(lastIndex, index); }; const resolveCallbackSync = (str) => { const callbacks = str.callbacks; if (!callbacks?.length) return str; const buffer = [str]; const context = {}; callbacks.forEach((c) => c({ phase: HtmlEscapedCallbackPhase.Stringify, buffer, context })); return buffer[0]; }; const resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => { if (typeof str === "object" && !(str instanceof String)) { if (!(str instanceof Promise)) str = str.toString(); if (str instanceof Promise) str = await str; } const callbacks = str.callbacks; if (!callbacks?.length) return Promise.resolve(str); if (buffer) buffer[0] += str; else buffer = [str]; const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context }))).then((res) => Promise.all(res.filter(Boolean).map((str) => resolveCallback(str, phase, false, context, buffer))).then(() => buffer[0])); if (preserveCallbacks) return raw(await resStr, callbacks); else return resStr; }; //#endregion export { HtmlEscapedCallbackPhase, escapeToBuffer, raw, resolveCallback, resolveCallbackSync, stringBufferToString };