14.6 复杂模式
相对于简单模式,复杂模式体现的是更高级的匹配应用,自然也更为烦琐。在复杂模式中引入了分组、反向引用、候选、非捕获性分组、前瞻、边界定位符和多行模式等概念。
14.6.1
分组
理解了简单模式,读者在开发时可能会想到两个问题:
前面介绍的量词可以表示重复出现多次的单个字符,但如何表示目标字符串中重复出现子串呢?
对于查询者而言,很容易得到整个表达式的结果,但如何得到表达式内子表达式的结果呢?
为解决以上问题,正则表达式引入分组符“()”概念。其语法为“(pattern)”,即将“pattern”部分组合成一个可统一操作的组合项或子匹配。简单来讲就是用括号括起一些字符、字符类或量词等,每个捕获的子匹配项按照其出现顺序存储在缓冲区中。缓冲区编号从1开始,最多可存储99个子匹配捕获的内容。
例如量词可以修饰分组,括号中的表达式可以作为整体被修饰,如正则表达式为/(abc){3}/,目标字符串为“wellabcabcabcdown”,则可成功匹配子串“abcabcabc”。
取匹配结果时,分组的子匹配结果可以被单独获取,例如表达式/abc(\d+)/,目标字符串为“abc1245”,\d+成功匹配“1245”的同时又被作为子匹配结果记录在缓冲区内,且编号为1(整个表达式的结果编号为0),那么开发者可以通过RegExp对象的$1属性来获得子匹配(\d+)的匹配结果“1245”,读者可以参考源程序14.2。
分组可以嵌套使用,如表达式/(abc(123))/,存储编号与左括号“(”出现的先后次序相同,如分组(123)编号为2。
14.6.2
反向引用
分组得到的子匹配,在匹配过程以及匹配结束时均可通过RegExp对象进行访问。正则表达式后边的部分,可以引用前面分组的子匹配已经匹配到的字符串,称为反向引用。
反向引用语法为“\num”,即可引用编号为num的子匹配存储的内容。num是从1到99的整数,并且num是前面表达式中已编号的子匹配,当然也不能超出已有编号的范围。
例如正则表达式/\w{5}/可以匹配目标字符串中连续出现五个字符,如“hahfi”、“abadc”、“aaaaa”、“zyieh”等都可以成功匹配。如果要求五次出现的都是同一个字符,如“aaaaa”、“ccccc”等,则可以用正则表达式/(\w)\1{4}/来完成匹配,\1{4}表示子匹配(\w)匹配到的结果再重复出现4次。
再来举个较为复杂的例子,正则表达式 /<(\w+)\s*(\w+(=('|").*?\4)?\s*)*>.*?</\1>/ 在匹配“<td id='td1' style="bgcolor:white"></td>”时成功匹配。这里的反向引用\1等于(\w+),从而保证了只有当“<td>”与“</td>”配对时,模式匹配才成功,否则匹配失败;而\4等于“'|"”,保证“=”号后要以单引号“’”或者双引号“””开始,并以对应的符号结尾,如id='td1',不允许出现id= id='td1"的情况。
注意:子表达式“'|"”表示的是单引号“’”和双引号“””之间“或”的关系。如果要表达“与”关系可用连接符“&”。
14.6.3
候选
候选,即用“|”来表示模式的或关系。语法为expression|expression,符号“|”左右两边表达式是“或”的关系,在模式匹配时可匹配左边或者右边。
例如正则表达式为/John|Jack/,匹配字符串为“Jack is at home, where is John”,可以成功匹配到子串“Jack”,再次匹配时也可成功匹配到“John”。
至此,分析电话号码的正则表达式如下所示:
/(((\d{3}\)|\d{3}-)\d{8})|(((\d{4}\)|\d{4}-)\d{7})$/
电话号码是3位区号加8位号码,或者4位区号加7位号码,这就是一个“或”的关系。所以,整个表达式总体为/()|()/,再考虑左右两边括号的内容。
三位区号可以用\d{3}表示,8位号码可以用\d{8}来表示,可能中间还有“-”隔开,所以添加了一个\d{3}-的选择,所以左边为(((\d{3}\)|\d{3}-)\d{8})。那右边4位区号加7位号码就可依葫芦画瓢为(((\d{4}\)|\d{4}-)\d{7}),最终得到整个正则表达式进行模式匹配。
14.6.4
非捕获性分组
在正则表达式的建立过程中,可能读者还会遇到这样的问题:想对某些字符串分组,但是又不想其形成子匹配,即不想其组合被编号并缓存起来。为解决此问题,需要用到正则表达式中“非捕获性分组”的概念。
非捕获性分组语法为(?:pattern) ,即将pattern部分组合成一个可统一操作的组合项,但不把这部分内容当作子匹配捕获,匹配的内容不进行编号也不存储在缓冲区中供以后使用。非捕获性分组方法在必须进行组合、但又不想对组合的部分进行缓存的情况下非常有用。
例如,要在一篇英文资料中查找“program”和“project”两个单词,正则表达式可表示为/program|project/,也可表示为/pro(gram|ject)/,但是缓存子匹配(gram|ject)没有意义,就可以用/ pro(?:gram|ject)/进行非捕获性匹配,这样既可以简洁匹配又可不缓存无实际意义的子匹配。
前瞻是对匹配字符作一些限定条件,例如限定被查找字符串后面必须跟什么字符或者不能跟什么字符。例如要匹配子串“abc”,但限定子串“abc”后必须紧跟子串“123”就必须使用“前瞻”的概念。前瞻分为正向前瞻和负向前瞻,下面分别予以详细介绍。
14.6.5
正向前瞻
正向前瞻语法为(?=pattern),即在目标字符串的相应位置必须有pattern部分匹配的内容,但不作为匹配结果处理,更不会存储在缓冲区内供以后使用。
例如,正则表达式/bed(?=room)/只能匹配子串“bedroom”,而子串“room”并不作为匹配结果返回,以进行后续处理。同样正则表达式/Windows(?=2000|xp)/不能匹配WindowsNT,只能匹配其后紧跟子串“2000”或“xp”的子串Windows,而子串“2000”和“xp”并不作为结果返回。
14.6.6
负向前瞻
负向前瞻语法为(?!pattern),在被搜索字符串的相应位置不能有pattern部分表示的内容,也不将其作为匹配结果进行处理,当然也不会存储在缓冲区。
例如正则表达式/bed(?!room)/只能匹配后面不跟子串“room”的子串“bed”,如子串“bedrock”等,且子串“room”并不作为匹配的结果返回。
同样,正则表达式/Windows(?!2000|xp)/匹配除“Windows2000”和“Windowsxp”之外的子串“Windows”,如子串“WindowsNT”等,且子串“2000”和“xp”并不作为匹配的结果返回。
注意:“前瞻”和“非捕获性分组”的概念容易混淆。相同之处是两者括号内的子表达式都不作为子匹配保存;不同点在于,后者括号内的子表达式是作为结果返回的,而前者括号内的子表达式不作为结果返回。例如非捕获性分组表达式/Windows (?:XP)/ 匹配的结果是“WindowsXP”,而前瞻表达式/Windows (?=XP)/ 匹配的结果是“Windows”。
14.6.7
边界定位符
在进行验证时,要使用定位符来限定字符出现的位置以更快匹配目标子串,这些定位符也叫边界符。常见的边界定位符如表14.8所示。
表14.8 边界定位符表
|
定 位 符 |
描 述 |
|
^ |
与字符串开始的地方匹配,不匹配任何字符 |
|
$ |
与字符串结束的地方匹配,不匹配任何字符 |
|
\b |
匹配一个单词边界,也就是单词和空格之间的位置,不匹配任何字符 |
|
\B |
\b取非,即匹配一个非单词边界 |
定位符^匹配必须发生在字符串的开头位置。例如:表达式/^aaa/在匹配“xxx aaa xxx”时失败,只有在匹配“aaa xxx xxx”才能匹配成功。如果设置了multiLine属性,正则表达式在每行的开头进行匹配,也即多行模式。
与定位符^相反,定位符$匹配必须发生在字符串的末尾。例如:表达式/aaa$/匹配“xxxx xxxx aaa”时成功,但匹配“xxxx aaa xxxx”则失败。如果设置了multiLine 属性,正则表达式在每行的末端进行匹配,即多行模式。
定位符\b包含了字与空格间的位置,以及目标字符串的开始和结束位置等。而定位符\B则刚好相反,匹配除了\b能匹配外的所有字符。
例如正则表达式\bjava\b在匹配目标字符串“javascript javaEjb java”时,只能成功匹配到最后一个“java”,而不是子串“javascript”和“javaEjb”中的“java”。
表达式/\Bjava\B/在匹配“javascript ForjavaEjb java”时,能成功匹配到“ForjavaEjb”,而不能匹配子串“javascript”或者“java”。
14.6.8
正则表达式中操作符的优先权顺序
在正则表达式中各操作符的优先权顺序不相同,正如算术运算时加号(+)和乘号(´)的优先权顺序不同一样。充分了解操作符的优先权顺序,对正则表达式的理解和建立是很有帮助的。正则表达式中操作符的优先权顺序总结如表14.9所示,且优先权层与层之间从上到下递减,同层中从左到右递减。
表14.9 操作符优先权列表
|
顺 序 |
符 号 |
描 述 |
|
1 |
\ |
转义符 |
|
2 |
(),(?:),(?=),[] |
括号 |
|
3 |
*,+,?,{n},{n,},{n,m} |
量词 |
|
4 |
^,$ |
定位符 |
|
5 |
| |
候选 |
14.6.9
复杂模式综合实例
下面运用复杂模式中的分组、反向引用、候选、非捕获性分组和正向前瞻综合实现匹配搜索,从结果分析它们之间的异同点。
正则表达式为:/(windows(?:XP))|(windows(?=NT))|(windows)\3$/g。
匹配字符串为:目前windowsXP,而windowsNT则很少为人所认识。windows NT是“Windows New Technology”的缩写。将来是否会出现Windows windows版本还真难说, 那么什么是windows windows呢?
考察源程序14.10。
//源程序14.10
<!DOCTYPE HTML PUBLIC"-//W3C//DTD HTML 4.0//EN"
"http://www.w3.org/TR/REC-html140/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Sample Page!</title>
<script language="JavaScript" type="text/javascript">
<!--
function test()
{
var myString="目前windowsXP,而windowsNT则很少为人所认识。"+ "\n"
+"windows NT是“Windows New Technology”的缩写。" +"\n"
+"将来是否会出现Windows windows版本还真难说," +"\n"
+"那么什么是windows windows呢?";
var regex=/(windows(?:XP))|(windows(?=NT))|(windows)\3$/g;
var msg="\n正则表达式复杂模式 :\n\n";
msg+="目标字符串 :\n"+myString+"\n";
msg+="操作语句 :\n"+"var array=regex.exec(myString)\n\n";
msg+="匹配结果 :\n";
var array=regex.exec(myString);
if(array)
{
msg+="第一次成功匹配!\n"
+"index="+array.index+"\n"
+"lastIndex="+array.lastIndex+"\n"
+"$1="+RegExp.$1+"\n\n";
}
array=regex.exec(myString);
if(array)
{
msg+="第二次成功匹配!\n"
+"index="+array.index+"\n"
+"lastIndex="+array.lastIndex+"\n"
+"$2="+RegExp.$2+"\n\n";
}
array=regex.exec(myString);
if(array)
{
msg+="第三次成功匹配!\n"
+"index="+array.index+"\n"
+"lastIndex="+array.lastIndex+"\n"
+"$3="+RegExp.$3+"\n";
}
alert(msg);
return true;
}
-->
</script>
</head>
<body>
<center>
<p>
复杂模式综合实例程序:
</p>
<form onSubmit="return test();">
<input type="submit" value="确定">
</form>
</center>
</body>
</html>
程序运行后,在原始页面中单击“确定”按钮,弹出警告框如图14.18所示。

图14.18 复杂综合实例运行结果
结果分析:
第一次(windows(?:XP))成功匹配windowsXP,而且子匹配$1是windowsXP,为非捕获性分组;
第二次(windows(?=NT))成功匹配windowsNT,注意不是window NT(中间是有空格的),且返回的子匹配$2是windows,而不是windowsNT,为正向前瞻;
第三次(windows)\3$成功匹配最后一个windows windows,使用了反向引用和定位符。






