三月
24
2016

PHP 正则中的断言

在阅读 symfony 源码的过程中,发现了一个不认识的正则表达式:([^ ]+?)(?: |(?<!\\)"|(?<!\\)'|$),看了很久,还真没有弄懂是要干嘛。

所以赶紧查看了一下 PHP 文档正则表达式的部分,直到翻看到断言这一章的时候,终于算是找到答案了。原来这是运用了 PHP 正则中的断言。

一个断言就是一个对当前匹配位置之前或之后的字符的测试, 它不会实际消耗任何字符。简单的断言代码有\b、\B、 \A、 \Z、\z、 ^、$ 等等。 更加复杂的断言以子组的方式编码。

断言有两种类型:

  • 前瞻断言:用于从当前位置向前测试,注意前瞻表示向当前字符串之后的字符串进行测试
  • 后瞻断言:用于从当前位置向后测试,注意后瞻表示向当前字符串之前的字符串进行测试

两种断言又细分为积极断言和消极断言。

前瞻断言中的积极断言以 (?= 开始,用于断言向前测试的结果为真;前瞻断言中的消极断言以 (?! 开始,用于断言向前的测试结果为假。例如:

  • \w+(?=;) 匹配一个单词紧跟着一个分号但是匹配结果不会包含分号
  • foo(?!bar) 匹配所有后面没有紧跟 ”bar””foo” 字符串

但是有个特别需要注意的地方:

  • (?!foo)bar 不能用于测试 bar 前面出现的不是 foo,因为前瞻断言的特性,他只能判断向右的字符串不是 foo,所以在 (?:foo) 遇到 bar 的时候永远为真(我想,此时电脑的心声是:这熊孩子,明明是 bar,还问我是不是 foo,哈哈)。

要实现上例中原本要实现的想法,就得换个法子了,也就是接下来登场的后瞻断言。

后瞻断言中的积极断言以 (?<=开头,消极断言以 (?<! 开头。例如:

  • (?<!foo)bar 用于查找任何前面不是 ”foo””bar”

后瞻断言的内容被严格限制为只能用于匹配定长字符串。但是,如果有多个可选分支, 它们不需要拥有相同的长度。比如 (?<=bullock|donkey) 是允许的, 但是 (?<!dogs?|cats?) 将会引发一个编译期的错误。

多个断言(任意顺序)可以同时出现。 比如 (?<=\d{3})(?<!999)foo 匹配前面有三个数字但不是 ”999” 的字符串 ”foo”。注意, 每个断言独立应用到对目标字符串该点的匹配。 首先它会检查前面的三位都是数字, 然后检查这三位不是 ”999”。 这个模式不能匹配 ”foo” 前面有三位数字然后紧跟 3 位非 9996 个字符的字符串,比如, 它不匹配 ”123abcfoo”。 匹配 ”123abcfoo” 这个字符串的模式可以是 (?<=\d{3}…)(?<!999)foo

这种情况下,第一个断言查看(当前匹配点)前面的 6 个字符,检查前三个是数字, 然后第二个断言检查(当前匹配点)前三个字符不是 ”999”

断言可以以任意复杂度嵌套。 比如 (?<=(?<!foo)bar)baz 匹配前面有 ”bar” 但是 ”bar” 前面没有 ”foo””baz”。 另外一个模式 (?<=\d{3}…(?<!999))foo 则匹配前面有三个数字字符紧跟 3个不是 999 的任意字符的 ”foo”

断言子组时非捕获子组,并且不能用量词修饰, 因为对同一件事做多次断言是没有意义的.如果所有的断言都包含一个捕获子组, 那么为了在整个模式中捕获子组计数的目的,它们都会被计算在内。然而, 子字符串的捕获仅可以用于正面断言,因为对于消极的断言是没有意义的。

将断言计算在内,可以拥有的最大子组数量是 200 个。

非特殊注明博文为 爱PHP吧(www.iphp8.com)原创内容,转载请注明原文地址:http://www.iphp8.com/?post=79

评论

发表评论:

(选填)

(选填)