56 lines
1.8 KiB
TypeScript
56 lines
1.8 KiB
TypeScript
import {
|
|
defaultTreeAdapter,
|
|
parse,
|
|
type DefaultTreeAdapterTypes,
|
|
} from "parse5";
|
|
|
|
interface HtmlScriptReference {
|
|
readonly scriptPath: string;
|
|
readonly startOffset: number;
|
|
readonly endOffset: number;
|
|
}
|
|
|
|
/** Extract inert HTML script references and the first document base href. */
|
|
export const htmlArtifactReferences = (
|
|
text: string,
|
|
): {
|
|
readonly baseHref: string | null;
|
|
readonly scripts: readonly HtmlScriptReference[];
|
|
} => {
|
|
const pending: DefaultTreeAdapterTypes.Node[] = [
|
|
parse(text, { sourceCodeLocationInfo: true }),
|
|
];
|
|
const scripts: HtmlScriptReference[] = [];
|
|
let baseHref: string | null = null;
|
|
while (pending.length > 0) {
|
|
const node = pending.pop();
|
|
if (node === undefined) continue;
|
|
if (
|
|
defaultTreeAdapter.isElementNode(node) &&
|
|
node.namespaceURI === "http://www.w3.org/1999/xhtml"
|
|
) {
|
|
if (node.tagName === "base" && baseHref === null) {
|
|
const href = node.attrs.find(({ name }) => name === "href");
|
|
if (href !== undefined) baseHref = href.value;
|
|
}
|
|
if (node.tagName === "script") {
|
|
const src = node.attrs.find(({ name }) => name === "src");
|
|
const location = node.sourceCodeLocation?.startTag;
|
|
if (src !== undefined && src.value !== "" && location !== undefined) {
|
|
scripts.push({
|
|
scriptPath: src.value,
|
|
startOffset: location.startOffset,
|
|
endOffset: location.endOffset,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
// Template content belongs to a separate inert document fragment. Only
|
|
// traverse the actual document children, without recursion on deep input.
|
|
if ("childNodes" in node) {
|
|
for (const child of node.childNodes.toReversed()) pending.push(child);
|
|
}
|
|
}
|
|
return { baseHref, scripts };
|
|
};
|