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(); 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 { 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, '' + document.documentElement.outerHTML); } const filenames = Deno.args; for (const filename of filenames) { await renderMathInFile(filename); }