负向零宽断言
前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反 义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办? 例如,如果我们想查找这样的单词--它里面出现了字母q,但是q后面跟的不是字母u,我 们可以尝试这样:
\b\w*q[^u]\w*\b 匹配 包含后面不是字母u的字母q的单词 。但是如果多做测试(或 者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话, 像Iraq,Benq,这个表达式就会出错。这是因为 [^u] 总要匹配一个字符,所以如果q是 单词的最后一个字符的话,后面的 [^u] 将会匹配q后面的单词分隔符(可能是空格,或 者是句号或其它的什么),后面的 \w*\b 将会匹配下一个单词,于是 \b\w*q[^u]\w*\b 就 能匹配整个 Iraq fighting 。 负向零宽断言 能解决这样的问题,因为它只匹配一个位 置,并不消费任何字符。现在,我们可以这样来解决这个问题: \b\w*q(?!u)\w*\b 。
零宽度负预测先行断言 (?!exp) , 断言此位置的后面不能匹配表达式exp 。例 如: \d{3}(?!\d) 匹配 三位数字,而且这三位数字的后面不能是数字 ; \b((?!abc)\w)
\b 匹配 不包含连续字符串abc的单词 。
同理,我们可以用 (?<!exp) , 零宽度负回顾后发断言 来 断言此位置的前面不能匹 配表达式exp : (?<![a-z])\d{7} 匹配 前面不是小写字母的七位数字 。
一个更复杂的例子: (?<=<(\w )
>).*(?=<\/\1>) 匹配 不包含属性的简单 HTML标签内里的内容 。 (<?(\w )>) 指定了这样的 前缀 : 被尖括号括起
请详细分析表达式 (?<=<(\w )>).*(?
=<\/\1>) ,这个表达式最能表现零宽断言的 真正用途。
来的单词 (比如可能是<b>),然后是 .* (任意的字符串),最后是一个 后缀 (?=<\/\1>) 。 注意后缀里的 \/ ,它用到了前面提过的字符转义; \1 则是一个反向引用,引用的正 是 捕获的第一组 ,前面的 (\w ) 匹配的内容,这样如果前缀实际上是<b>的话,后缀
就是</b>了。整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括前缀和后
缀本身)。