Jamgreen Jamgreen - 5 months ago 28
Node.js Question

Render math in node.js template with KaTeX

I want to render math in a page with latex mode in

node.js
. I have looked at
MathJaX
and
KaTeX
.

I render my page with

router.get('/math', function (req, res) {
res.render('math');
});


so how can I make sure the math on this page is rendered as math?

I can use

const katex = require('katex');
const math = katex.renderToString("c = \\pm\\sqrt{a^2 + b^2}", { displayMode: true });


and then set variables in the template with

router.get('/math', function (req, res) {
res.render('math', { math: math });
});


but I would rather want to write all the math directly in the template instead of setting each variable specifically in the javascript code.

Edit



I am getting the html from the template with

router.get('/math', function (req, res) {
res.render('math', function (err, html) {
html = html.replace(/\$\$(.*?)\$\$/g, function (outer, inner) {
return katex.renderToString(inner, { displayMode: true });
});

res.send(html);
});
});


is it a good way to do it or can I omit calling
res.render()
before using
res.send()
?

When I use

html = html.replace(/\$\$(.*?)\$\$/g, function (outer, inner) {
return katex.renderToString(inner, { displayMode: true });
}).replace(/\$(.*?)\$/g, function (outer, inner) {
return katex.renderToString(inner);
});


the server fails and I get the error
ParseError: KaTeX parse error: Expected 'EOF', got '$' at position 1: $_

MvG MvG
Answer

The KaTeX core doesn't care about where the text input comes from. Identifying TeX source snippets is not part of its objective. There is a contributed extension called auto-render which is maintained as part of the KaTeX code base. It will identify TeX input within a page, and replace it with KaTeX-rendered HTML. But it operates client-side on the DOM-tree, not server-side on the HTML markup text.

So I suggest you roll your own code here. I guess that you shouldn't need any DOM parsing. Instead I'd try to come up with some suitable regular expressions to describe math blocks, and then replace these by their renderToString analogon. Something like

html = html.replace(/\$\$(.*?)\$\$/g, function(outer, inner) {
    return katex.renderToString(inner, { displayMode: true });
}).replace(/\\\[(.*?)\\\]/g, function(outer, innner) {
    return katex.renderToString(inner, { displayMode: true });
}).replace(/\\\((.*?)\\\)/g, function(outer, innner) {
    return katex.renderToString(inner, { displayMode: false });
});

Depending on your use case, you may want to apply this substitution to your input template, to the arguments you provide to your template, or to the result you get from rendering the template. In all three cases you should try to get your hands on the relevant portion of HTML text as a single string at some point. Which in some cases might involve buffering a stream-based template output. Since you didn't say what frameworks you are using for templates and app server, I can't provide more detail on this.

Note that the above gives TeX a higher priority than HTML: Input like $$a<p>b$$ are interpreted as TeX input a < p > b. This is in contrast to client-side rendering (as by the auto-renderer), where the above would be treated as two paragraphs, neither of them containing a complete TeX input fragment, and where to achieve a < p > b rendering one would have to encode the < as &lt;. If you control all your input, giving TeX priority is likely what you want. If you accept user-provided input, though, then this behavior might cause surprises with some content-sanitizing procedures, or perhaps also Wiki markup formatting code. So if you intend to do anything along those lines, make sure you know what behavior you want, and make your clients aware of it.

If you want to aim for even higher compatibility with TeX, you could try supporting additional top-level environments. For example, you could include

html = html.replace(/\\begin\{align\*\}(.*?)\\end\{align\*\}/g, function(outer, inner) {
    return katex.renderToString("\\begin{aligned}" + inner + "\\end{aligned}", { displayMode: true });
})

using the fact that the aligned environment has been implemented while the align* environment has not.