LaTeX 错误渲染了 PHP 代码,wordpress插件解决方案
Contents
1. 问题描述
在自己的 wordpress 网站记录一篇 PHP 相关的文章时,遇到了一个网页的渲染问题:PHP 的变量符号 $ 与 markdown 插件的 LaTeX 公式渲染标识符 $....$ 冲突了,这导致正文以及代码块中的一些 PHP 函数被错误解析为数学公式。
例如:PHP 函数
function update( $new_instance, $old_instance )在正文中被错误解析为 function update( new_instance,old_instance )。
这个问题在大部分支持良好的 markdown 解释器下都不是很严重的问题,但是由于 wordpress 并不原生支持 markdown, 我是通过插件 WP Editor.md 实现的 markdown 支持, 而这个插件的 LaTeX 识别会导致这个现象(github 上也有人反应相关 issue)。
由于这个插件已经是比较好的支持中文的 markdown 插件了,所以并不准备更换, 最后从下文的三种解决方法中选择了第一种方式。
2. Simple Mathjax 插件解决
我们可以禁用 WP Editor.md 的 LaTeX 渲染功能,诉诸于别的更专长的数学公式插件。
我选择的是 Simple MathJax。
其原理就是帮助你在生成的 HTML 文件中调取 MathJax 库,并且通过特定的标识符号检测出需要渲染的 LaTeX 代码,最终生成可以被浏览器渲染的 HTML 文本。
MathJax 是一个用于在网页中显示数学公式的 JavaScript 库
然而禁用 WP Editor.md 的 LaTeX 功能,直接更换并启用 Simple MathJax 插件后,虽然代码块内的 PHP 代码可以被正常规避公式检测成功以代码形式渲染了,正文中形如 function update( $new_instance, $old_instance ) 的函数仍然会被错误识别的数学公式。我们需要进一步的修改 Simple MathJax 插件。
2.1 修改 Simple MathJax 公式检测代码
进入插件修改页面
通过下图红框中的入口,进入插件的编辑器页面,在这里我们就可以修改插件的源码了。
注意:在修改插件的代码前,最好先禁用相关插件以防出现无法预测的错误。

