浅谈学习正则表达式的重要性

2017-08-10 by Liuqingwen | Tags: Web Hexo | Hits

一、问题

使用 Hexo 搭建博客确实简单又强大,简单在于构建和发布过程,强大在于它的扩展性。关于 Hexo 博客插件功能有兴趣的朋友可以参考我之前的一篇文章:分享几个实用的 HEXO 博客功能插件 ,但是有时候这些功能比较官方,我们还是需要自己动手 DIY 一下才能更好的适应自己的网页。我现在使用的博客 RSS 订阅功能这个插件( hexo-generator-feed )就不太适合我自己的博客行情

问题是这样的,因为我使用了图片懒加载的功能,导致生成的 RSS.xml 文件包含的图片部分是真实地址,部分是预加载图片的地址而不是真实源图片地址:

1
2
3
<p><img src="http://url/to/imgloader.gif" data-echo="real-image.jpg"></p>
<p><img src="real-image.jpg"></p>
<p><img src="http://url/to/real-image.jpg"></p>

这个时候就需要自己动手稍微 Hack 一下插件的源码了,对整篇的文字进行查找替换就需要正则表达式派上用场了。

二、解决方法

对于 JavaScript 编程我是门外汉,不过好在正则表达式在不同语言之间是通用的,至少大部分场景是这样,那么对于会 Java 的我来说对源码简单修改一下足够了。关于正则表达式这里有一篇文章总结的比较好,刚好介绍了我需要使用的知识点:正则表达式中的不匹配,下面引用的是文章的正则表达式定义表格:

表达式 定义 表达式 定义 表达式 定义 表达式 定义 表达式 定义
[abc] a或b或c . 任意单个字符 a? 零个或一个a [^abc] 任意不是abc的字符 \s 空格
a* 零个或多个a [a-z] a-z的任意字符 \S 非空格 a+ 一个或多个a [a-zA-Z] a-z或A-Z
\d 任意数字 a{n} 正好出现n次a ^ 一行开头 \D 任意非数字 a{n,} 至少出现n次a
$ 一行末尾 \w 任意字母数字或下划线 a{n,m} 出现n-m次a (…) 括号用于分组 \W 任意非字母数字或下划线
a*? 零个或多个a(非贪婪) (a|b) a或b (a)…\1 引用分组 (?=a) 前面有a (?!a) 前面没有a

对于上面的代码我要做到三点:

  1. 图片 src 是真实地址的不能改,比如: src="http://url/to/real-image.jpg"
  2. 图片 src 是相对地址的,需要添加绝对地址: src="real-image.jpg" 改成 src="http://url/to/real-image.jpg"
  3. 图片 src 是懒加载图片的,修改为 data-echo 表示的绝对地址: src="http://url/to/imgloader.gif" data-echo="real-image.jpg" 改成 src="http://url/to/real-image.jpg"

第三种情况很好处理,正则表达式: /(http\:\/\/url\/to\/imgloader.gif" data-echo=")/g 来进行替换即可 ,这里很多符号需要使用 \ 反斜杠来转义,另外 g 表示全局搜索替换。

第二种情况和第一种情况很相似,但是第一种情况是不需要做任何修改的,刚开始我简单的替换 src=" 为绝对路径 src=http://url/to/ 是行不通的,这样会把第一种情况的图片地址也替换掉: src="http://url/to/http://url/to/real-image.jpg" 这是我不想要的结果!

所以,这里需要用到正则表达式中的不匹配原则了,如果路径中不包含 http:// 那么就是相对地址,需要修改!正则表达式是: /<img src="(?!http:\/\/).+(.jpg|.png|.gif)"/gi ,显然, (?!http:\/\/) 是表示匹配字符串不包含 http:// 的意思,这里注意 i 表示不区分大小写进行搜索, . 表示匹配任何换行符之外的单个字符,然后 + 代表不止一个, (.jpg|.png|.gif) 表示这三种图片格式中的任何一种即可。这样正则表达式就达到匹配搜素的目的了。

