给基于 wxParse 的 WordPress 版小程序添加代码高亮

最近一直在搞小程序中的代码高亮,测试了 towxml、richText 和 html2json 插件都不太满意,没办法,自己摸索吧。最终我选择了基于 wxParse 和 Prism 来实现代码高亮,简单的思路就是通过判断当前标签是否是“pre”,如果是,就将“pre”标签中的代码段用 Prism 的 API 来实现高亮,再将高亮后的内容返回,最后都交给 wxParse 来解析 html。有了思路,下面就开始干!

一、准备工作

在我之前写的文章中(解决 WordPress 微信小程序文章内代码显示、图片非 CDN 地址、图片附带链接的问题  解决 WordPress 版小程序文章内容中的“<”和“>”字符显示问题)介绍了如何显示代码段和 HTML 转义字符解析的问题,这次的代码高亮也要先从这两点入手。

1、代码段的标签

在 WordPress 中,我用的是自己改写过的 HighlightJs 插件,因此代码段的标签是这样的:

<pre class="pure-highlightjs"><code class="javascript">此处是代码内容
</code></pre>

这是两层标签,在之前那篇讲代码显示的文章中,我将代码段的这两层标签替换为一层“code”标签,这里我不用“code”改用“pre”。为什么呢?“code”标签有时候我会用来标记一些简短的代码内容,而“pre”则用来标记代码段。

