关于 Markdown 与 Mathjax 的冲突问题及几个解决方案

文章目录

这篇文章本来是作为上一篇中 Mathjax 块的一部分的,但是由于越写越多,最后就决定直接单独拿出来了

Note:由于 Highlight.js 并没有对 TeX 的语法高亮,下面的一些 TeX 公式代码区块就没有指定语言……似乎时不时被识别成 Ruby 或者 C# 然后按照其高亮规则进行处理了……反正总之还是能看的只是高亮方式的依据并不是 TeX 的语法

Part 1. 冲突问题

由于 Markdown 本身并不滋瓷数学公式的解析,所以在其看来公式的语法块内部或者说整个公式都是一堆普通的文本,也就是说在解析成 HTML 代码的时候还是会把它们当做遵循 Markdown 语法的代码进行处理,这就导致了一些麻烦,比较常见的(其实是我目前已经碰到过的)是转义符问题和 TeX 公式中下标代码的解析问题

具体情况可以在 文章标题图片 中看到,实际上就是以下公式代码:

$$ \iint\limits_{D}\left(\frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y}\right)\,\mathrm{d}x\,\mathrm{d}y = \oint_{L^{+}}\left(P\mathrm{d}x + Q\mathrm{d}y\right) $$

在被 Markdown 解析后会显示成这样子:

$$ \iint\limits{D}\left(\frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y}\right),\mathrm{d}x,\mathrm{d}y = \oint{L^{+}}\left(P\mathrm{d}x + Q\mathrm{d}y\right) $$

查看这一块的 HTML 代码,是这样的:

<p>  
    $$ \iint\limits
    <em>
        {D}\left(\frac{\partial Q}{\partial x} - 
        \frac{\partial P}{\partial 
        y}\right),\mathrm{d}x,\mathrm{d}y = \oint
    </em>
    {L^{+}}\left(P\mathrm{d}x + Q\mathrm{d}y\right) $$
</p>  

不难发现问题所在:

  • 短空格即 \( \mathrm{d}x\,\mathrm{d}y \) 中间及之前的符号 \, 被转义显示成了 ,
  • 两个下标符号即 _ 之间的内容被解析到了 <em></em> 标签内,导致 Mathjax 无法搜索到这一对 $$,也就不会进行后续处理了

下面具体谈谈这两种问题

转义符问题

这个可以说是坠痛苦的了,如果是简单点的不带斜杠的公式倒还好,只要把带有斜杠的起始符号多加个转义符就行了,比如 \( 要写成 \\(,如果是稍微复杂一点的带有比较多可能导致转义的斜杠的公式,要一个个加上转义符就超级麻烦,例子就是上述加在微分符号之间的短空格 \,

不过考虑到很多字符都是斜杠加英文单词,并不会导致 Markdown 的转义处理,只有在 TeX 公式的语法里面本来就需要转义的符号才会有这种麻烦,而且我这种咸鱼应该不会碰得到这些复杂的公式,所以日常使用应该也不会有很大问题

下标符号解析问题

这是我在测试的时候尝试写矩阵时发现的,写完保存发现没解析出来……看了下网页源码发现有一部分被解析到 <em> 标签内,一开始还没反应过来是怎么萎事,因为无论是写 Markdown 还是 HTML 我都从来没用过这种东西(然而这篇文章开头就用到了 hhh)。仔细看看定位以后发现是下标符号即下划线 _ 之间的部分会被显示成强调,也就是解析到 <em></em>

这些问题也就会导致直接使用公式的 TeX 源码时,真正在页面上的内容并不一定和一开始粘贴上去的相同,这样不正确的显示也就造成了 Mathjax 无法正常进行解析甚至提取的情况

Part 2. 解决方案

那么解决方案其实就是要阻止 Markdown 对这些特殊字符进行解析了,或者至少要让解析后的文本和我们期待的相同……
我 xjb 试了下目前根据尝试思路和效果整理出以下三种解决方案,欢迎指出错误或者提供改进建议 Orz

Solution 1:向 Markdown 低头

这个方案的思路是迎合 Markdown 的解析或者根据其语法规则避开解析

对于转义符的问题,在有转义符的地方再加个转义符当然是可以达到效果的,相对特殊一点的情况就是矩阵的换行在 TeX 里是 \\ 所以在写 Markdown 的时候应该是 \\\\,这个其实应该也很容易想到 = =
上面也提过,实际上由于大部分的斜杠后面都跟的是一个单词,而这些并不会引起转义的问题,所以对于大部分的普通公式,这样子的修改也不会需要多少时间

然后下标符号解析的问题,根据 Markdown 的语法,_* 左边或右边有空格的情况下就会被视为是普通的下划线和星号,所以只要在左边多加个空格就可以了,也不影响 TeX 的解析,只是看起来其实还是会有些别扭的hhh

举个栗子,标题图片中的 格林公式

$$ \iint\limits_{D}\left(\frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y}\right)\,\mathrm{d}x\,\mathrm{d}y = \oint_{L^{+}}\left(P\mathrm{d}x + Q\mathrm{d}y\right) $$

处理后写成这样子即可(原来的形式见上文或右击公式区选择导出为 TeX Commands):

$$ \iint\limits _{D}\left(\frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y}\right)\\,\mathrm{d}x\\,\mathrm{d}y = \oint _{L^{+}}\left(P\mathrm{d}x + Q\mathrm{d}y\right) $$

