Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/core/deno-dom.ts
3562 views
1
/*
2
* deno-dom.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
7
import { debug } from "../deno_ral/log.ts";
8
9
import { HTMLDocument, initParser } from "deno_dom/deno-dom-wasm-noinit.ts";
10
import { register } from "deno_dom/src/parser.ts";
11
import { DOMParser } from "deno_dom/src/dom/dom-parser.ts";
12
import { makeTimedFunctionAsync } from "./performance/function-times.ts";
13
14
export async function getDomParser() {
15
await initDenoDom();
16
return new DOMParser();
17
}
18
19
export const parseHtml = makeTimedFunctionAsync(
20
"parseHtml",
21
async function parseHtml(src: string): Promise<HTMLDocument> {
22
await initDenoDom();
23
const result = (new DOMParser()).parseFromString(src, "text/html");
24
if (!result) {
25
throw new Error("Couldn't parse string into HTML");
26
}
27
return result;
28
},
29
);
30
31
export async function writeDomToHtmlFile(
32
doc: HTMLDocument,
33
path: string,
34
doctype?: string,
35
) {
36
if (doc.documentElement === null) {
37
throw new Error("Document has no root element");
38
}
39
const output = doctype
40
? doctype + "\n" + doc.documentElement.outerHTML
41
: doc.documentElement.outerHTML;
42
await Deno.writeTextFile(path, output);
43
}
44
45
// We are combining a number of scripts from
46
// https://github.com/b-fuze/deno-dom/blob/master/deno-dom-native.ts
47
// into this. If deno-dom fails, it's likely that this needs to be brought up to date.
48
49
// 2022-08-26: cscheid changed this to match commit a69551336f37cd4010032e039231d926e1a4774c
50
// 2023-02-06: jjallaire confirmed that this is up to date as of commit e18ab07fd6e23f1e32ffd77fb4c0f92fadb81b87
51
// 2024-09-23: cscheid changed this to match commit a84e6057d367282efdf3f1a1f80c3f0982252ffa
52
53
let s_DenoDomInitialized = false;
54
export async function initDenoDom() {
55
if (!s_DenoDomInitialized) {
56
try {
57
// try to load the native plugin
58
const denoNativePluginPath = Deno.env.get("DENO_DOM_PLUGIN");
59
if (denoNativePluginPath) {
60
// These types are only to deal with `as const` `readonly` shenanigans
61
type DeepWriteable<T> = {
62
-readonly [P in keyof T]: DeepWriteable<T[P]>;
63
};
64
const _symbols = {
65
deno_dom_usize_len: { parameters: [], result: "usize" },
66
deno_dom_parse_sync: {
67
parameters: ["buffer", "usize", "buffer"],
68
result: "void",
69
},
70
deno_dom_parse_frag_sync: {
71
parameters: ["buffer", "usize", "buffer", "usize", "buffer"],
72
result: "void",
73
},
74
deno_dom_is_big_endian: { parameters: [], result: "u32" },
75
deno_dom_copy_buf: {
76
parameters: ["buffer", "buffer"],
77
result: "void",
78
},
79
} as const;
80
const symbols = _symbols as DeepWriteable<typeof _symbols>;
81
82
const dylib = Deno.dlopen(denoNativePluginPath, symbols);
83
84
const utf8Encoder = new TextEncoder();
85
const utf8Decoder = new TextDecoder();
86
const usizeBytes = Number(dylib.symbols.deno_dom_usize_len());
87
const isBigEndian = Boolean(
88
dylib.symbols.deno_dom_is_big_endian() as number,
89
);
90
91
const dylibParseSync = dylib.symbols.deno_dom_parse_sync.bind(
92
dylib.symbols,
93
);
94
const dylibParseFragSync = dylib.symbols.deno_dom_parse_frag_sync.bind(
95
dylib.symbols,
96
);
97
98
// Reused for each invocation. Not thread safe, but JS isn't multithreaded
99
// anyways.
100
const returnBufSizeLenRaw = new ArrayBuffer(usizeBytes * 2);
101
const returnBufSizeLen = new Uint8Array(returnBufSizeLenRaw);
102
103
type DocumentParser = (
104
srcBuf: Uint8Array,
105
srcLength: bigint,
106
returnBuf: Uint8Array,
107
) => void;
108
type FragmentParser = (
109
srcBuf: Uint8Array,
110
srcLength: bigint,
111
contextLocalNameBuf: Uint8Array,
112
contextLocalNameLength: bigint,
113
returnBuf: Uint8Array,
114
) => void;
115
116
const genericParse = (
117
parser: DocumentParser | FragmentParser,
118
srcHtml: string,
119
contextLocalName?: string,
120
): string => {
121
const encodedHtml = utf8Encoder.encode(srcHtml);
122
if (contextLocalName) {
123
const encodedContextLocalName = utf8Encoder.encode(
124
contextLocalName,
125
);
126
(parser as FragmentParser)(
127
encodedHtml,
128
BigInt(encodedHtml.length),
129
encodedContextLocalName,
130
BigInt(encodedContextLocalName.length),
131
returnBufSizeLen,
132
);
133
} else {
134
(parser as DocumentParser)(
135
encodedHtml,
136
BigInt(encodedHtml.length),
137
returnBufSizeLen,
138
);
139
}
140
141
const outBufSize = Number(
142
new DataView(returnBufSizeLenRaw).getBigUint64(0, !isBigEndian),
143
);
144
const outBuf = new Uint8Array(outBufSize);
145
dylib.symbols.deno_dom_copy_buf(
146
returnBufSizeLen.slice(usizeBytes),
147
outBuf,
148
);
149
150
return utf8Decoder.decode(outBuf);
151
};
152
153
const parse = (html: string): string => {
154
return genericParse(dylibParseSync, html);
155
};
156
157
const parseFrag = (html: string, contextLocalName?: string): string => {
158
return genericParse(dylibParseFragSync, html, contextLocalName);
159
};
160
161
debug("Loaded deno-dom-native");
162
163
// Register parse function and return
164
register(parse, parseFrag);
165
s_DenoDomInitialized = true;
166
return;
167
}
168
} catch (e) {
169
if (!(e instanceof Error)) {
170
throw e;
171
}
172
debug("Error loading deno-dom-native: " + e.message);
173
}
174
}
175
176
// didn't successfully load deno-dom-native, load the wasm version
177
if (!s_DenoDomInitialized) {
178
await initParser();
179
s_DenoDomInitialized = true;
180
}
181
}
182
183
export * from "deno_dom/src/api.ts";
184
export { DOMParser } from "deno_dom/src/dom/dom-parser.ts";
185
export { Node, NodeType } from "deno_dom/src/dom/node.ts";
186
187