回溯引用前后一致匹配1、回溯引用有什么用2、回溯引用匹配3、回溯引用在替换操作中的应用3.1、大小写转换1、回溯引用有什么用为了理解回溯引用的概念我们最好是看一个例子。HTML程序员经常使用标题标签H1到H6以及配对的结束标签来定义和排版Web页面里的标题文字。现在我们不妨假设你需要把某个Web页面里的所有标题文字全都查找出来而不管它的级别是多少。下面就是这个例子文本BODYH1Welcome to my Homepage/H1Content is divided into two sections:BRH2ColdFusion/H2Information about Macromedia ColdFusion.H2Wireless/H2Information about Bluetooth, 802.11, and more./BODY正则表达式[hH]1.*\/[hH]1模式[hH]1.*\/[hH]1只能匹配一级标题从H1或h1到/H1或/h1; HTML语言不区分字母的大小写。但我们刚才说的是匹配任意级别的标题HTML文档里的标题总共有6个级别这应该怎么办呢最容易想到的办法是用一个字符集合来代替1如下所示正则表达式[hH][1-6].*?\/[hH][1-6]结果这个模式看来不错[hH][1-6]匹配任何一级标题的开始标签具体到这个例子它匹配到了H1和H2,/[hH][1-6]匹配任何一级标题的结束标签具体到这个例子它匹配到了/H1和/H2。这里使用的是?懒惰型而不是贪婪型。我们在第5章里讲过和其他几个元字符是“贪婪型”元字符所以模式[hH][1-6]./[hH][1-6]有可能会从第2行的H1一直匹配到第6行的/H2这可不是我们想要的结果使用“懒惰型”元字符*解决了这个问题。之所以说“有可能”而不是“肯定”是因为在这个特定的例子里即便是使用了“贪婪型”元字符也不一定会有问题。一般来说元字符不匹配换行符而上例中的每个标题都各自占据一行。但在这里使用懒惰型元字符没有任何坏处——事前小心总比事后后悔好。现在成功了吗未必。看看下面这个例子这次使用的是还是刚才那个模式你就知道我为什么这样说了文本BODYH1Welcome to my Homepage/H1Content is divided into two sections:BRH2ColdFusion/H2Information about Macromedia ColdFusion.H2Wireless/H2Information about Bluetooth, 802.11, and more.H2This is not valid HTML/H3/BODY正则表达式[hH][1-6].*?\/[hH][1-6]结果在这个例子里原始文本里有一个标题是以H2开头、以H3结束的。这显然是一个不合法的标题但它与我们所使用的模式匹配上了。出现这种情况的根源是这个模式的第2部分用来匹配结束标签的那个部分对这个模式的第1部分用来匹配开始标签的那个部分毫无所知。要想彻底解决这个问题就只能求助于回溯引用。2、回溯引用匹配我们等会儿再去解决匹配HTML标题的问题。先来看一个比较简单的例子这个问题如果不使用回溯引用将根本无法解决。假设你有一段文本你想把这段文本里所有连续重复出现的单词打字错误其中有一个单词输了两遍找出来。显然在搜索某个单词的第二次出现时这个单词必须是已知的。回溯引用允许正则表达式模式引用前面的匹配结果具体到这个例子就是前面匹配到的单词。把这个问题弄明白的最佳办法是看看它到底是如何工作的。下面是一段包含着3组重复单词的文本它们就是我们要找的东西文本This is a blockofoftext,several words here are are repeated,and and they should not be.正则表达式[](\w)[]\1结果这个模式找到了我们想要的东西但它是如何做到这一点的呢[ ]匹配一个或多个空格\w匹配一个或多个字母数字字符[ ]匹配随后的空格。注意\w是括在括号里的它是一个子表达式。这个子表达式不是用来进行重复匹配的这里根本不涉及重复匹配的问题。这个子表达式只是把整个模式的一部分单独划分出来以便在后面引用。这个模式的最后一部分是\1这是一个回溯引用而它引用的正是前面划分出来的那个子表达式当\w匹配到单词of的时候\1也匹配单词of当\w匹配到单词and的时候\1也匹配单词and。回溯引用指的是模式的后半部分引用在前半部分中定义的子表达式如上例所示。\1到底代表着什么它代表着模式里的第1个子表达式\2代表着第2个子表达式、\3代表着第3个依次类推。于是在上面那个例子里[ ]\w[ ]\1将匹配同一个单词的连续两次重复出现。看过回溯引用的用法之后我们再回过头来看看应该如何解决匹配HTML标题的问题。利用回溯引用构造一个模式去匹配任何一级标题的开始标签和与之配对的结束标签忽略任何不配对的标签组合对我们来说已经不是什么难题了。下面就是这个例子文本BODYH1Welcome to my Homepage/H1Content is divided into two sections:BRH2ColdFusion/H2Information about Macromedia ColdFusion.H2Wireless/H2Information about Bluetooth, 802.11, and more.H2This is not valid HTML/H3/BODY正则表达式[hH]([1-6]).*?\/[hH]\1结果总共找到了3个匹配1个一级标题H1…/H1和2个二级标题H2…/H2。[hH][1-6]匹配任何一级标题的开始标签但我们这次用和把[1-6]括了起来使它成为了一个子表达式。这样一来我们就可以在用来匹配标题结束标签的/[hH]\1用\1来引用这个子表达式了。子表达式[1-6]匹配数字1~6, \1只匹配与之相同的数字。这样一来原始文本里的H2This isnot valid HTML/H3就不会被匹配到了。警告 回溯引用只能用来引用模式里的子表达式用和括起来的正则表达式片段。提示 回溯引用匹配通常从1开始计数\1、\2等等。在许多实现里第0个匹配\0可以用来代表整个正则表达式。注意 正如看到的那样子表达式是通过它们的相对位置来引用的\1对应着第1个子表达式\5对应着第5个子表达式等等。虽然受到普遍的支持但这种语法存在着一个严重的不足如果子表达式的相对位置发生了变化整个模式也许就不能再完成原来的工作删除或添加子表达式的后果可能更为严重。为了弥补这一不足一些比较新的正则表达式实现还支持“命名捕获”named capture给某个子表达式起一个唯一的名字然后用这个名字而不是相对位置来引用这个子表达式。因为命名捕获还没有得到广泛支持而且已支持的实现具体的语法也极不统一所以本书没有对此进行讨论。但是如果你正在使用的正则表达式实现支持命名捕获功能如NET你应该充分利用。3、回溯引用在替换操作中的应用到目前为止你们在这本书里见到的正则表达式都是用来执行搜索的即在一段文本里查找特定的内容。你在今后的实际工作中也会发现你所编写的绝大多数正则表达式模式也可以用来搜索文本。但这并不是正则表达式的全部功能正则表达式还可以用来完成各种复杂的替换操作。简单的文本替换操作无须使用正则表达式就可以完成。比如说如果只是把某个文档里的CA全部替换为California或把MI全部替换为Michigan的话用正则表达式来完成这些替换就未免有点儿大材小用了。这句话的意思并不是说正则表达式不能用来执行这种替换只是那么做没有什么实际价值。事实上用普通的字符串处理函数来完成这种替换反而会更容易一些。正则表达式更适用于复杂的替换尤其是需要使用回溯引用的场合那才能体现出正则表达式的真正威力。文本Hello,benforta.com is my email address.正则表达式\w[\w\.]*[\w\.]\.\w结果这个模式可以把原始文本里的电子邮件地址查找出来。现在假设你需要把原始文本里的电子邮件地址全都转换为可点击的链接你该怎么办在HTML文档里你需要使用AHREF“maito:useraddress.com”useraddress.com这样的语法来创建一个可点击的电子邮件地址。能不能用一个正则表达式把一个电子邮件地址转换为这种可点击的地址格式呢当然能而且非常容易——但前提是你得使用回溯引用如下所示文本Hello,benforta.com is my email address.正则表达式(\w[\w\.]*[\w\.]\.\w)替换AHREFmailto:$1$1/A结果Hello,AHREFmailto:benforta.combenforta.com/Ais my email address.替换操作需要用到两个正则表达式一个用来给出搜索模式另一个用来给出匹配文本的替换模式。回溯引用可以跨模式使用在第一个模式里被匹配的子表达式可以用在第二个模式里。这里使用的模式\w[\w\ .]*[\w\.]\ .\w与我们以前使用的完全一样匹配电子邮件地址但这次把它写成了一个子表达式。这样一来被匹配到的文本就可以用在替换模式里了。AHREFmailto:$1$1/A使用了两次被匹配的子表达式一次是在HREF属性里来定义mailto:…另一次是做为可点击文本。具体到这个例子benforta.com变 成了A HREFmailto:benforta.combenforta.com /A而这正是我们想要的结果。警告 我们刚才讲过回溯引用语法在不同的正则表达式实现里有很大的差异JavaScript用户需要用$来代替; ColdFusion用户在查找和替换操作里都必须使用\。提示 正如你在上面这个例子里看到的那样同一个子表达式可以被引用任意多次——只要在需要用到它的地方写出它的回溯引用就行了。我们再来看一个例子。在一个用来保存用户信息的数据库里电话号码被保存为313-555-1234。现在你需要把电话号码重新排版为313555-1234。下面就是这个例子文本313-555-1234248-555-9999810-555-9000正则表达式(\d{3})(-)(\d{3})(-)(\d{4})替换($1)$3-$5结果(313)555-1234(248)555-9999(810)555-9000和刚才一样这里也使用了两个正则表达式模式。第1个模式看起来很复杂我们来分析一下。\d{3}-\d{3}-\d{4}用来匹配一个电话号码它被划分为5个子表达式5个组成部分第1个子表达式\d{3}匹配前3位数字第2个子表达式-匹配-字符等等。最终的结果是一个电话号码被划分成了5个部分每个部分分别对应着一个子表达式区号、一个连字符、电话号码的前3位数字、又一个连字符、电话号码的后4位数字。这5个部分都可以单独拿出来使用负责重新排版电话号码的替换模式$1$3-$5只用到了它们当中的3个剩下的两个没有用到但这已足以把313-555-1234转换为313555-1234。3.1、大小写转换有些正则表达式实现允许我们使用表列出的元字符对字母进行大小写转换。\l和\u只能把下一个字符或子表达式转换为小写或大写。\L和\U将把它后面的所有字符转换为小写或大写直到遇上\E为止。下面是一个简单的例子把一级标题H1…/H1的标题文字转换为大写文本BODYH1Welcome to my Homepage/H1Content is divided into two sections:BRH2ColdFusion/H2Information about Macromedia ColdFusion.H2Wireless/H2Information about Bluetooth, 802.11, and more.H2This is not valid HTML/H3/BODY正则表达式([Hh]1)(.*?)(\/[Hh]1)替换$1\U$2\E$3结果BODYH1WELCOMETOMYHOMEPAGE/H1Content is divided into two sections:BRH2ColdFusion/H2Information about Macromedia ColdFusion.H2Wireless/H2Information about Bluetooth,802.11,and more.H2This is not validHTML/H3/BODY模式[Hh]1.*?/[Hh]1把一级标题分成了3个子表达式开始标签、标题文字、结束标签。第2个模式再把文本重新组合起来$1包含着开始标签\U$2\E把第2个子表达式标题文字转换为大写$3包含着结束标签。