139 lines
3.1 KiB
TypeScript
139 lines
3.1 KiB
TypeScript
import {
|
|
DOMParser,
|
|
Element,
|
|
NodeType,
|
|
Text,
|
|
} from "https://deno.land/x/deno_dom@v0.1.38/deno-dom-wasm.ts";
|
|
import { assert } from "https://deno.land/std/testing/asserts.ts";
|
|
import katex from "https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.mjs";
|
|
|
|
// this is similar to delimiter object in auto-render.js
|
|
interface IKaTeXAutoRenderSurround {
|
|
display: boolean;
|
|
test: RegExp;
|
|
}
|
|
|
|
// options to be given to renderMathInElement
|
|
interface IKaTeXAutoRenderOption {
|
|
// delimiters
|
|
surronds: IKaTeXAutoRenderSurround[];
|
|
|
|
// tag names which are excluded
|
|
ignoreTags: string[];
|
|
}
|
|
|
|
// the default option is same with auto-render.js
|
|
const DefaultOptions: IKaTeXAutoRenderOption = {
|
|
surronds: [
|
|
{ test: /\$\$(.+?)\$\$/, display: true },
|
|
{ test: /\$(.+?)\$/, display: false },
|
|
],
|
|
ignoreTags: [
|
|
"script",
|
|
"noscript",
|
|
"style",
|
|
"textarea",
|
|
"pre",
|
|
"code",
|
|
],
|
|
};
|
|
|
|
function renderText(
|
|
element: Text,
|
|
options: IKaTeXAutoRenderOption,
|
|
): string | null {
|
|
let restText = element.textContent;
|
|
const partialResults = new Array<string>();
|
|
|
|
let anyMatch = false;
|
|
|
|
while (true) {
|
|
let match: RegExpExecArray | null = null;
|
|
let surround: IKaTeXAutoRenderSurround | null = null;
|
|
for (const sr of options.surronds) {
|
|
match = sr.test.exec(restText);
|
|
surround = sr;
|
|
if (match) break;
|
|
}
|
|
if (!match) {
|
|
break;
|
|
} else {
|
|
anyMatch = true;
|
|
}
|
|
|
|
// Insert text before the match
|
|
partialResults.push(restText.substr(0, match.index));
|
|
|
|
// Renders and inserts the KaTeX text
|
|
assert(surround !== null);
|
|
const renderOption = Object.assign({}, options, {
|
|
displayMode: surround.display,
|
|
});
|
|
partialResults.push(
|
|
katex.renderToString(match[1], renderOption),
|
|
);
|
|
|
|
restText = restText.substr(match.index + match[0].length);
|
|
}
|
|
|
|
if (!anyMatch) {
|
|
return null;
|
|
}
|
|
|
|
// Inserts the leftover text
|
|
partialResults.push(restText);
|
|
return partialResults.join("");
|
|
}
|
|
|
|
function renderElement(
|
|
element: Element,
|
|
options: IKaTeXAutoRenderOption,
|
|
): void {
|
|
for (const child of element.childNodes) {
|
|
switch (child.nodeType) {
|
|
case NodeType.ELEMENT_NODE:
|
|
{
|
|
if (
|
|
!options.ignoreTags.includes(
|
|
child.nodeName.toLowerCase(),
|
|
)
|
|
) {
|
|
renderElement(child as Element, options);
|
|
}
|
|
break;
|
|
}
|
|
case NodeType.TEXT_NODE:
|
|
{
|
|
const newText = renderText(child as Text, options);
|
|
if (newText != null) {
|
|
const document = child.ownerDocument;
|
|
assert(document !== null);
|
|
const newChild = document.createElement("");
|
|
newChild.innerHTML = newText;
|
|
child.replaceWith(...newChild.childNodes);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function renderMathInFile(filename: string): Promise<void> {
|
|
const decoder = new TextDecoder();
|
|
const document = new DOMParser().parseFromString(
|
|
decoder.decode(await Deno.readFile(filename)),
|
|
"text/html",
|
|
)!;
|
|
|
|
renderElement(document.body, DefaultOptions);
|
|
|
|
assert(document.documentElement !== null);
|
|
await Deno.writeTextFile(filename, '<!DOCTYPE html>' + document.documentElement.outerHTML);
|
|
}
|
|
|
|
const filenames = Deno.args;
|
|
for (const filename of filenames) {
|
|
await renderMathInFile(filename);
|
|
}
|