怎么替换两层标签为一个“pre”呢?还是用之前那篇文章中讲到的“replace”大法。就是在“detail.js”中修改传给 wxParse 的内容:

    fetchDetailData: function (id) {
        var self = this;
        wx.showLoading({
          title: '正在加载文章',
          mask: true
        });
        var getPostDetailRequest = wxRequest.getRequest(Api.getPostByID(id));
        var res;
        var _displayLike = 'none';
        getPostDetailRequest
            .then(response => {
                res = response;
                
                WxParse.wxParse('article', 'html', response.data.content.rendered.replace(/<pre[^>]*><code class=\"([^\s]*)\">/g, "<pre class=\"pure-highlightjs $1\">").replace(/<\/code><\/pre>/g, "</pre>"), self, 5);

可以看到最后一行的两个“replace()”函数就将 ‘<pre class="pure-highlightjs"><code class="javascript">‘ 替换为了 ‘<pre class="pure-highlightjs javascript">‘ ,也就是代码语言的定义放到了“pre”标签的 class 中,这个之后会用到。而那个“pure-highlightjs”也是为了区分代码端和普通“pre”标签所添加的 class。

2、代码换行问题

在之前的文章中,为了解决代码段换行问题,我将代码段中的“\r\n”换成了“<p></p>”,在用了 Prism 代码高亮后肯定不能再这样替换了,并且使用了 Prism 后代码段中的换行才能让代码正常显示,但是呢,wxParse 默认是替换掉所有的换行,包括“\n”和“\r\n”。经过观察 WordPress 文章源代码后发现除了代码段,文章其余的换行都是“\n”,因此,对 wxParse 替换掉换行的代码进行修改(下面的代码在 wxDiscode.js 文件的 strMoreDiscode 函数中):

function strMoreDiscode(str){
    //str = str.replace(/\r\n/g,"");  
    str = str.replace(/([^\r])\n/g,'$1');

    //str = str.replace(/code/g,"wxxxcode-style");
    return str;
}

经过上面的修改,在进行替换时,会将“\n”替换掉而“\r\n”并不会,这样就保留了代码段中的换行。

3、HTML 转义字符

在之前的文章中说到了如何解决文章中“<”、“>”和“&”字符的显示问题,这里我用的方法还是那个,先将 wxDiscode.js 中的

str = str.replace(/&lt;/g, '<');
str = str.replace(/&gt;/g, '>');
str = str.replace(/&amp;/g, '&');

注释掉,然后在 html2json.js 中的 transEmojiStr() 函数中添加这三个字符的替换:

function transEmojiStr(str){
  // var eReg = new RegExp("["+__reg+' '+"]");
  //修复 < > 的显示问题
  str = str.replace(/&lt;/g, '<');
  str = str.replace(/&gt;/g, '>');
  str = str.replace(/&amp;/g, '&');
4、/* */注释不显示问题

如果文章内容中含有“/*  */” 的注释,wxParse 在解析时会替换掉,解决的办法就是在 html2json.js 中注释掉这个替换语句:

function trimHtml(html) {
  return html
        //.replace(/\n+/g, '')
        .replace(/<!--.*?-->/ig, '')
        // 替换掉 /* */
        //.replace(/\/\*.*?\*\//ig, '')
        .replace(/[ ]+</ig, '<')
}

即上面代码的第6行。

5、代码段注释的背景色问题

在代码高亮了后,注释部分的背景色有点问题,在查看了元素的属性后发现是 detail.wxss 中有一个 :

.comment {
  /* border-bottom: 8px solid #fff; */
  background-color: #f5f7f7; 
  padding: 0 24rpx;
  border-radius: 8px;
  margin: 8px 0;
}

原来这个样式是为了显示评论的,但是代码高亮后的注释也是用的这个 class,就导致了冲突。解决办法就会注释掉上面的“background-color: #f5f7f7;”。

基本上没啥别的小问题了,接下来就是开始处理代码高亮的部分了。

二、代码高亮

1、Prism

首先要去 Prism 的官网下载相应的 js 和 css 文件,可以根据自己的需要来选择支持的代码语言和代码高亮的样式(Plugins 可以不选),官网下载页面点这里

将下载下来的 prism.js 和 prism.css(重命名为 prism.wxss)添加到小程序的代码路径下,添加到哪里都行,只要在相应的文件中正确引用就行。我这里以添加到“/utils/prism.wxss” 和 “/utils/prism.js” 为例,如果你添加的路径和我的不同,下面代码中关于这两个的路径一定记得做更改!

对了,还有就是 prism.wxss 中要做一些更改,把“pre”换成“.wxParse-pre”或者“.pure-highlightjs”,注意,后者是适合我的代码段结构的,建议自定义。为什么呢?在 HTML 中,我们是用 “<pre>” 标签来标识代码,而在小程序中,经过 wxparse 的解析后,“<pre>” 变为了 “class=wxParse-pre”,即 “<pre>” 标签元素在解析后变为了 class=“wxParse-pre” 元素,所以 prism.css 中的选择器也要做相应的更改(元素选择变为 class 选择),例如:

/* 原来的 */
pre[class*="language-"] {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
}

要修改为:

/* 修改后的,也可以是 .wxParse-pre */
.pure-highlightjs {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
}

也就是把出现的“pre”换为“.wxParse-pre”或者“.pure-highlightjs”。

2、添加 highlight.js

这个 highlight.js 就是处理代码段高亮的核心部分了。当然一些代码是参考“richText”的解析来写的。将下面的代码直接复制为“highlight.js”并添加到小程序的“/wxParse/highlight.js”。内容含有过多的注释,如不需要可以在添加时删去。

var Prism = require('../utils/prism.js'); // 引入 prism.js ,注意改为自己的路径

function highlight(data) {

  // 用来测试代码执行的时间
  // let beginTime = new Date().getTime();
  // console.log('开始时间:'+beginData);

  let nameArr = ['pre'];
  // Prism 所支持的代码语言数组
  let langArr = new Array();
  langArr = listLanguages(); // 获取 PrismJs 中的代码语言
  // console.log("langArr: "+ langArr);

  // 为了防止代码段中的换行被 wxDiscode 给替换掉,不能使用 str = str.replace(/\n/g,""); 语句,要使用 str = str.replace(/([^\r])\n/g,'$1');。因为在我的代码段中换行都是 /r/n,而文章其他部分都是 /n。我这里注释掉是因为 wxDiscode 中已经添加了 str = str.replace(/([^\r])\n/g,'$1');
  // data = data.replace(/([^\r])\n/g, '$1');

  let html = data; // html 是代码高亮后的 html 内容,小写的 html 对应着下面的“替换”方法用到的变量
  // let HTML = ''; // 这里是整合代码高亮方法二用到的变量,用的是字符串相加方法。
  
  //匹配到的所有标签<\/pre>
  let tagArr = data.match(/<\/?pre[^>]*>/g);
  // 如果没有 pre 标签就直接返回原来的内容,不做代码高亮处理
  if (tagArr == null) {
    return html;
  }
  //记录每一个 pre 标签在data中的索引位置
  let indexArr = [];
  //计算索引位置
  for (let i = 0; i < tagArr.length; i++) {
    //添加索引值
    if (i == 0) {
      indexArr.push(data.indexOf(tagArr[i]));
    }
    else {
      indexArr.push(data.indexOf(tagArr[i], indexArr[i - 1]));
    }
  }
  // console.log("indexArr: "+indexArr);
  //记录基本的class信息
  let cls;

  // 开始循环处理 pre 标签
  let i = 0;
  while (i < tagArr.length - 1) { // 这里减一是因为不处理最后的 </pre> 标签
    // 调用函数来获取 class 信息
    getStartInfo(tagArr[i])
    // 获取标签的值
    var label = tagArr[i].match(/<*([^> ]*)/)[1];
    // console.log("label: "+label);
    //相应的一些判断
    if (tagArr[i + 1] === '</' + label + '>') { //判断紧跟它的下一个标签是否为它的闭合标签
      // console.log("cls: "+cls);
      if (label === 'pre' && cls.split(' ').indexOf('pure-highlightjs') >= 0) { // 这里的条件判断,第二个要根据自己的代码段情况来自定义。我的代码段 pre 都有一个 class 是 pure-highlightjs
        // 代码语言判断,根据类进行判断,自定义,比如 lang-语言,language-语言。
        // 取第二个 class 的值,根据自己的代码段情况自定义。我的代码语言值是 class 的第二个值
        let lang = cls.split(' ')[1]; // [1] 代表第二个
        if (/lang-(.*)/i.test(lang)) { // 代码语言定义是 lang-XXX 的样式
          lang = lang.replace(/lang-(.*)/i,'$1');
        }
        else if (/languages?-(.*)/i.test(lang)) {
          lang = lang.replace(/languages?-(.*)/i, '$1'); // 代码语言定义是 language(s)-XXX 的样式
        }
        // console.log("lang: "+ lang);
        if (langArr.indexOf(lang) == -1 || lang == null || lang == 'none' || lang == 'null') { // 如果代码语言不在 Prism 存在的语言,或者 class 值是 null,则不执行代码高亮函数
          // 下面的这些注释掉的是方法二,将原来的内容中普通文本和代码段连接起来
          // if (i == 0) {
          //   HTML = HTML + data.substring(0, indexArr[i+1]);
          // }
          // else if (i == (tagArr.length - 2)) {
          //   HTML = HTML + data.substring(indexArr[i - 1]);
          // }
          // else {
          //   HTML = HTML + data.substring(indexArr[i - 1], indexArr[i+1]);
          // }
        }
        else {
          console.log("lang: " + lang);
          // 获取代码段内容为 code
          let code = data.substring(indexArr[i], indexArr[i + 1]).replace(/<pre[^>]*>/, '');

          // console.log('code: ' + code);
          // 执行 Prism 的代码高亮函数,先做一个替换,将转义后的 < > & 恢复
          let hcode = Prism.highlight(escape2Html(code), Prism.languages[lang], lang);
          // console.log("hcode: "+ hcode);
          // 下面同样是方法二
          // if (i == 0) { // 如果是第一个代码段,将代码段前面的内容和高亮后的内容相加
          //   HTML = HTML + data.substring(0,indexArr[i]) + '<pre class="pure-highlightjs ' + lang + '">' + hcode;
          // }
          // else if (i == (tagArr.length - 2) ) { // 如果是最后一个代码段,将上一个</pre>到这个<pre>中的内容和高亮后的内容和代码段后面的内容相加
          //   HTML = HTML + data.substring(indexArr[i-1], indexArr[i]) + '<pre class="pure-highlightjs ' + lang + '">' + hcode + data.substring(indexArr[i+1]);
          // }
          // else { // 将上一个</pre>到这个<pre>中的内容和高亮后的内容相加
          //   HTML = HTML + data.substring(indexArr[i-1], indexArr[i]) + '<pre class="pure-highlightjs ' + lang + '">' + hcode;
          // }
          // 这里是方法一,直接替换原来未被高亮的代码为高亮后的代码段。相比方法一可能会比较耗时,但是简单。
          html = html.replace(code, hcode);
          // console.log('每一个html: '+html);
        }

      }
      // 指向下一个标签(闭合标签)索引
      i++;
    }
    // 指向下一个标签(开始标签)的索引
    i++;
  }

  // console.log("html: "+html);
  // 测试执行时间
  // let endTime = new Date().getTime();
  // let executeTime = endTime - beginTime;
  // console.log('高亮执行时间: ' + executeTime + '毫秒');

  // 如果用的是方法二就将下面的 html 改为 HTML
  return html;

  //获取基本信息
  function getStartInfo(str) {
    //取得一些基本信息
    cls = matchRule(str, 'class');
  }

  //获取部分属性的值
  function matchRule(str, rule) {
    let value = '';
    let re = new RegExp(rule + '=[\'"]?([^\'"]*)');
    if (str.match(re) !== null) {
      value = str.match(re)[1];
    }
    return value;
  }
  //检查是否为支持的标签
  function checkName(str) {
    let name = 'div';
    for (let i = 0; i < nameArr.length; i++) {
      if (str === nameArr[i]) {
        name = str;
        break;
      }
    }
    return name;
  }

  //html字符转换 // 注意,顺序不能错
  function escape2Html(str) {
    str = str.replace(/&lt;/g, '<');
    str = str.replace(/&gt;/g, '>');
    str = str.replace(/&amp;/g, '&');
    return str;
  }

  // 列出当前 Prism.js 中已有的代码语言,可以自己在 Prism 的下载页面选择更多的语言。
  function listLanguages() {
    var langs = new Array();
    let i = 0;
    for (let language in Prism.languages) {
      if (Object.prototype.toString.call(Prism.languages[language]) !== '[object Function]') {
        langs[i] = language;
        i++;
      }
    }
    return langs;
  }
}

module.exports = {
  highlight: highlight
};

为什么写这么多注释呢?毕竟这个代码只在我的代码段结构下测试通过的,建议大家在移植的时候如果有问题能够修改该代码。

3、添加相关引用

在添加了 highlight.js 后,就要在 html2json.js 中引用并调用该文件。

首先在 html2json.js 开头引用 highlight.js :

var highlight = require('./highlight.js');  // 注意路径

然后在 html2json() 函数中调用:

function html2json(html, bindName) {
    //处理字符串
    // console.log('1');
    html = removeDOCTYPE(html);
    html = trimHtml(html);
    html = wxDiscode.strDiscode(html);
    html = highlight.highlight(html);

这里要注意的是,在调用 strDiscode() 函数后再调用 highlight() 函数!

接着是样式表的引用,在 detail.wxss 的开头部分引用 prism.wxss

@import "../../utils/prism.wxss"; // 注意路径

三、高亮效果

差不多就这样吧,有啥问题我会再补充,预览截图:

小程序代码高亮预览
小程序代码高亮预览

还可以扫描文章下面的小程序码在小程序中打开本篇文章来看一看效果。


版权说明:
作品 sunriseydy 采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
文章内容如未说明均为原创,欢迎转载,但请注明原作者(sunriseydy)和原文链接(https://blog.sunriseydy.top/technology/server-blog/wordpress/wordpress-miniapp-code-highlight/)
部分来自互联网的文章,如有侵权,请联系我,24小时内删除,谢谢

微博 QQ 打赏 点赞 4

“给基于 wxParse 的 WordPress 版小程序添加代码高亮”的2个回复

  1. 笑春风

    对了,还有就是 prism.wxss 中要做一些更改,把“pre”换成“.wxParse-pre”或者“.pure-highlightjs”,注意,后者是适合我的代码段结构的,建议自定义。

    这句话没明白,所有“pre”字符都替换????

    • @笑春风 我已经更新了一个示例,其实也就是把“pre”字符替换。

评论一下呗亲

电子邮件地址不会被公开。 必填项已用*标注