正则表达式(二)

2018-03-22  本文已影响13人  IsaacHHH

我们想象一下这样的场景:需要用正则表达式去标记要匹配的文本位置,而不仅仅是去查找文本本身。这就需要我们正则中的另一个知识:前后查找。

从需求中去理解"前后查找"

所谓的前后查找,在进步一用大白话来说就是:我要查找某个位置的前面(或后面)的内容。比如:https://baidu.com,我们需要查找:前面的,也就是https这几个字。

或许对于上面这个例子,不用前后查找也能实现。比如用"文本匹配"、子表达式以及利用开始^、结束$符都可以。但是很显然是比较耗时、繁琐的。比如下面这个例子:

<body>
  <h1>我是H1标签</h1>
</boy>

对于上面的例子中,我们想要获取到标题标签里的内容, 注意:仅仅是要获取内容,而不获取标签本身。

或许我们可以这么写:
<h1>.*<\/h1>
或许再完善一些,可以这么写:
<[hH]([1-6])>.*<\/[hH]\1>
我们会发现,这样虽然可以获取到标题标签和内容,但是我们不想要<h1>——<h6>这些元素标签,我们只需要标签里的内容。虽然我们可以在进一步对返回的匹配结果进行处理,但是相对于前后查找显然是比较繁琐的。

有了实际场景需求后,可以去尝试用前后查找的方法去解决。


前后查找实际应用

前后前后,肯定会有前有后,也就是会有向前查找向后查找

向前查找

基本语法:?=

先来看几个例子

  1. 找出URL中的使用的是什么协议。
let str1 = 'https://baidu.com';
let str2 = 'http://qq.com';
let str3 = 'ftp://taobao.com';

// 正则表达式
let regexp = /.+(?=:)/;
str1.match(regexp); // https
str2.match(regexp); // http
str3.match(regexp); // ftp

这里,我们在js中使用match方法去找出协议名称,可以很容易的匹配到。

对于这段正则/.+(?=:), 其中?=正是向前查找的语法。对于使用()包裹起来的是字表达时,这个我们应该了解,也就是说,在子表达式中, 我们通过向前查找的语法?=告诉正则, 我们只匹配:前面的内容。

用一些专业术语来说, 我们这里?=:的意思是:只要找到:就行,但是不消费它,并且返回:前面的内容。

如果我们不用向后查找?=试一下:
正则:.+:
使用上面的正则运行一下你会发现,冒号:也被包含在匹配结果里了,显然,这不是我们想要的。

向后查找

基本语法: ?<=

我们发现,向后查找相较于向前查找,在语法上,知识少了一个箭头:<。在概念上,理解了前面的向前查找,向后查应该也会理解。

所谓的向后查找,就是查找某个字符位置的后面内容。

先来看几个例子

还是拿前面的向查找的的匹配URL协议的例子。只不过我们现在把需求改一下,改成获取URL地址,但是不包含协议头和冒号

修改后的正则: (?<=:).+
这样,我们就会获取到协议后面的网址。也就是说会获取:后面的内容(虽然带上了两个斜线//)

当然,这里我们可以直接调用JS中的location对象中的host(或者hostname)属性,包括协议名称也可以拿到,但是我们这里说的是正则😁。


解决问题(前后查找同时用)

前面通过两个简单的例子,我们理解了什么是向后查找和向前查找。前后查找就是匹配一个标识的前后内容,可以将这个标识理解成界定符(或分隔符), 但是不消费这个界定符。

理解了之后,我们开始着手解决刚开始我们遇到的场景:去找出标题标签中的内容。
在开始之前,我们要知道,前后查找可以同时用,也就是说向后查找和向前查找可以同时用
而这里我们要查找出来标签里的文本内容,换句话说,就是要查找<h1></h1>之间的内容。
再进一步理解,就是我们要<h1>进行向后查找,对<h1/>进行向前查找

文本:

<body>
  <h1>我是H1</h1>
</body>

正则:(?<=<h1>).*(?=<\/h1>)

这里我们通过两个子表达式来解决了前面的问题,两个子表达式就像两个定界符,分别在两边卡住了我们需要的内容。解决思路前面我们也已经解释了,就是前后查找同时用。
当然,这个正则有一些局限性,只能匹配<h1>标签,我们可以进一步完善:

完善后的正则:
(?<=[hH]([1-6])>).*(?=<\/[hH]\1>)
这里我们可以成功匹配<h1><h6>标签之间的内容,并且不论标签是否大小写,还有一点就是:利用了回溯引用,前后标签不匹配的我们会忽略。关于回溯引用,我的另外一篇文章中有写到。


负向前(后)查找

这里所说的负向前查找负向后查找,和前面我们所说的向前查找和向后查找是相对的。通俗点说,这里的就是取非的意思,也就是对前后查找取非。

这里的取非,和我们正则中的另外一个取非字符^不同,这里的取非,并没有使用^字符。

负向前查找

负向前查找会向前查找与给定模式不匹配的内容。

基本语法: ?!

我们来看一个例子

123(?!com) //匹配后面不带com的123
可以匹配: 123cn
不能匹配:123com

负向后查找

负向后查找会向后查找与给定模式不匹配的内容

基本语法:?<!

(?<!com)123 // 匹配前面不带com的123
可以匹配: abc123
不可匹配: com123

我们来看一个例子

文本:

I paid $30 for 100 apples,
50 pranges, and 60 pears,
I saved $5 on this order.

我们需要做的是:找出价格,但是不包含$符号。这里我们使用向后查找
正则(?<=\$)\d+
这样,我们会查找出价格,在这里就是305

如果我们要查找出来数量,这里就要使用到负向后查找
正则\b(?<!\$)\d+
我们此时会返回1005060

另外我们这里使用\b元字符来定义了单词边界,是为了防止出现查找到的内容包含一些非单词的内容。你可以尝试下去掉\b元字符再去匹配下,会发现返回的结果中包含了$30中的0,因为这个0完全符合规则,因为0前面不是$。

当然了,如果不使用负向前查找,也可以使用[^$]\d+,但是需要注意的是,返回的每个结果前面会包含一个空格。


总结

这里的前后查找又称零宽断言
有四种基本的前后查找操作符:

所谓的前后查找,就是将我们充当定界符的东西放在=后面,这样就不会消费这个定界符,返回的结果只是这个界定符前面(或后面)的内容。

同时,我们可以将前后查找组合使用。

上一篇下一篇

猜你喜欢

热点阅读