SimpleMathJax 公式检测逻辑
插件原始采用的是最经典的检测方案,既同时允许两种公式标识符:
$....$,$$....$$
最常见的 LaTeX 的$系标识符号:以$....$标识行内公式,以$$....$$标识行外公式。\(....\),\[....\]
更加不容易冲突的\(系标识符号:以\(....\)标识行内公式,以\[....\]标识行外公式。
显然第一种检测方案就是渲染冲突的问题所在,我们需要在插件源代码中将其取消掉,只使用不容易冲突的 \( 系标识符号。
代码修改
通过阅读我们发现插件显式的定义了行内公式 inlineMath 属性,定义部分的代码如下:
/*
* Default MathJax configuration scripts, for each major version.
*/
public static $default_configs = array(
2 => "MathJax.Hub.Config({\n tex2jax: {\n inlineMath: [['$','$'], ['\\\\(','\\\\)']],\n processEscapes: true,\n ignoreHtmlClass: 'tex2jax_ignore|editor-rich-text'\n }\n});\n",
3 => "MathJax = {\n tex: {\n inlineMath: [['$','$'],['\\\\(','\\\\)']], \n processEscapes: true\n },\n options: {\n ignoreHtmlClass: 'tex2jax_ignore|editor-rich-text'\n }\n};\n",
4 => "MathJax = {\n tex: {\n inlineMath: [['$','$'],['\\\\(','\\\\)']], \n processEscapes: true\n },\n options: {\n ignoreHtmlClass: 'tex2jax_ignore|editor-rich-text'\n }\n};\n"
);
其中 ['$','$'] 代表以 $....$ 标识行内公式,['\\\\(','\\\\)'] 代表以 \(....\) 标识行内公式。
为什么是 \\\\(: 这里有两层转义:
- 在 PHP 字符串中,一个反斜杠
\是转义符。为了在字符串里得到一个字面上的\,你需要写成\\。所以\\\\(在 PHP 内存中变成了字符串\\ (。 - 当这个字符串
\\ (被输出到前端并由 JavaScript 读取时,JavaScript 也视\为转义符。它会将\\解析为字面上的\,将\\(解析为字面上的\(。
最终效果:在 MathJax 实际工作时,它收到的定界符就是 \( 和 \)。
于是为了最终解决 $ 系标识符号行内公式标识符导致的渲染混乱,我们吧代码中的相关部分删除并更新插件即可,最终形如:
/*
* Default MathJax configuration scripts, for each major version.
*/
public static $default_configs = array(
2 => "MathJax.Hub.Config({\n tex2jax: {\n inlineMath: [['\\\\(','\\\\)']],\n processEscapes: true,\n ignoreHtmlClass: 'tex2jax_ignore|editor-rich-text'\n }\n});\n",
3 => "MathJax = {\n tex: {\n inlineMath: [['\\\\(','\\\\)']], \n processEscapes: true\n },\n options: {\n ignoreHtmlClass: 'tex2jax_ignore|editor-rich-text'\n }\n};\n",
4 => "MathJax = {\n tex: {\n inlineMath: [['\\\\(','\\\\)']], \n processEscapes: true\n },\n options: {\n ignoreHtmlClass: 'tex2jax_ignore|editor-rich-text'\n }\n};\n"
);
注意:我们只修改了行内公式的 $....$ 标识符,行外标识符 $$....$$ 仍然有效。
2.2 Simple MathJax 使用例
*行内公式:*
\\(K_k = P_{k|k-1} H_k^T (H_k P_{k|k-1} H_k^T + R_k)^{-1}\\)
*行外公式:*
\\[K_k = P_{k|k-1} H_k^T (H_k P_{k|k-1} H_k^T + R_k)^{-1}\\]
显示效果
行内公式:
K_k = P_{k|k-1} H_k^T (H_k P_{k|k-1} H_k^T + R_k)^{-1}
行外公式:
K_k = P_{k|k-1} H_k^T (H_k P_{k|k-1} H_k^T + R_k)^{-1}
3. 内嵌 HTML 代码解决
禁用 WP Editor.md 的 LaTeX 渲染功能后,如果也不想引入更多的插件的话,还有一种适合最多平台的情况,那就是直接使用 HTML 文本来进行公式渲染。
可查阅并参考文档MathML | MDN
<!-- 在 head 内部引用需要的解释脚本可以使得渲染更加漂亮 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<!-- 不引用也可以直接输出有字体的公式 -->
<link rel="stylesheet" href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
<math xmlns="http://www.w3.org/1998/Math/MathML"><msub><mi>K</mi><mi>k</mi></msub><mo>=</mo><msub><mi>P</mi><mrow><mi>k</mi><mrow><mo stretchy="false">|</mo></mrow><mi>k</mi><mo>−</mo><mn>1</mn></mrow></msub><msubsup><mi>H</mi><mi>k</mi><mi>T</mi></msubsup><mo stretchy="false">(</mo><msub><mi>H</mi><mi>k</mi></msub><msub><mi>P</mi><mrow><mi>k</mi><mrow><mo stretchy="false">|</mo></mrow><mi>k</mi><mo>−</mo><mn>1</mn></mrow></msub><msubsup><mi>H</mi><mi>k</mi><mi>T</mi></msubsup><mo>+</mo><msub><mi>R</mi><mi>k</mi></msub><msup><mo stretchy="false">)</mo><mrow><mo>−</mo><mn>1</mn></mrow></msup></math>
<link rel="stylesheet" href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><msub><mi>K</mi><mi>k</mi></msub><mo>=</mo><msub><mi>P</mi><mrow><mi>k</mi><mrow><mo stretchy="false">|</mo></mrow><mi>k</mi><mo>−</mo><mn>1</mn></mrow></msub><msubsup><mi>H</mi><mi>k</mi><mi>T</mi></msubsup><mo stretchy="false">(</mo><msub><mi>H</mi><mi>k</mi></msub><msub><mi>P</mi><mrow><mi>k</mi><mrow><mo stretchy="false">|</mo></mrow><mi>k</mi><mo>−</mo><mn>1</mn></mrow></msub><msubsup><mi>H</mi><mi>k</mi><mi>T</mi></msubsup><mo>+</mo><msub><mi>R</mi><mi>k</mi></msub><msup><mo stretchy="false">)</mo><mrow><mo>−</mo><mn>1</mn></mrow></msup></math>
显示效果
4. 修改 WP Editor.md 源代码
我们也可以完整的修改插件的相关代码,从而更优雅的解决这个问题。
首先定位插件和 LaTeX 渲染相关的代码位置在 wp-editormd/src/App/KaTeX.php 。
阅读原始代码后发现,负责检测公式标识符的函数 katex_markup_single() katex_markup_double(),都允许 $ 系标识符和内部 LaTeX 代码之间存在空格,即:
$y=f(x)$和$y=f(x) $都可以被正确解析为 y=f(x)
$$....$$的情况相同
而很多 LaTeX 渲染脚本都不允许这种情况存在,也就避免了上文中形如函数 function update( $new_instance, $old_instance ) 被错误解析为 function update( new_instance,old_instance ) 的情况。
于是我们仅仅需要为源代码加上
不允许标识符与 LaTeX 公式代码之间存在空格
的逻辑即可。
更新后的 KaTeX.php 已上传至 Github: https://github.com/lichenrobo/WP-Editor.md/ 。
4.1 代码解析
其中最核心的就是一个统一的正则表达式,分析如下:
/**
* 一个用于捕获行外和行内数学公式的统一正则表达式。
* 使用 OR '|' 操作符,并优先尝试匹配更长的 '$$' 标识符。
* 这可以防止 '$$' 被错误地解析为两个独立的 '$' 。
*
* 正则表达式分解:
* 捕获组 1: 匹配整个 $$...$$ 块。
* ->捕获组 2: 捕获 $$...$$ 内部的实际 LaTeX 内容。
* 捕获组 3: 匹配整个 $...$ 块。
* ->捕获组 4: 捕获 $...$ 内部的实际 LaTeX 内容。
*/
$regex = '/
( # 捕获组 1: 捕获 $$...$$ 块
\$\$ # 以 $$ 开始
( # 捕获组 2: 捕获内容
(?:
[^$]+ # 匹配任何非美元符号的字符
| # 或
\$ (?<! \$\$ ) # 匹配一个美元符号,前提是它前面不是另一个美元符号
)+? # 一次或多次非贪婪匹配
)
\$\$ # 以 $$ 结束
)
| # 或
( # 捕获组 3: 捕获 $...$ 块
\$ # 以 $ 开始
( # 捕获组 4: 捕获内容
(?:
[^$]+ # 匹配任何非美元符号的字符
| # 或
\$ (?<! \$\$ ) # 匹配一个美元符号,前提是它前面不是另一个美元符号
)+? # 一次或多次非贪婪匹配
)
\$ # 以 $ 结束
)
/ix';
其余代码及注释参考 github 仓库。
4.2 WP Editor.md 使用例
*行内公式:*
$K_k = P_{k|k-1} H_k^T (H_k P_{k|k-1} H_k^T + R_k)^{-1}$
*行外公式:*
$$K_k = P_{k|k-1} H_k^T (H_k P_{k|k-1} H_k^T + R_k)^{-1}$$
显示效果
行内公式:
K_k = P_{k|k-1} H_k^T (H_k P_{k|k-1} H_k^T + R_k)^{-1}
行外公式:
K_k = P_{k|k-1} H_k^T (H_k P_{k|k-1} H_k^T + R_k)^{-1}