再举个栗子,范德蒙矩阵

$$ V = { \begin{bmatrix} 1 & \alpha_{1} & \alpha_{1}^{2} & \dots & \alpha_{1}^{n-1} \\ 1 & \alpha_{2} & \alpha_{2}^{2} & \dots & \alpha_{2}^{n-1} \\ 1 & \alpha_{3} & \alpha_{3}^{2} & \dots & \alpha_{3}^{n-1} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 1 & \alpha_{m} & \alpha_{m}^{2} & \dots & \alpha_{m}^{n-1} \\ \end{bmatrix} } $$

TeX 的写法应该是:

$$
V = {  
\begin{bmatrix}
    1 & \alpha_{1} & \alpha_{1}^{2} & \dots & \alpha_{1}^{n-1} \\
    1 & \alpha_{2} & \alpha_{2}^{2} & \dots & \alpha_{2}^{n-1} \\
    1 & \alpha_{3} & \alpha_{3}^{2} & \dots & \alpha_{3}^{n-1} \\
    \vdots & \vdots & \vdots & \ddots & \vdots \\
    1 & \alpha_{m} & \alpha_{m}^{2} & \dots & \alpha_{m}^{n-1} \\
\end{bmatrix}
}
$$

按照这种做法处理之后就是:

$$
V = {  
\begin{bmatrix}
    1 & \alpha _{1} & \alpha _{1}^{2} & \dots & \alpha _{1}^{n-1} \\\\
    1 & \alpha _{2} & \alpha _{2}^{2} & \dots & \alpha _{2}^{n-1} \\\\
    1 & \alpha _{3} & \alpha _{3}^{2} & \dots & \alpha _{3}^{n-1} \\\\
    \vdots & \vdots & \vdots & \ddots & \vdots \\\\
    1 & \alpha _{m} & \alpha _{m}^{2} & \dots & \alpha _{m}^{n-1} \\\\
\end{bmatrix}
}
$$

在网页上显示的效果还是一样的,右键选项导出成 TeX 源码时也只是下划线前多了一个空格,其他的内容完全和原本的代码相同:

$$ V = {
\begin{bmatrix} 1 & \alpha _{1} & \alpha _{1}^{2} & \dots & \alpha _{1}^{n-1} \\ 1 & \alpha _{2} & \alpha _{2}^{2} & \dots & \alpha _{2}^{n-1} \\ 1 & \alpha _{3} & \alpha _{3}^{2} & \dots & \alpha _{3}^{n-1} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 1 & \alpha _{m} & \alpha _{m}^{2} & \dots & \alpha _{m}^{n-1} \\ \end{bmatrix} } $$

可以通过查看提供的预览确认转义是否正确完成,即预览中显示的应是预期的公式代码

Solution 2:让 Markdown 低头

这个方案的思路就是利用 Markdown 本身的语法使其对公式区块的所有符号都不做处理

Markdown 本身提供的一种不处理的语法就是代码或代码区块,即两个 ` 或两个 ``` 之间的字符

不过,Mathjax 默认对于代码区块内的内容是不做检查的,所以还要改一下配置,即加上配置信息

<script type="text/x-mathjax-config">  
MathJax.Hub.Config({  
  tex2jax: {
    skipTags: ["script","noscript","style","textarea"]
    //Default: ["script","noscript","style","textarea","pre","code"]
  }
});
</script>  

对于代码区块而言,内容是在 <pre><code> 标签对之内的,所以如果要对区块公式使用这种方法的话,应该把 <pre> 标签也去掉

