使用KaTeX在node.js模板中渲染数学公式

3

我希望能够在 node.js 中使用 LaTeX 模式呈现页面中的数学公式。我已经查看了 MathJaXKaTeX

我使用以下方式呈现页面:

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

那么如何确保页面上的数学公式渲染为数学符号呢?

我可以使用

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

然后在模板中使用变量进行设置:

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

但我更愿意直接在模板中写数学公式,而不是在javascript代码中逐个设置变量。

编辑

我通过以下方式从模板获取HTML:

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);
  });
});

请问在使用res.send()之前是否需要调用res.render()呢?这种做法合适吗?

举个例子:

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

服务器出现故障,我收到了错误提示:ParseError: KaTeX解析错误:期望 'EOF' ,但在位置1遇到 '$':$_


作为这个问题和答案的补充,使用 /\$\$(.*?)\$\$/g 不能匹配换行符(因为.无法匹配换行符)。我建议使用 /\$\$([^\$]*?)\$\$/g,这将更好地匹配客户端行为。 - schneiderfelipe
2个回答

3
KaTeX核心不关心文本输入的来源。识别TeX源代码片段不是其目标的一部分。有一个名为auto-render的贡献扩展,它作为KaTeX代码库的一部分进行维护。它将在页面中识别TeX输入,并用KaTeX渲染的HTML替换它。但它在DOM树上操作客户端,而不是在HTML标记文本上操作服务器端。
因此,我建议您在这里编写自己的代码。我想你不需要任何DOM解析。相反,我会尝试想出一些适当的正则表达式来描述数学块,然后用它们的renderToString模拟替换它们。类似于:
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 });
});

根据您的使用情况,您可能希望将此替换应用于输入模板、提供给模板的参数或从渲染模板获得的结果。在这三种情况下,您都应该尝试在某个时刻以单个字符串的形式获取相关的HTML文本部分。在某些情况下,这可能涉及缓冲基于流的模板输出。由于您没有说明您正在使用哪些框架进行模板和应用程序服务器,因此我无法提供更多详细信息。

请注意,上述内容将TeX的优先级高于HTML:像$$a<p>b$$这样的输入会被解释为TeX输入a < p > b。这与客户端渲染(如自动渲染器)不同,后者将以上内容视为两个段落,均不包含完整的TeX输入片段,要实现a < p > b的呈现,需要将<编码为&lt;。如果您控制所有输入,则很可能希望给予TeX优先级。但是,如果接受用户提供的输入,则此行为可能会在某些内容消毒程序或Wiki标记格式化代码中引起意外。因此,如果您打算执行任何此类操作,请确保知道所需的行为,并让您的客户端知道它。
如果您想更高度兼容TeX,可以尝试支持其他顶级环境。例如,您可以包括
html = html.replace(/\\begin\{align\*\}(.*?)\\end\{align\*\}/g, function(outer, inner) {
    return katex.renderToString("\\begin{aligned}" + inner + "\\end{aligned}", { displayMode: true });
})

利用 aligned 环境已经实现而 align* 环境尚未实现的事实。


针对您的编辑回复:

我认为使用带有回调函数的res.renderres.send的组合看起来不错。如果您自己调用模板渲染,就可以避免调用res.render,但这取决于您是否认为这是可取的。

没有了解输入情况,很难确定错误消息的原因。似乎您可能有一些以双重$$开头但结尾只有单个$的输入,所以第一个正则表达式无法匹配,并且第二个正则表达式在其匹配字符串中包含额外的$


谢谢!我正在使用 Handlebars。我通过 res.render('math') 渲染了我的模板,那么能否从这个模板中获取内容并通过您的代码片段运行并将其放回去呢? - Jamgreen
如果我在使用.replace(/\$\$(.*?)\$\$/g, function(outer, innner) {之后再使用.replace(/\$(.*?)\$/g, function(outer, innner) {,就会出现服务器错误。只使用一个$有什么问题? - Jamgreen
@Jamgreen:你在使用Express吗?它确实描述了res.render。你用的是哪个版本?无论如何,你可能不想调用res.render,而是直接调用模板引擎,然后使用res.end发送结果。单个$作为分隔符可能会有些棘手,因为a)它们可能出现在常规文本中,b)它们也可能出现在LaTeX代码中,例如在类似于x > x_0\text{ for all $x$ in $X$}的结构中。但是不应该有错误,所以如果需要更多帮助,您可能需要提供错误消息。 - MvG
在普通的JS中,.replace会是什么样子?它给了我一个"inner is not defined"的错误。 - Leonard Storcks

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

在许多情况下,这种方法不起作用,因为会出现以下情况:

enter image description here

多个公式被识别为一个,从而导致错误。
为了防止这种情况,请使用:
/\\\(((.)*?(?=\\\)))?\\\)/

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

然而,仍存在一些注意事项,例如某些内容无法正确呈现。另一种方法可能是 https://joa.sh/posts/2015-09-14-prerender-mathjax.html(这是我接下来要尝试的方法...)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接