另外,正则表达中括号 () 非常有用( (x)(?:x) 含义相反,可以参考相关资料 ),初学者很容易忽略这一点!它的含义和用途是:

(x)
匹配 x 并且记住匹配项,就像下面的例子展示的那样。括号被称为捕获括号
模式 /(foo) (bar) \1 \2/ 中的 (foo)(bar) 匹配并记住字符串 foo bar foo bar 中前两个单词。模式中的 \1\2 匹配字符串的后两个单词。注意 \1\2\n 是用在正则表达式的匹配环节。在正则表达式的替换环节,则要使用像 $1$2$n 这样的语法,例如,'bar foo'.replace(/(...) (...)/, '$2 $1')

所以最终我的代码如下,我加了两个括号用于记住匹配项并用 $1$2 来使用,代码一目了然:

hexo-generator-feed/lib/generator.js
1
2
3
4
5
6
7
8
9
10
11
12
if(feedConfig.replaceURL) {
var regLazy = /(http\:\/\/liuqingwen.me\/images\/imgloader.gif" data-echo=")/g;
var regSrc = /(<img src=")((?!http:\/\/).+(.jpg|.png|.gif)")/gi;
posts.forEach(function(post) {
var coverdiv = post.permalink + post.cover_index;
var contenthead = '<span class="image main"><img src="' + coverdiv + '" alt="' + post.title + '"></span>';
var content = post.content.replace(regSrc, "$1" + post.permalink + "$2");
content = content.replace(regLazy, post.permalink);
//element.content = contenthead + content;
post.newContent = contenthead + content;
});
}

注意上面代码中我所注释的那段代码,我发现我并不能直接修改 element.content 那样会导致我所有博客文章和 RSS 文件一同被莫名其妙地改掉,这是我没有预料到的,所以,鉴于 JavaScript 的动态语言特性,我给每篇文章 post 动态地添加了一个属性: post.newContent 用于 RSS 的生成。

最后还需要在模板代码中进行应用:

atom.xml
1
2
3
4
5
{% if config.feed.content and post.content and post.newContent %}
<content type="html"><![CDATA[{{ post.newContent | safe }}]]></content>
{% elif config.feed.content and post.content %}
<content type="html"><![CDATA[{{ post.content | safe }}]]></content>
{% endif %}

三、写在最后

其实我们在进行字符串匹配、替换、修改的时候,我们不一定完全需要使用正则表达式,特别是那些不复杂的情况,简单使用字符串的一些标准方法就可以进行查找替换修改了。但是,我觉得能用正则表达式就尽量使用正则表达式,有时候性能也不会差,我给出三点简单的原因:

1 正则表达式有时候并不慢

在对于长篇的文字匹配搜索的时候,正则表达式表达更加合理,速度也不慢,我觉得优先使用正则表达式。虽然我也没有理论支持,但是想想,正则表达式为啥存在于各种语言之中?是吧。 smiley

2 我所熟悉的 Java 中 replaceAll 函数

这个函数表面上和 replace 一样,实际上它的第一个参数是一个正则表达式而非字符,所以 "1.2.3".replaceAll(".", "-") 的结果不是 1-2-3 而是 ----- ,因为 "." 是正则表达式代表任何非空字符的匹配规则啊。 joy

3 正则表达式在不同语言中基本通用

不一定是 JavaScript ,对于 Java 或者其他语言都能通用正则表达式,看来学习它是很有必要的,你说呢? grin

参考资料:
正则表达式(MDN - Mozilla Developer Network): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
正则表达式中的不匹配: http://www.isnowfy.com/regular-expression-negative/
EJS (GitHub): https://github.com/tj/ejs
SWIG (GitHub): https://github.com/paularmstrong/swig
PUG (GitHub): https://github.com/pugjs/pug


Comments: