The Preg Functions
本节从最基本的“这个正则表达式能否在字符串中找到匹配”开始,详细介绍各个函数。
preg_match
使用方法
preg_match(pattern,subject[,matches[,flags [,offset]]])
参数简介
pattern 分隔符包围起来的正则表达式,可能出现修饰符(☞444)。
subject 需要搜索的目标字符串。
matches 非强制出现,用来接受匹配数据。
flags 非强制出现,此标志位会影响整个函数的行为。这里只容许出现一个标志位,
PREG_OFFSET_CAPTURE(☞452).
offset非强制出现,从0开始,表示匹配尝试开始的位置(☞453)。
返回值
如果找到匹配,就返回true,否则返回false。
讲解
最简单的用法是:
preg_match($pattern,$subject)
如果$pattern在$subject中能找到匹配,就会返回true。下面有几个简单的例子:
捕获匹配数据
preg_match的第3个参数如果出现,则会用来保存匹配结果的信息。用户可以照自己的意愿使用任何变量,不过最常用的名字是$matches。在本书中,如果我在特定的例子之外提到$matches,指的就是“preg_match接收的第3个参数”。
匹配成功之后,preg_match返回true,并按如下规则设置$matches:
如果使用了命名分组,$matches中也会保存对应的元素(下一节有这样的例子)。
第5章中(☞191)曾出现过这个简单的例子:
最好是在preg_match返回true的情况下用$matches(随便你怎么命名)。如果匹配不成功,会返回 false,或者错误(例如模式错误或函数标志位设置错误)。有的错误发生之后,$matches 是空数组,但也有时候它的值不会变化,所以我们不能认为,$matches 不为空就表示匹配功。
下面这个例子使用了3组捕获型括号:
数组结尾“未参与匹配”的元素会被忽略
如果一组捕获型括号没有参与最终匹配,它会在对应的$matches中生成一个空字符串(注2)。需要说明的是,$matches末尾的空字符串都会被忽略。在前面那段程序中,如果「(/d+)」参与了匹配,$matches[3]会保存一个数值,否则,$matches[3]根本就不会存在。
命名捕获
如果我们用命名捕获(☞138)重写之前的例子,正则表达式会长一些,不过代码更容易阅读:
命名捕获看起来更清晰,这样我们不需要把$matches的内容复制给各个变量,就能直接使用变量名,而不是$matches,例如这样:
如果使用了命名捕获,按数字编号的捕获仍然会插入$matches。例如,在匹配$url(值为‘http://regex.info’)之后,之前例子中的$UrlInfo包含:
这样的重复有点浪费,但这是获得命名捕获的便捷和清晰所必须付出的代价。为清晰起见,我不推荐同时使用命名和数字编号来访问$matches的元素,当然用$matches[0]表示全局匹配例外。
请注意,数组中不包括编号为3和名称为‘port’的入口(entry),因为这一组捕获型括号没有参与到最终匹配中,而且处于最后(因此会被忽略☞450)。
还要提一点,尽管现在使用例如「(?P<2>…)」之类的数字命名并不会出错,但这种做法并不可取。PHP4和PHP5在处理非正常情况时会有所区别,可能不会按照个人的意愿发展,所以最好还是不要使用数字来命名捕获分组。
更多的匹配细节:PREG_OFFSET_CAPTURE
如果设置了 preg_match 的第 4 个参数 flags,而且包含 PREG_OFFSET_CAPTURE(这也是preg_match目前能够接受的唯一标志位),则$matches的每个元素不再是普通字符串,而是由两个元素构成的子数组,其中第1 个元素是匹配的文本,第2 个元素是这段文本在目标字符串中的偏移值(如果没有参与匹配,则为-1)。
偏移值从0开始,表示这段文本相对目标字符串的偏移值,即使设置了第5个参数$offset,偏移值的计算也不会变化。它们通常按照字节来计数,即使使用了模式修饰符 u 也是如此(☞447)。
来看个从tag中提取HREF属性的例子。HTML的属性值两边可能是双引号、单引号,或者干脆没有引号,这样的值在下面这个正则表达式的第1组、第2组和第3组捕获型括号中被捕获:
如果$tag包含:
<a name=bloglink href='http://regex.info/blog/'rel="nofollow">匹配成功之后,$matches的内容是:
$matches[0][0]包含正则表达式匹配的所有文本,$matches[0][1]表示匹配文本在目标字符串中的偏移值,按字节计数。
为了清晰起见,另一种获得$matches[0][0]的办法是:
substr($tag,$matches[0][1],strlen($matches[0][0]));
$matches[1][1]是-1,表示第1组捕获括号没有参与匹配。第3组也没有参与,但是因为之前提到的理由(☞450),结尾未参与匹配的捕获括号匹配的文本不会包含在$matches中。
offset参数
如果 preg_match中设置了 offset 参数,引擎会从目标字符串的对应位置开始(如果 offset是负数,则从字符串的末尾开始倒数)。默认情况下,offset是0(也就是说,从目标字符串的开头开始)。
请注意,offset是按字节计数的,即使使用了模式修饰符u也是这样。如果设置不正确(例如从某个多字节字符的“内部”开始)会导致匹配失败。
即使offset不等于0,PHP也不会把这个位置标记为「^」——字符串的起始位置,它只表示正则引擎开始尝试的位置。不过,逆序环视倒是可以检查offset左边的文本。
preg_match_all
使用方法
preg_match_all(pattern,subject,matches [,flags [,offset]])
参数简介
pattern分隔符包围起来的正则表达式,可能出现修饰符(☞444)。
subject需要检索的目标字符串。
matches 用来保存匹配数据的变量(必须出现)。
flags非强制出现,标志位设定整个函数的功能:
PREG_OFFSET_CAPTURE(☞456)
和/或任意:
PREG_PATTERN_ORDER(☞455) PREG_SET_ORDER(☞456)
offset非强制出现,从0开始,表示目标字符串中匹配尝试开始的位置(与preg_match的offset参数相等☞453)。
返回值
preg_match_all返回匹配的次数。
讲解
preg_match_all类似于preg_match,只是在找到第一个匹配之后,它会继续搜索字符串,找到其他的匹配。每个匹配都会创建一个包含匹配数据的数组,所以最后 matches变量就是一个二维数组,其中的每个子数组对应一次匹配。
这里有个简单的例子:
preg_match_all要求必须出现第3个参数(也就是用来收集所有成功的匹配信息的变量)。所以,这个例子中虽然没有用到$all_matches,但仍然必须设置这个变量。
收集匹配数据
preg_match和 preg_match_all的另一个主要区别是第3 个参数中的数据。preg_match进行至多一次匹配,所以它把匹配的数据存储在matches变量中。与此不同的是,preg_match_all能匹配许多次,所以它的第3 个参数保存了多个单次匹配的 matches。为了说明这种区别,我使用$all_matches作为preg_match_all的变量名,而不是$preg_match中常用的$matches。
preg_match_all可以以两种方式在$all_matches中存放数据,根据下面两个互斥的第4个参数flag:PREG_PATTERN_ORDER或是PREG_SET_ORDER来决定。
默认的排列方式是 PREG_PATTERN_ORDER 下面有个例子(我称其为“按分组编号的(collated)”——稍后将介绍)。如果没有设置标志位,这就是默认的配列方式:
$all_matches的结果为:
一共匹配了两次,每次都包含一个“全局匹配”字符串,以及 3 个捕获型括号对应的子字符串。我称其为“按分组编号的(collated)”,因为所有的全局匹配都存放在一个数组里(在$all_matches[0],每次匹配中,第1组括号配的文本存放在另一个数组$all_matches[1]中,依次类推。
默认情况下,$all_matches是按分组编号的,但我们可设置PREG_SET_ORDER来改变它。PREG_SET_ORDER 排列方式 如果设定了 PREG_SET_ORDER 标志位,就会采用“堆叠(stacked)”的排列方式。它会把第 1 次匹配的所有数据保存在$all_matches[0] 中,第 2次匹配的所有数据保存在$all_matches[1] 中,依次类推。这就是我们检索字符串的顺序,把每次成功匹配的$matches放进$all_matches数组中。
下面是之前那个例子的PREG_SET_ORDER的版本:
结果是:
两种排列方式的总结如下:
preg_match_all和PREG_OFFSET_CAPTURE标志位
就像 preg_match 一样,也可以在 preg_match_all 中使用 PREG_OFFSET_CAPTURE,让$all_matches的每个末端元素(leaf element)成为一个两个元素的数组(匹配的文本,以及按字节计算的偏移值)。也就是说,$all_matches 成为一个数组的数组的数组,这可真饶舌。如果你希望同时使用PREG_OFFSET_CAPTURE和PREG_SET_ORDER,请使用逻辑运算符“or”来连接:
preg_match_all与命名分组
如果使用了命名分组,$all_matches将会多出命名元素(同preg_match一样☞451)。这段程序:
$all_matches的结果是:
如果使用PREG_SET_ORDER:
结果就是:
我个人认为,在使用了命名分组之后,就应该去掉数字编号,因为这样程序更清晰,效率更高,不过,如果它们被保留了,你可以当它们不存在。
preg_replace
使用方法
preg_replace(pattern,replacement,subject [,limit [,count]])
参数简介
pattern分隔符包围起来的正则表达式,可能出现修饰符。pattern也可能是一个pattern-argument字符串的数组。
replacement replacement字符串,如果pattern是一个数组,则replacement是包含多个字符串的数组。如果使用了模式修饰符 e,则字符串(或者是数组中的字符串)会被当作PHP代码(☞459)。
subject需要搜索的目标字符串。也可能是字符串数组(按顺序依次处理)。
limit非强制出现,是一个整数,表示替换发生的上限(☞460)。
count非强制出现,用来保存实际进行的替换次数(只有PHP5提供,☞460)。
返回值
如果 subject 是单个字符串,则返回值也是一个字符串(subject 的副本,可能经过修改)。
如果subject是字符串数组,返回值也是数组(包含subject的副本,可能经过修改)。
讲解
PHP 提供了许多对文本进行查找-替换的办法。如果查找部分可以用普通的字符串描述,str_replace或者 str_ireplace就更合适,但是如果查找比较复杂,就应该使用 preg_replace。
来看一个简单的例子:在Web开发中经常会遇到这样的任务,把信用卡号或电话号码输入一张表单。你是否经常看到“不要输入空格和连字符”的提示?要求用户按规则输入数据,还是由程序员做一点小小的改进,让用户可以照自己的习惯输入数据?哪种办法更好(注3)?毕竟,这里我们的要求就是“清理”这样的输入数据:
其中用 preg_replace 来去掉非数字字符。更确切的说,它用 preg_replace 来生成$card_number 的副本,将其中的非数字字符替换为空(空字符串),把这个经过修改的副本赋值给$card_number。
单字符串,单替换规则的preg_replace
前面三个元素(pattern,replacement 和 subject)都是既可以为字符串,也可以为字符串数组。通常这三者都是普通的字符串,preg_replace 首先生成 subject 的副本,在其中找到pattern的第1次匹配,将匹配的文本替换为replacement,然后重复这一过程,直到搜索到字符串的末尾。
在replacement字符串中,‘$0’表示匹配的所有文本,‘$1’表示第1组捕获型括号匹配的文本,‘$2’表示第2组,依次类推。请注意,美元符加数字的字符序列并不会引用变量,虽然它们在其他某些语言中有这种功能,但是 preg_replace 能识别简单的序列,并进行特殊处理。你可以使用一对花括号来包围数字,比如‘${0}’和‘${1}’,这样就不会引起混淆。
这个简单的例子把HTML的bold tag转换为全部大写:
$html=preg_replace('//b[A-Z]{2,}/b/','<b>$0</b>',$html);
如果使用了模式修饰符e(它只能出现在preg_replace中),replacement字符串会作为PHP代码,每次匹配时执行,结果作为replacement字符串。下面这个扩展的例子把bold tag里的单词变为小写:
如果正则表达式匹配的单词是‘HEY’,replacement字符串中的$0会被替换为这个值。结果,replacement字符串就成了‘strtolower("<b>HEY</b>")’,执行这段PHP代码,结果就是‘<b>hey</b>’。
如果使用模式修饰符 e,replacement 字符串中的捕获引用会按照特殊的规定来插值:插值中的引号(单引号或双引号)会转义。如果不这样处理,插入的数值中的引号会导致 PHP代码出错。
如果使用模式修饰符e,在replacement字符串中引用外部变量,最好是在replacement字符串文本中使用单引号,这样变量就不会进行错误的插值。
这个例子类似于PHP内建的htmlspecialchars()函数:
$new_subject=preg_replace('/[&<">]/eS','$replacement["$0"]',$subject);需要注意,这个例子中的replacement使用了单引号字符串来避免$replacement变量插值,直到将其作为PHP代码执行。如果使用双引号字符串,在传递给preg_replace之前,插值就会进行。
可以用模式修饰符S用来提高效率(☞478)。
preg_replace 的第 4 个参数用来设定替换操作次数的上限(单位是单个字符串-单个正则表达式,参见下一节)。默认值是-1,表示“没有限制”。
如果设置了第5个参数count(PHP4没有提供),它会用来保存preg_replace的实际替换的次数。如果你希望知道是否发生了替换,可以比较原来的目标字符串和结果,不过检查count参数效率更高。
多字符串,多替换规则
前一节已经提到,目标字符串通常是普通字符串,至少我们目前看到的所有例子都是如此。不过,subject 也可以是一个字符串数组,这样搜索和替换是对每个字符串依次进行的。返回值也是由每个字符串经过搜索和替换之后的数组。
无论使用的是字符串还是字符串数组,pattern和replacement参数也可以是字符串数组,下面是各种组合及其意义:
如果subject参数是数组,则依次处理数组中的每个元素,返回值也是字符串数组。
请注意limit参数是以单个pattern和单个subject为单位的。它不是对所有的pattern和subject生效。返回的$count则是所有pattern和subject字符串所进行操作次数的总合。
这里有一个preg_replace的例子,其中pattern和replacement都是数组。其结果类似于PHP内建的htmlspecialchars()函数,它保证处理过的文本符合HTML规范:
如果输入的文本是:
AT&T--> "baby Bells"
$cooked的值就是:
AT&T-->"baby Bells"
当然也可以预先准备好这些数组,下面的程序运行结果相同:
preg_replace 能够接收数组作为参数是很方便的(这样程序员就不需要使用循环在各个pattern和subject中进行迭代),但是它的功能并没有增强。比如,各个pattern并不是“并行”处理的。但是,相比自己写PHP循环代码,内建的处理效率更高,而且更容易阅读。为了说清楚,请参考这个例子,其中所有的参数都是数组:
$result_array=preg_replace($regex_array,$replace_array,$subject_array);
它等价于:
数组参数的排序问题 如果pattern和replacement都是字符串,它们会根据数组的内部顺序配对,这种顺序通常就是它们添加到数组中的先后顺序(pattern 数组中添加的第1 个元素对应replacement 数组中的第1 个元素,依次类推)。也就是说,对于 array()创建的“文本数组”来说,排序没有问题,例如:
「[a-z]+」对应‘word<$0>’,下面的「/d+」对应‘num<$0>’,结果就是
result:word<this> word<has> num<7> word<words> word<and> num<31> word<letters>相反,如果 pattern 或 replacement 数组是多次填充的,数组的内部顺序可能就不同于 keys的顺序(也就是说,由 keys 表示的数字顺序)。所以前一页的程序使用数组模拟preg_replacement的程序要使用each来按照数组的内部顺序遍历整个数组,而不关心它们的keys如何。
如果 pattern 或 replacement 数组的内部顺序不同于你希望匹配的顺序,可以使用 ksort()函数来确保每个数组的实际顺序和外表顺序是相同的。
如果pattern和replacement都是数组,而pattern中元素的数目多于replacement中的元素,则会在replacement数组中产生对应的空字符串,来进行配对。
pattern 数组中的元素顺序不同,结果可能大不相同,因为它们是按照数组中的顺序来处理的。如果把例子中的pattern数组的顺序颠倒过来(把replacement数组中的顺序也颠倒过来),结果是什么呢?也就是说,下面代码的结果是什么呢?
ϖ 请翻到下页查看答案。
preg_replace_callback
使用方法
preg_replace_callback(pattern,callback,subject [,limit [,count]])参数简介
pattern 分隔符包围起来的正则表达式,可能出现修饰符(☞444)。也可能是字符串数组。
callback PHP回调函数,每次匹配成功,就执行它,生成replacement字符串。
subject 需要搜索的目标字符串。也可能是字符串数组(依次处理)。
limit非强制出现,设定替换操作的上限(☞460)。
count非强制出现,用来保存实际发生替换的次数(只在PHP 5.1.0中提供)。
返回值
如果subject是字符串,返回值就是字符串(其实是subject的一个副本,可能经过了修改)。如果subject是字符串数组,返回值就是数组(每个元素都是subject中对应元素的副本,可能发生了修改)。
讲解
preg_replace_callback类似于 preg_replace,只是replacement 参数变成了PHP 回调函数,而不是字符串或是字符串数组。它有点像使用模式修饰符e的preg_replace(☞459),但是效率更高(如果replacement部分的代码很复杂,这种办法更易于阅读)。
请参考PHP文档获得更多关于回调的知识,不过简单地说,PHP回调引用(以许多种方式中的一种)一个预先规定的函数,以预先规定的参数,返回预先规定的值。在preg_replace_callback 中,每次成功匹配之后都会进行这种调用,参数是$matches 数组。函数的返回值用作preg_replace_callback作为replacement。
回调可以以三种方式引用函数。一种是直接以字符串形式给出函数名;另一种是用PHP内建的create_function生成一个匿名函数。稍后我们会看到使用这两种方法的例子。第三种方式本书没有提及,它采用面向对象的方式,由一个包含两个元素(分别是类名和方法名)的数组构成。
下面这个例子用preg_replace_callback和辅助函数重写了第460页的程序。callback参数是一个字符串,包含辅助函数的名字:
如果$subject的值是:
"AT&T" sounds like "ATNT"
则$new_subject的值就是:
"AT&T"sounds like"ATNT"
本例中的text2html_callback是普通的PHP函数,用作preg_replace_callback中的回调函数,它的接收参数是$matches数组(当然,这个变量可以随意命名,不过我选择遵循之前使用$matches的惯例)。
为完整起见,下面我给出使用匿名函数的办法(使用PHP内建的create_function函数)。这段程序产生的$replacement变量与上面一样。函数体也相同,只是此时函数没有名字,只能在preg_replace_callback中使用:
使用callback,还是模式修饰符e
如果处理不复杂,使用模式修饰符的程序比preg_replace_callback更容易看懂。但是,如果效率很重要,那么请记住,如果使用模式修饰符e,每次匹配成功之后都需要检查作为PHP代码的replacement参数。相比之下,preg_replace_callback的效率就要高许多(如果使用回调,PHP代码只需要审查1次)。
preg_split
使用方法
preg_split(pattern,subject [,limit,[flags]])
参数简介
pattern 分隔符包围起来的正则表达式,可能还有修饰符(☞444)。
subject 需要分割的目标字符串。
limit非强制出现,是一个整数,表示切分之后元素的上限。
flags非强制出现,此标志位影响整个切割行为,以下三项可以随意组合:
它们的讲解从第468页开始。多个标志位使用二元运算符“或”来连接(与第456页一样)。
返回值
返回一个字符串数组。
讲解
preg_split会把字符串的副本切分为多个片段,以数组的形式返回。非强制出现参数limit设定返回数组中元素数目的上限(如果需要,最后的元素包括“其他所有字符”)。可以设定不同的标志位来调整返回的方式和内容。
从某种意义上来说,preg_split做的是与preg_match_all相反的事情:它找出目标字符串中不能由正则表达式匹配的部分。或者更传统地说,preg_split返回的是,将目标字符串中正则表达式匹配的部分删去之后的部分。preg_split 大概相当于 PHP 中内建的简单explode函数,不过使用的是正则表达式,而且功能更强大。
来看个简单的例子,如果某家金融网站需要接收用空格分隔的股票行情。可以使用explode拆分这些行情数据:
$tickers=explode('',$input);
不过,如果输入数据时不小心输入了不只一个空格,这个程序就不能处理了。更好的办法是使用preg_split,用正则表达式「/s+」来切分:
$tickers=preg_split('//s+/',$input);
除了明确运用“用空格切分”的规则之外,用户也通常使用逗号(或者是逗号加空格)来分隔,比如‘YHOO,MSFT,GOOG’。这些情况也很容易处理:
$tickers=preg_split('/[/s,]+/',$input);
针对上面的数据,$tickers得到的是包含3个元素的数组:‘YHOO’、‘MSFT’和‘GOOG’。
如果输入的数据是逗号分隔的(例如给照片标记 tag 时使用的“Web 2.0,”),就需要用「/s*,/s*」来处理:
$tags=preg_split('//s*,/s*/',$input);
比较「/s*,/s*」和「[/s,]+」很能说明问题。前者用逗号来切分(逗号必须出现),但也会删去逗号两边的空白字符。如果输入‘123,,,456’,则能够进行3次匹配(每次匹配一个逗号),返回4个元素:‘123’,两个空字符串,最后是‘456’。
另一方面,「[/s,]+」会使用任何逗号、连续的逗号、空白字符,或者是空白字符和逗号的结合来切分。在‘123,,,456’中,它一次就能匹配3个逗号,返回两个元素,‘123’和‘456’。
limit参数
limit 参数用来设定切分之后数组长度的上限。如果搜索尚未进行到字符串结尾时,切分的片段的数目已经达到limit,则之后的内容会全部保存到最后的元素当中。
来看个例子,我们需要手工解析服务器返回的 HTTP response。按照标准,header 和 body的分隔是四字符序列‘/r/n/r/n’,不幸的是,有的服务器使用的却是‘/n/n’。幸好,我们有preg_split,很容易处理这两种情况。假设整个response保存在$response中:
$parts=preg_split('//r?/n/r?/n/x',$response,2);
header保存在$parts[0]中,而body保存在$parts[1]中(使用模式修饰符S是为了提高效率☞478)。
第3个参数,即limit的值等于2,表示subject字符串最多只能切分成两个部分。如果找到了一个匹配,匹配之前的部分(也就是 header)会成为返回值的第一个元素。因为“字符串的其他部分”是第2个元素,这样就到达了上限,它(我们知道是body)会原封不动地作为返回值中的第二个元素。
如果没有limit(或者limit等于-1,这两种情况是等价的),preg_split会尽可能多地切分subject字符串,这样body可能也会被切分为许多段。设置上限并不能保证返回的数组中包含的元素就等于这个数,而只是保证最多包含这么多元素(阅读关于PREG_SPLIT_DELIM_CAPTURE的小节,你会发现甚至这种说法也不完全对)。
在两种情况下,应该人为设置上限。我们已经见过一种情况:希望最后的元素包含“其他所有内容”。在前一个例子中,一旦第一段(header)被切分出来,我们就不希望再对其他部分(body)进行切分。所以,把上限设为2会保留body。
如果用户知道自己不需要切割出来所有元素,也可以设定上限,提高效率。例如,如果$data字符串包含以「/s*,/s*」分隔的许多字段(比如姓名、地址、年龄,等等),而只需要前面两个,就可以把limit 设置为3,这样 preg_split在切分出前两个字段之后就不会继续工作:
$fields=preg_split('//s*,/s*/x',$data,3);
这样其他内容都保存在最后的第3个元素中,我们可以用array_pop来删除,或者置之不理。
如果你希望在没有设置上限的情况下使用任何preg_split标志位(下一节讨论),则必须提供一个占位符,将limit设置为-1,它表示“没有限制”。相反,如果limit等于 1,则表示“不需要切分”,所以它并不常用。上限等于0或者-1之外的任何负数都没有定义,所以请不要使用它们。
flag参数
preg_split中可以使用的3个标志位都会影响函数的功能。它们可以单独使用,也可以用二元运算符“or”连接(参见第456页的例子)。
PREG_SPLIT_OFFSET_CAPTURE 就像在 preg_match 和 preg_match_all 中使用 PREG_OFFSET_CAPTURE一样,这个标志位会修改结果数组,把每个元素变为包含两个元素的数组(元素本身和它在字符串中的偏移值)。
PREG_SPLIT_NO_EMPTY 这个标志位告诉preg_split忽略空字符串,不把它们放在返回数组中,也不记入limit的统计。对目标字符串的起始位置、结尾位置,或是空行的匹配,都会带来空字符串。
下面来改进前面的“Web 2.0”的tag的例子(☞466),如果$input为‘party,,fun’,那么:
$tags=preg_split('//s*,/s*/x',$input);
得到的$tags包含3个元素:‘party’、空字符串,然后是‘fun’。空字符串是逗号的两次匹配之间的“空白”。
如果设置了PREG_SPLIT_NO_EMPTY 标志位:
$tags=preg_split('//s*,/s*/x',$input,-1,PREG_SPLIT_NO_EMPTY);
结果数组只包含‘party’和‘fun’。
PREG_SPLIT_DELIM_CAPTURE 这个标志位在结果中包含匹配的文本,以及进行此次切分的正则表达式的捕获括号匹配的文本。来看个简单的例子,如果字符串中各个字段是以‘and’和‘or’来联系的,例如:
DLSR camera and Nikon D200 or Canon EOS 30D
如果不使用PREG_SPLIT_DELIM_CAPTURE,
$parts=preg_split('//s+(and|or)/s+/x',$input);
得到的$parts是:
array ('DLSR camera','Nikon D200','Canon EOS 30D')
分隔符中的匹配内容被去掉了。不过,如果使用了PRE_SPLIT_DELIM_CAPTURE 标志位(并且用-1作为limit参数的占位符):
$parts包含了捕获型括号匹配的分隔符:
array ('DLSR camera','and','Nikon D200','or','Canon EOS 30D')
此时,每次切分会在结果数组中增加一个元素,因为正则表达式中只有一组捕获型括号。然后我们就能够遍历$parts中的元素,对找到的‘and’和‘or’进行特殊处理。
请注意,如果使用了非捕获型括号(如果 pattern 参数为‘//s+(?:and|or)/s+/’),PREG_SPLIT_DELIM_CAPTURE 标志位不会产生任何效果,因为它只对捕获型括号有效。
来看另一个例子,第466页分析股市行情的例子:
$tickers=preg_split('/[/s,]+/',$input);
如果我们添加捕获型括号,以及PREG_SPLIT_DELIM_CAPTURE
$tickers=preg_split('/([/s,]+)/',$input,-1,PREG_SPLIT_DELIM_CAPTURE);结果$input 中的任何字符都没有被抛弃,它只是切分之后保存在$tickers 中。处理$tickers数组时,你知道编号为奇数的元素是「([/s,]+)」匹配的。这可能很有用,如果在向用户显示错误信息时,可以对不同的部分分别进行处理,然后将它们合并起来,还原出输入的字符串。
还有一点需要注意,通过PREG_SPLIT_DELIM_CAPTURE添加的元素不会影响切分上限。只有在这种情况下,结果数组中的元素数目才可能超过上限(如果正则表达式中的捕获型括号很多,则元素就要更多)。
结尾的未参与匹配的捕获型括号不会影响结果数组。也就是说,如果一组捕获型括号没有参与最终匹配(参见450页),可能会也可能不会在结果数组中添加空字符串。如果编号更靠后的捕获型括号参与了最终匹配,就会增加,否则就不会。请注意,如果使用了PREG_SPLIT_NO_EMTPY 标志位,结果会有变化,因为空字符串肯定会被抛弃。
preg_grep
使用方法
preg_grep(pattern,input[,flags])
参数简介
pattern 分隔符包围起来的正则表达式,可能出现修饰符。
input 一个数组,如果它们的值能够匹配pattern,则其值会复制到返回的数组中。
flags 非强制出现,此标志位PREG_GREP_INVERT或者是0。
返回值
一个数组,包含input中能够由pattern匹配的元素(如果使用了 PREG_GREP_INVERT标志位,则包括不能匹配的元素)。
讲解
preg_grep 用来生成 input 数组的副本,其中只保留了 value 能够匹配(如果使用了PREG_GREP_INVERT标志位,则不能匹配)pattern的元素。此value对应的key会保留。来看个简单的例子
preg_grep('//s/',$input);
它返回$input数组中的,由空白字符构成的元素。相反的例子是:
preg_grep('//s/',$input,PREG_GREP_INVERT);
它返回不包含空白字符的元素。请注意,第二个例子不同于:
preg_grep('/^/S+$/',$input);
因为后者不包括空(长度为0)值元素。
preg_quote
使用方法
preg_quote(input [,delimiter])
参数简介
input希望以文字方式用作pattern参数的字符串(☞444)。
delimiter 非强制出现的参数,包含1个字符的字符串,表示希望用在pattern参数中的分隔符。
返回值
preg_quote返回一个字符串,它是input 的副本,其中的正则表达式元字符进行了转义。如果指定了分隔符,则分隔符本身也会被转义。
讲解
如果要在正则表达式中以文字方式使用某个字符串,可以用内建的preg_quote函数来转义其中可能产生的正则表达式元字符。如果指定了创建pattern时使用的分隔符,字符串中的分隔符也会被转义。
preg_quote是专门应对特殊情况的函数,在许多情况下没有用,不过这里有个例子:
如果$MailSubject包含下面的字符串
**Super Deal** (Act Now!)
最后$pattern就会是
/^Subject:/s+(Re:/s*)*/*/*Super Deal/*/*/(Act Now/!/)/mi
这样就可以作为preg函数的pattern参数了。
如果指定的分隔符是‘{’之类对称的字符,那么对应的字符(例如‘}’)不会转义,所以请务必使用非对称的分隔符。
同样,空白字符和‘#’也不会转义,所以结果可能不适于用x修饰符。
这样说来,在把任意文本转换为 PHP 正则表达式的问题上,preg_quote 并不是完善的解决办法。它只解决了“文本到正则表达式”的问题,而没有解决“正则表达式到pattern参数”的问题,任何preg函数都需要这一步。下一节给出了解决办法。