正则表达式(二)
或许对于上面这个例子,不用前后查找也能实现。比如用"文本匹配"、子表达式以及利用开始 ^ 、结束 $ 符都可以。但是很显然是比较耗时、繁琐的。比如下面这个例子:
对于上面的例子中,我们想要获取到标题标签里的 内容 , 注意:仅仅是要获取内容,而不获取标签本身。
或许我们可以这么写:
<h1>.*<\/h1>
或许再完善一些,可以这么写:
<[hH]([1-6])>.*<\/[hH]\1>
我们会发现,这样虽然可以获取到标题标签和内容,但是我们不想要 <h1>——<h6> 这些元素标签,我们只需要标签里的内容。虽然我们可以在进一步对返回的匹配结果进行处理,但是相对于前后查找显然是比较繁琐的。
基本语法: ?=
先来看几个例子
这里,我们在js中使用 match 方法去找出协议名称,可以很容易的匹配到。
对于这段正则 /.+(?=:) , 其中 ?= 正是向前查找的语法。对于使用 () 包裹起来的是字表达时,这个我们应该了解,也就是说,在子表达式中, 我们通过向前查找的语法 ?= 告诉正则, 我们只匹配 : 前面的内容。
如果我们不用向后查找 ?= 试一下:
正则: .+:
使用上面的正则运行一下你会发现,冒号 : 也被包含在匹配结果里了,显然,这不是我们想要的。
基本语法: ?<=
所谓的向后查找,就是查找某个字符位置的后面内容。
先来看几个例子
还是拿前面的向 前 查找的的匹配URL协议的例子。只不过我们现在把需求改一下,改成获取URL地址,但是 不包含协议头和冒号 。
修改后的正则: (?<=:).+ 。
这样,我们就会获取到协议后面的网址。也就是说会获取 : 后面的内容 (虽然带上了两个斜线 // ) 。
当然,这里我们可以直接调用JS中的location对象中的host(或者hostname)属性 ,包括协议名称也可以拿到,但是我们这里说的是正则😁。
理解了之后,我们开始着手解决刚开始我们遇到的场景:去找出标题标签中的内容。
在开始之前,我们要知道,前后查找可以同时用,也就是说 向后查找和向前查找可以同时用 。
而这里我们要查找出来标签里的文本内容,换句话说,就是要查找 <h1> 和 </h1> 之间的内容。
再进一步理解,就是我们要 对 <h1> 进行向后查找,对 <h1/> 进行向前查找 。
文本:
正则: (?<=<h1>).*(?=<\/h1>) 。
这里我们通过两个子表达式来解决了前面的问题,两个子表达式就像两个定界符,分别在两边卡住了我们需要的内容。解决思路前面我们也已经解释了,就是前后查找同时用。
当然,这个正则有一些局限性,只能匹配 <h1> 标签,我们可以进一步完善:
完善后的正则:
(?<=[hH]([1-6])>).*(?=<\/[hH]\1>)
这里我们可以成功匹配 <h1> 到 <h6> 标签之间的内容,并且不论标签是否大小写,还有一点就是:利用了回溯引用, 前后标签不匹配的我们会忽略 。关于回溯引用,我的 另外一篇文章 中有写到。
这里的取非,和我们正则中的另外一个取非字符 ^ 不同,这里的取非,并没有使用 ^ 字符。
基本语法: ?!
我们来看一个例子
基本语法: ?<!
我们来看一个例子
文本:
我们需要做的是:找出价格,但是不包含 $ 符号。这里我们使用 向后查找 。
正则 : (?<=\$)\d+
这样,我们会查找出价格,在这里就是 30 和 5
如果我们要查找出来数量,这里就要使用到 负向后查找 。
正则 : \b(?<!\$)\d+
我们此时会返回 100 、 50 和 60 。
另外我们这里使用 \b 元字符来定义了单词边界,是为了防止出现查找到的内容包含一些非单词的内容。你可以尝试下去掉 \b 元字符再去匹配下,会发现返回的结果中包含了 $30 中的0,因为这个0完全符合规则,因为0前面不是$。
这里的前后查找又称 零宽断言 。
有四种基本的前后查找操作符:
所谓的前后查找,就是将我们充当定界符的东西放在 = 后面,这样就不会消费这个定界符,返回的结果只是这个界定符前面(或后面)的内容。
同时,我们可以将前后查找组合使用。