本文由纯净的天空原创, 未经允许, 请勿转载。
想在Wordpress中使用Markdown+Latex,如何设置?同时使用Markdown插件+Latex插件不就OK了吗?
理想很完美,现实却很骨感。之前baidu+google搜索查看了很多国内外相关资料,尝试了各种插件或者修改Wordpress后台代码,方案都或多或少有些问题,这里亲测实践出了一套可行方案。
为什么现有的方案有问题?
先说说直接Jetpack插件
Jetpack本身是可以直接支持Markdown+Latex的,网上有很多人说要用这个。如果是在国外,这个插件应该很好很强大,但是放到国内,问题就来了。首先网速就是个不可逾越的障碍,因为Jetpack的激活需要连接到国外云服务器,注册+日常操作经常会跟服务器交互,目前的速度累觉不爱。其次是,jetpack是很多的插件大集合,大部分功能我们的Wordpress网站并不一定需要,没必要浪费资源。
独立Markdown插件+Latex(MathJax)
WordPress中是Markdown先处理,然后再通过MathJax展示公式(JS或图片两种模式)。问题在于, Markdown语法跟Latex语法是有冲突的,比如:
下划线”_”在Markdown中表示斜体,而在Latex中表示下标。Markdown先处理时”_”会被转为为<em>
或者<i>
标签,这样等到Mathjax处理时,公式就乱了。
实践可行的方案
第一步: 安装Markdown on Save Improved插件, 再安装LaTeX for WordPress插件,这两个插件都可以在Wordpress后台直接安装。
- Markdown on Save Improved简洁可用,且在Wordpress中能兼容之前的非Markdown文章。
- Latex for WordPress插件依赖的Javascript等资源访问比较慢,我们可以整个拷贝到Wordpress后台,然后做如下设置:
第二步: 安装完上述插件之后,就可以成功使用Markdown了(注意禁用其他文本编辑插件),但是Latex公式有些问题。
-
分析Markdown on Save Improved的代码可以发现,在markdown处理过程中,依次有stripslashes/transform/addslashes操作,这个会将Latex公式中的反斜杠”\”去掉,而这个正是Latex最常用的转义符号。
-
另外就是Markdown语法跟Latex语法冲突的问题,比如下划线”_”。
- 针对上述两个问题,我们的解决方案是:先将Latex代码段替换成某个特殊的字符串,等Markdown处理完再替换回来。具体操作为:将markdown-on-save.php中的函数
process
修改为下列函数。
protected function process( $content, $id ){
return $this->processMarkdownWithLatex($content, $id);
/** 老代码, markdown跟latex有冲突,所以重新实现了一个
$this->maybe_load_markdown();
// $content is slashed, but Markdown parser hates it precious.
$content = stripslashes( $content );
// convert to Markdown
$content = $this->parser->transform( $content );
// reference the post_id to make footnote ids unique
$content = preg_replace( '/fn(ref)?:/', "fn$1-$id:", $content );
// WordPress expects slashed data. Put needed ones back.
$content = addslashes( $content );
return $content;*/
}
protected function processMarkdownWithLatex( $content, $id ) {
$this->maybe_load_markdown();
//获取latex代码段
$latexSegments = $this->getLatexSegments($content);
//在markdown转换前,替换掉latex代码
$newContent = $content;
if($latexSegments){
$newContent = $this->replaceLatexSegments($content, $latexSegments);
}
// $content is slashed, but Markdown parser hates it precious.
$newContent = stripslashes( $newContent );
// convert to Markdown
$newContent = $this->parser->transform( $newContent );
// reference the post_id to make footnote ids unique
$newContent = preg_replace( '/fn(ref)?:/', "fn$1-$id:", $newContent );
// WordPress expects slashed data. Put needed ones back.
$newContent = addslashes( $newContent );
//将Latex代码替换回来
$content = $newContent;
if($latexSegments){
$content = $this->recoverLatexSegments($newContent, $latexSegments);
}
return $content;
}
上述框架代码依赖的相关函数实现如下:
/**
@ 依次替换一个字符串中的多个子串, 待替换子串用位置+长度指定
*/
protected function substr_replace_multi($inputStr, $replacementArr, $startArr, $lenArr){
$retStr = "";
$lastIdx = 0;
$numReplacement = count($replacementArr);
foreach($replacementArr as $idx => $replacement){
$start = $startArr[$idx];
$len = $lenArr[$idx];
$retStr .= substr($inputStr, $lastIdx, $start - $lastIdx);
$retStr .= $replacement;
$lastIdx = $start + $len;
}
if($numReplacement > 0){
$retStr .= substr($inputStr, $startArr[$numReplacement - 1] + $lenArr[$numReplacement - 1]);
}
else if($numReplacement == 0){
$retStr = $inputStr;
}
return $retStr;
}
/**
@ 依次替换一个字符串中的多个子串
*/
protected function str_replace_multi($inputStr, $searchArr, $replacementArr){
$newReplacementArr = array();
$posArr = array();
$lenArr = array();
$offset = 0;
foreach($searchArr as $idx => $search){
$pos = strpos($inputStr, $search, $offset);
if($pos !== false){
$replacement = $replacementArr[$idx];
$newReplacementArr[] = $replacement;
$len = strlen($search);
$posArr[] = $pos;
$lenArr[] = $len;
$offset = $pos + $len;
}
}
return $this->substr_replace_multi($inputStr, $newReplacementArr, $posArr, $lenArr);
}
//正则找到所有代码段
protected function getLatexSegments($content){
$matches = null;
//匹配双美元符号'$$'开始和结束的代码段。
$numMatch = preg_match_all('/(^| )\$\$(.{1,1000}?)\$\$/sm', $content, $matches, $flags = PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE);
if($numMatch){
return $matches[0];
}
return false;
}
//将latex代码段替换为特殊字符串||@...@||
protected function replaceLatexSegments($content, $matches, $magicSeparator = "@"){
$newContent = $content;
$posArr = array();
$lenArr = array();
$replacementArr = array();
foreach($matches as $idx => $match){
$latexSegment = $match[0];
$pos = $match[1];
$len = strlen($latexSegment);
$replacement = "||".str_repeat($magicSeparator, $idx + 1)."||";
$replacementArr[] = $replacement;
$posArr[] = $pos;
$lenArr[] = $len;
}
$newContent = $this->substr_replace_multi($content, $replacementArr, $posArr, $lenArr);
return $newContent;
}
//将特殊字符串替换为latex代码段
protected function recoverLatexSegments($content, $matches, $magicSeparator = "@"){
$searchArr = array();
$replacementArr = array();
foreach($matches as $idx => $match){
$latexSegment = $match[0];
$pos = $match[1];
$len = strlen($latexSegment);
$replacement = $latexSegment;
$search = "||".str_repeat($magicSeparator, $idx + 1)."||";
$searchArr[] = $search;
$replacementArr[] = $replacement;
}
$newContent = $this->str_replace_multi($content, $searchArr, $replacementArr);
return $newContent;
}