Jamgreen
Jamgreen

Reputation: 11059

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: $_

Upvotes: 3

Views: 2786

Answers (2)

Leonard Storcks
Leonard Storcks

Reputation: 519

/\\\((.*?)\\\)/g

does not work in many cases, as the following happens:

enter image description here

Multiple formulas are recognized as one, resulting in errors.

To prevent this, use

/\\\(((.)*?(?=\\\)))?\\\)/

Example

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

However, there are still caveats, for instance things not being rendered properly. An alternative approach could be https://joa.sh/posts/2015-09-14-prerender-mathjax.html (this is the next one I am going to try ...)

Upvotes: 1

MvG
MvG

Reputation: 60968

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.


In response to your edit:

The combination of res.render with a callback and res.send inside that callback looks good to me. You could avoid calling res.render if you called the template rendering yourself instead, but it's up to you to decide whether that is desirable.

The cause of the error message is hard to decide without knowing the input. It seems as though you might have some input starting with a double $$ but ending only with a single $, so that the first regexp fails to match and the second includes an additional $ in its matched string.

Upvotes: 3

Related Questions