export {
attributeName as htmlAttributeName,
isSafeHtmlName,
} from "../shared/attributes";
export function escapeHtml(value: unknown): string {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
export function serializeInlineScript(value: unknown): string {
const json = JSON.stringify(value);
if (json === undefined) return "null";
return json
.replaceAll("<", "\\u003c")
.replaceAll(">", "\\u003e")
.replaceAll("&", "\\u0026")
.replaceAll("\u2028", "\\u2028")
.replaceAll("\u2029", "\\u2029");
}
const TITLE_ELEMENT = /^
)/i;
const DOCUMENT_TITLE = /]*)?>[\s\S]*?<\/title\s*>/i;
const DOCUMENT_TITLES = /]*)?>[\s\S]*?<\/title\s*>/gi;
export function injectHeadElements(
html: string,
elements: readonly string[],
defaultTitle?: string,
): string {
const match = /]*)?>([\s\S]*?)<\/head\s*>/i.exec(html);
if (!match) return html;
let headHtml = match[0];
let headElements = [...elements];
let lastCollectedTitle = -1;
for (let i = 0; i < headElements.length; i++) {
if (TITLE_ELEMENT.test(headElements[i])) lastCollectedTitle = i;
}
if (lastCollectedTitle >= 0) {
headElements = headElements.filter(
(element, index) =>
!TITLE_ELEMENT.test(element) || index === lastCollectedTitle,
);
headHtml = headHtml.replace(DOCUMENT_TITLES, "");
} else if (!DOCUMENT_TITLE.test(headHtml) && defaultTitle !== undefined) {
headElements.unshift(`${escapeHtml(defaultTitle)}`);
}
headHtml = headHtml.replace(
/<\/head\s*>/i,
() => `${headElements.join("\n")}`,
);
return (
html.slice(0, match.index) +
headHtml +
html.slice(match.index + match[0].length)
);
}