import { debug } from "../deno_ral/log.ts";
import { HTMLDocument, initParser } from "deno_dom/deno-dom-wasm-noinit.ts";
import { register } from "deno_dom/src/parser.ts";
import { DOMParser } from "deno_dom/src/dom/dom-parser.ts";
import { makeTimedFunctionAsync } from "./performance/function-times.ts";
export async function getDomParser() {
await initDenoDom();
return new DOMParser();
}
export const parseHtml = makeTimedFunctionAsync(
"parseHtml",
async function parseHtml(src: string): Promise<HTMLDocument> {
await initDenoDom();
const result = (new DOMParser()).parseFromString(src, "text/html");
if (!result) {
throw new Error("Couldn't parse string into HTML");
}
return result;
},
);
export async function writeDomToHtmlFile(
doc: HTMLDocument,
path: string,
doctype?: string,
) {
if (doc.documentElement === null) {
throw new Error("Document has no root element");
}
const output = doctype
? doctype + "\n" + doc.documentElement.outerHTML
: doc.documentElement.outerHTML;
await Deno.writeTextFile(path, output);
}
let s_DenoDomInitialized = false;
export async function initDenoDom() {
if (!s_DenoDomInitialized) {
try {
const denoNativePluginPath = Deno.env.get("DENO_DOM_PLUGIN");
if (denoNativePluginPath) {
type DeepWriteable<T> = {
-readonly [P in keyof T]: DeepWriteable<T[P]>;
};
const _symbols = {
deno_dom_usize_len: { parameters: [], result: "usize" },
deno_dom_parse_sync: {
parameters: ["buffer", "usize", "buffer"],
result: "void",
},
deno_dom_parse_frag_sync: {
parameters: ["buffer", "usize", "buffer", "usize", "buffer"],
result: "void",
},
deno_dom_is_big_endian: { parameters: [], result: "u32" },
deno_dom_copy_buf: {
parameters: ["buffer", "buffer"],
result: "void",
},
} as const;
const symbols = _symbols as DeepWriteable<typeof _symbols>;
const dylib = Deno.dlopen(denoNativePluginPath, symbols);
const utf8Encoder = new TextEncoder();
const utf8Decoder = new TextDecoder();
const usizeBytes = Number(dylib.symbols.deno_dom_usize_len());
const isBigEndian = Boolean(
dylib.symbols.deno_dom_is_big_endian() as number,
);
const dylibParseSync = dylib.symbols.deno_dom_parse_sync.bind(
dylib.symbols,
);
const dylibParseFragSync = dylib.symbols.deno_dom_parse_frag_sync.bind(
dylib.symbols,
);
const returnBufSizeLenRaw = new ArrayBuffer(usizeBytes * 2);
const returnBufSizeLen = new Uint8Array(returnBufSizeLenRaw);
type DocumentParser = (
srcBuf: Uint8Array,
srcLength: bigint,
returnBuf: Uint8Array,
) => void;
type FragmentParser = (
srcBuf: Uint8Array,
srcLength: bigint,
contextLocalNameBuf: Uint8Array,
contextLocalNameLength: bigint,
returnBuf: Uint8Array,
) => void;
const genericParse = (
parser: DocumentParser | FragmentParser,
srcHtml: string,
contextLocalName?: string,
): string => {
const encodedHtml = utf8Encoder.encode(srcHtml);
if (contextLocalName) {
const encodedContextLocalName = utf8Encoder.encode(
contextLocalName,
);
(parser as FragmentParser)(
encodedHtml,
BigInt(encodedHtml.length),
encodedContextLocalName,
BigInt(encodedContextLocalName.length),
returnBufSizeLen,
);
} else {
(parser as DocumentParser)(
encodedHtml,
BigInt(encodedHtml.length),
returnBufSizeLen,
);
}
const outBufSize = Number(
new DataView(returnBufSizeLenRaw).getBigUint64(0, !isBigEndian),
);
const outBuf = new Uint8Array(outBufSize);
dylib.symbols.deno_dom_copy_buf(
returnBufSizeLen.slice(usizeBytes),
outBuf,
);
return utf8Decoder.decode(outBuf);
};
const parse = (html: string): string => {
return genericParse(dylibParseSync, html);
};
const parseFrag = (html: string, contextLocalName?: string): string => {
return genericParse(dylibParseFragSync, html, contextLocalName);
};
debug("Loaded deno-dom-native");
register(parse, parseFrag);
s_DenoDomInitialized = true;
return;
}
} catch (e) {
if (!(e instanceof Error)) {
throw e;
}
debug("Error loading deno-dom-native: " + e.message);
}
}
if (!s_DenoDomInitialized) {
await initParser();
s_DenoDomInitialized = true;
}
}
export * from "deno_dom/src/api.ts";
export { DOMParser } from "deno_dom/src/dom/dom-parser.ts";
export { Node, NodeType } from "deno_dom/src/dom/node.ts";