当前位置: 首页>>技术问答>>正文


WordPress中解决Markdown和Latex冲突问题

丹阳 技术问答, 系统&架构 , , 1条评论

本文由纯净的天空原创, 未经允许, 请勿转载。

想在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后台直接安装。
  1. Markdown on Save Improved简洁可用,且在Wordpress中能兼容之前的非Markdown文章。
  2. Latex for WordPress插件依赖的Javascript等资源访问比较慢,我们可以整个拷贝到Wordpress后台,然后做如下设置:

WordpressLatex设置

第二步: 安装完上述插件之后,就可以成功使用Markdown了(注意禁用其他文本编辑插件),但是Latex公式有些问题。
  1. 分析Markdown on Save Improved的代码可以发现,在markdown处理过程中,依次有stripslashes/transform/addslashes操作,这个会将Latex公式中的反斜杠"\"去掉,而这个正是Latex最常用的转义符号。

    Markdown on Save Improved: Process函数

  2. 另外就是Markdown语法跟Latex语法冲突的问题,比如下划线"_"。

  3. 针对上述两个问题,我们的解决方案是:先将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;
    }
本文由《纯净的天空》出品。文章地址: https://vimsky.com/article/2383.html,未经允许,请勿转载。

一条评论

  1. qingchuan 2017年3月19日 23:21

    注意,如果非Latex代码中出现了成对出现的美元符号'$$',那么会被误判为Latex代码,不过这种情况应该极少见。