但是如果使用这种方法的话,又带来了新的问题,那就是一般代码都是有单独的 CSS 的,尤其是在代码使用的背景色等重要内容和普通文本不同时候,这个公式的显示也会受到 CSS 的影响,如果对公式显示影响较大或者对此比较介意的话建议看这篇 MathJax 与 Markdown 的究极融合 中的处理方法,主要思路就是通过去除公式代码外的 <code> 标签来阻止相关 CSS 的影响
不过看代码似乎并没有阻止 <pre> 的 CSS 影响,具体情况我也没有测试过,如果有相关需求或者对此感兴趣的话可以自己尝试一下

Solution 3:强行让 Markdown 低头

这个方案的思路是通过使用 HTML 标签使 Markdown 无法对公式代码区块中的内容进行转义

Markdown 本身似乎就是为了提供一种便于日常非网页类的书写方式和阅读文本,很多的 parser (应该可以说全部吧?) 都是允许使用 HTML 标签的,只是一般而言从优雅的角度来说很少会去使用

比如以下代码便会完全正常存储而不进行任何处理:

<div class="example">  
    Hello World! Test Formula: \( f(x,y) = ax + by + c = 0  \)
</div>  
<style>  
    .example {
        margin: 1.75em 0 1.75em 0;
        padding: 0 0 0 1.75em;
        border-left: #2e9fff 0.4em solid;
        background-color: #ebeef2;
    }
</style>  

然后在页面加载时,浏览器按照 HTML 处理,CSS 也会起作用,效果如下:

Hello World! Test Formula: \( f(x,y) = ax + by + c = 0 \)

当然这次处理的问题肯定是不需要写 CSS 什么的我只是突然写上了瘾(逃

对于 Ghost 而言,每个段落都会解析在 <p><\p> 之间,所以其实这个解决方案就是直接把公式放在 <p></p> 内,比如格林公式在这种方案下编辑时就应该是这样子的:

<p>  
$$ \iint\limits_{D}\left(\frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y}\right)\,\mathrm{d}x\,\mathrm{d}y = \oint_{L^{+}}\left(P\mathrm{d}x + Q\mathrm{d}y\right) $$
</p>  

公式分隔符及其内部不用做任何更改,即另一种带斜杠的分隔符也不需要加转义符:

<p>  
\[ \iint\limits_{D}\left(\frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y}\right)\,\mathrm{d}x\,\mathrm{d}y = \oint_{L^{+}}\left(P\mathrm{d}x + Q\mathrm{d}y\right) \]
</p>  

但是有个微小的问题,就是经过测试发现 <span></span> 之间的内容还是会被处理,所以行内公式可能就用不上这个方法了

但是其实这三种方法严格来讲应该也都是为区块公式准备的,毕竟碰得到这些问题的本身应该也是相对复杂点的公式,应该不会作为行内公式编辑,所以影响也不大

Part 3. 方案评价和比较

第一种方案很显然是不利于代码移植的,下标符号加上空格虽然不好看但是对 TeX 的解析还是没有影响的,但是转义符的问题就使得编辑时的导入导出都不是很方便了(当然从文章显示时的公式上右键导出成 TeX 命令还是没问题的,因为那已经是转义之后的代码)

而第二种方案操作上就不那么容易,不过对编辑而言方便很多,做好了相关的处理工作在编辑的时候便只需要使用反引号将公式代码包括进去

就我个人而言,我比较喜欢也更倾向于推荐第三种方式,因为它操作最简单、移植最方便,除了加上一对 <p> 之外不用再做任何处理,而且导入和导出时格式都完全相同
只是看个人爱好是否觉得在 Markdown 里面使用 HTML 标签不太优雅,要说在这个基础上更优雅一点的方案那无疑是第二种了

说了这么多其实对我个人来说应该没太大的影响的hhh,毕竟本来平常写公式就不多,更别说这些情况下的公式
写这篇文章的原因当然还是因为个人性格和爱好,在发现这类问题的时候还是很希望弄清楚原因然后找出解决方案的

还有一句题外话……其实 21 号写好前面那片文章以后,23 号就已经基本完成了这篇包括所有的测试在内的准备工作,不过后来咸鱼了好久好久,然后下午才开始编辑最后的内容、做标题图,这也是为什么本来属于前文的一部分的内容竟然过了 10 天才写完发布,这段时间内养生,简直美滋滋