浅谈学习正则表达式的重要性
一、问题
使用 Hexo 搭建博客确实简单又强大,简单在于构建和发布过程,强大在于它的扩展性。关于 Hexo 博客插件功能有兴趣的朋友可以参考我之前的一篇文章:分享几个实用的 HEXO 博客功能插件 ,但是有时候这些功能比较官方,我们还是需要自己动手 DIY 一下才能更好的适应自己的网页。我现在使用的博客 RSS 订阅功能这个插件( hexo-generator-feed )就不太适合我自己的博客行情。
问题是这样的,因为我使用了图片懒加载的功能,导致生成的 RSS.xml 文件包含的图片部分是真实地址,部分是预加载图片的地址而不是真实源图片地址:
1 | <p><img src="http://url/to/imgloader.gif" data-echo="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 |
对于上面的代码我要做到三点:
- 图片 src 是真实地址的不能改,比如:
src="http://url/to/real-image.jpg"
- 图片 src 是相对地址的,需要添加绝对地址:
src="real-image.jpg"
改成src="http://url/to/real-image.jpg"
- 图片 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
来使用,代码一目了然:
1 | if(feedConfig.replaceURL) { |
注意上面代码中我所注释的那段代码,我发现我并不能直接修改 element.content
那样会导致我所有博客文章和 RSS 文件一同被莫名其妙地改掉,这是我没有预料到的,所以,鉴于 JavaScript 的动态语言特性,我给每篇文章 post
动态地添加了一个属性: post.newContent
用于 RSS 的生成。
最后还需要在模板代码中进行应用:
1 | {% if config.feed.content and post.content and post.newContent %} |
三、写在最后
其实我们在进行字符串匹配、替换、修改的时候,我们不一定完全需要使用正则表达式,特别是那些不复杂的情况,简单使用字符串的一些标准方法就可以进行查找替换修改了。但是,我觉得能用正则表达式就尽量使用正则表达式,有时候性能也不会差,我给出三点简单的原因:
1 正则表达式有时候并不慢
在对于长篇的文字匹配搜索的时候,正则表达式表达更加合理,速度也不慢,我觉得优先使用正则表达式。虽然我也没有理论支持,但是想想,正则表达式为啥存在于各种语言之中?是吧。
2 我所熟悉的 Java 中 replaceAll 函数
这个函数表面上和 replace
一样,实际上它的第一个参数是一个正则表达式而非字符,所以 "1.2.3".replaceAll(".", "-")
的结果不是 1-2-3
而是 -----
,因为 "."
是正则表达式代表任何非空字符的匹配规则啊。
3 正则表达式在不同语言中基本通用
不一定是 JavaScript ,对于 Java 或者其他语言都能通用正则表达式,看来学习它是很有必要的,你说呢?
参考资料:
正则表达式(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