首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

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}{nm}

量词

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,使用了反向引用和定位符。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论