`
searun
  • 浏览: 174015 次
  • 性别: Icon_minigender_1
  • 来自: 合肥
社区版块
存档分类
最新评论

[跟我学正则表达式] 8. 使用后向引用

阅读更多

前一章中介绍了如何使用子表达式将字符分成组。这种分组的主要用途之一是可以控制组的重复次数(在前一章中已经演示过)。本章中将介绍子表达式一个更重要的用法——使用后向引用。

 

理解后向引用

理解后向引用需求的最好办法是看一个例子。 HTML 开发者经常使用段落标签( <H1> <H6> ,包括相应的结束标签)来定义 Web 页面的提纲。假设你需要定位所有的段落标签,而不管相应的段落级别。下面是一个例子:

文本
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
</BODY>

正则表达式
<[hH]1>.*</[hH]1>

结果
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
</BODY>
分析

这里的模式“<[hH]1>.*</[hH]1>”匹配了第一个段落(从 <H1> </H1> ),同样可以匹配 <h1> HTML 不是大小写敏感的)。但是什么模式可以匹配所有的六种段落(六种中的任何一种都是合法的)?

 

一种方案是将前面的 1 换成数字区间,如下面所示:

文本
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
</BODY>

正则表达式
<[hH][1-6]>.*?</[hH][1-6]>

结果
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
</BODY>

分析

这看起来可以工作:“<[hH][1-6]>”可以匹配任何开始的段落标签(在例子中包括<H1>和<H2>),而“<[hH][1-6]>”可以匹配所有的结束标签(在例子中为< / H1>和< / H2>)。

 

注意:我们这里使用了“.*?”而不是“.* ”。正如在第五章解释的一样,如“ * ”的量词是贪婪的,所以模式“<[hH][1-6]>.*</[hH][1-6]>”将匹配从第二行中的 <H1> 直到第六行中的 </H2> 。可以使用非贪婪量词“ .*? ”来解决这个问题。

 

我说的是可能,而不是可以,因为这个特定的例子中即使是使用贪婪量词也是可以解决问题的。因为这里的元字符“ . ”不能匹配换行符,而在这个例子中,段落标签都是位于单独的行中。但是这里使用非贪婪的量词匹配符是没有副作用的,最好使用安全的模式。

成功了吗?还没有。看看下面的例子(使用了同样的模式):

文本
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>

正则表达式
<[hH][1-6]>.*?</[hH][1-6]>

结果
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>

分析

采用 <H2> 开始而采用 </H3> 的段落标题标签是非法的,但是现在的模式可以匹配。

 

这里的问题在于匹配的第二个部分(匹配结束的标签)没有办法知道匹配第一部分的知识(匹配开始的标签)。这也是为什么后向引用重要的原因。

 

使用后向引用匹配

在后面将会重新来看上面的例子。现在我们来看一个简单的例子,一个如果不使用后向引用就不能解决问题的例子。

 

假设现在有一段文本,你希望找到所有重复的单词(笔误使得单词出现了两次)。很显然,在搜索单词的第二次出现的时候,必须首先知道此单词。后向引用允许正则表达式模式参照前面的匹配内容(在这个例子中,就是第一次匹配的单词)。

理解这个特性的最好方式就是看看它的使用。下面的文本中包含了三组需要定位的重复单词:

文本
This is a block of of text,
several words here are are
repeated, and and they
should not be.

正则表达式
[ ]+(\w+)[ ]+\1

结果
This is a block of of text,
several words here are are
repeated, and and they
should not be.

分析

此模式可以工作,但是为什么可以工作呢?“[ ]+ ”匹配一个或者更多空格,“\w+”匹配一个或者更多的文字数字式字符,而“[ ]+”则用来匹配尾部的空格。但是注意到这里的“ \w+ ”加上了括号使其成为子表达式。此子表达式并不是用于重复匹配,而且本例中也不需要重复。这里的子表达式仅仅是对表达式进行分组,标记此子表达式供以后使用。模式的最后部分是“ \1 ”,这是对子表达式的后向引用,所以当“ \w+ ”匹配了单词 of ,“ \1 ”也将匹配 of ,当“ \w+ ”匹配了单词 and ,“ \1 ”也将匹配 and

 

注意:术语后向应用是因为这些实体将引用以前的子表达式。

但是“ \1 ”的实际含义是什么呢?它匹配模式中第一个子表达式。同理,“ \2 ”将匹配第二个子表达式,“ \3 ”将匹配第四个,依此类推。“[ ]+(\w+)[ ]+\1”因此将可以匹配所有重复出现的单词。

提示:你可以将后向应用理解成变量。

现在你已经看到了后向引用的用法,再来看看前面的 HTML 例子。使用后向引用,可以创建一个模式用来匹配开始标签和结束标签(忽略所有不匹配的标签对)。下面是这个例子:

文本
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>

正则表达式
<[hH]([1-6])>.*?</[hH]\1>

结果
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>

分析

同样的,在这里找到了三个匹配:一个<H1> 对和两个 <H2>对。就像以前一样,“<[hH]([1-6])>”将匹配任何的段落标签。但是和以前不一样的是,这里的“ [1-6] ”使用了小括号括起来成为了子表达式。这样,匹配结束标签的模式可以通过“</[hH]\1>”中的“ \1 ”来引用此子表达式。“ (1-6) ”是一个可以匹配数字 1 6 的子表达式,“ \1 ”因此可以匹配相同的数字。在这种情况下,“<H2>This is not valid HTML</H3>”将不能匹配。

 

笔记:非常遗憾的是,后向引用语法在不同的正则表达式实现中是不一样的。 JavaScript 中使用 \ 来表示后向引用(除了 $ 使用时的替换操作),Macromedia ColdFusion 和vi也是这样。 Perl 语言使用的是 $ (所以 $1 表示这里的 \1 )。 .NET 正则表达式支持返回一个包含匹配名为 Groups 属性的对象,所以 C# 中的match.Groups[1]将引用第一个匹配,Visual Basic .NET中的match.Groups(1)将引用第一个匹配。 PHP 通过名为 $matches 的数组返回此信息,所以 $matches[1] 引用第一个匹配(尽管可以通过标志来改变)。在 JAVA Python 语言中则返回包含一个数组名为 group 的匹配对象。

具体的正则表达式实现相关信息可以参看附录 1 :流行应用和语言中的正则表达式。

注意:后向引用只能够引用子表达式(需要使用小括号括起来)。

提示:引用的匹配一般是从 1 开始。在大多数的实现中,匹配 0 可以用来引用整个表达式。

笔记:正如你所看到的,子表达式是通过相对位置来引用的: \1 引用第一个, \5 引用第五个,等等。尽管获得了广泛的支持,这个语法有着一个严重的问题:移动或者修改子表达式(也因此改变了子表达式的顺序)将会破坏模式,增加或者删除子表达式将会带来更大的问题。为了能够克服这个缺点,现在有些新的正则表达式实现支持命名引用,也就是说为每个可能引用的子表达式给定一个唯一的名称,在以后可以通过此名称来引用(而不是相对位置)。命名引用在本书中并没有包含,因为这还不是一个广泛支持的特性,而且支持此特性的正则表达式实现的语法都很不一样。尽管如此,如果你使用的应用或者语言支持命名引用的话(如 .NET ),最好是利用这种特性的好处。

 

执行替换操作

到现在为止我们所看到的正则表达式都是进行搜索——在一段文本中定位单词。事实上,可能确实大部分的正则表达式都是用来进行搜索。但是这不是正则表达式可以做的所有事情——正则表达式还可以用来执行替换操作。举个例子,将 CA 替换成California和将MI替换成Michigan 并不是正则表达式需要完成的工作。尽管使用正则表达式也是合法的,但是没有必要这么做。事实上,在这里如果使用简单的字符串操作函数的话过程将会变得更加容易。

正则表达式只有在使用后向引用的时候才变得有竞争性。下面是在第五章中使用过的例子:

文本
Hello, ben@forta.com is my email address.

正则表达式
\w+[\w\.]*@[\w\.]+\.\w+

结果
Hello, ben@forta.com is my email address.

分析

这个模式匹配了一段文本中的邮件地址(已经在第五章中进行了解释)。

 

但是如果你现在希望将所有的邮件地址修改为可点击的该怎么做?在 HTML 语言中可以使用“<A HREF=" mailto:user@address.com">user@address.com</A >” 来创建一个可点击的邮件地址。是否可以通过正则表达式改变为可点击的邮件地址?事实上,是而且非常简单(当你知道如何使用后向引用的时候)。

文本
Hello, ben@forta.com is my email address.

正则表达式
(\w+[\w\.]*@[\w\.]+\.\w+)

替换

<A HREF="mailto:$1">$1</A >

 

结果

Hello, <A HREF="mailto:ben@forta.com">ben@forta.com</A >
is my email address.

分析

在替换操作中,将使用两个正则表达式:其中一个指定搜索模式,第二个指定需要匹配的文本。后向引用可能会跨越模式,所以在第一个模式中匹配的子表达式将会用于第二个模式中。“(\w+[\w\.]*@[\w\.]+\.\w+)”和前面的模式是一样的(定位邮件地址),但是这里指定了一个子表达式。这样匹配的文本将可以用于替换模式。“<A HREF=" mailto:$1">$1</A > ” 使用了匹配的子表达式两次——第一个用为 HREF 的属性(定义 mailto: ),后面则定义了可点击文本。所以,“ ben@forta.com ”将变为“<A HREF=" mailto:ben@forta.com">ben@forta.com</A >”,这也是我们所需要的。

 

注意:前面已经提到过,你可能需要根据实现来修改后向引用。在这里,JavaScript用户将使用 $ 来替代前面的 \ 。而对于ColdFusion 用户则可以使用 \ 来执行搜索和替换操作。

提示:就像在这个例子中看到的,一个子表达式可以通过后向引用根据需要简单的引用多次。

让我们再来看一个例子。用户信息保存在数据库中,其中电话号码使用“313-555-1234”的格式保存。现在,需要重新格式化电话号码为“(313) 555-1234”。下面是这个例子:

文本
313-555-1234
248-555-9999
810-555-9000

正则表达式
(\d{3})(-)(\d{3})(-)(\d{4})

替换
($1) $3-$5

结果
(313) 555-1234
(248) 555-9999
(810) 555-9000

分析

同样的,这里使用了两个正则表达式模式。第一个看起来很复杂,实际上是比较简单的。“(\d{3})(-)(\d{3})(-)(\d{4})”匹配了一个电话号码,并分成了五个子表达式(五个部分)。“(\d{3})”匹配刚开始的三个数字并作为第一个子表达式,“ (-) ”匹配“ - ”并作为第二个子表达式,依此类推。最后的结果是将此电话号码分为了五个部分(每个部分都是一个子表达式):区域码,连字符,号码前三个数字,连字符,号码后四个数字。这四个部分可以根据需要单独引用,所以“($1) $3-$5”只是使用了其中的三个子表达式,而忽略了另外的两个,因此“313-555-1234”改变为了“(313) 555-1234”。

 

提示:在对文本重新格式化的时候,一般来说会将文本变为许多小的子表达式,这可以更好地控制文本。

 

改变大小写

有些正则表达式实现还支持通过表 8.1 中的元字符来改变大小写。

 

元字符

描述

\E

终止\L 或者 \U 的转换

\l

将接下来的字符改为小写

\L

将接下来的所有字符改为小写直到遇到 \E

\u

将接下来的字符改为大写

\U

将接下来的所有字符改为大写直到遇到 \E

表8.1. 改变大小写的元字符

 

\l \u 放置在字符或者子表达式之前用来转换接下来的字符大小写。 \L \U 用来转会接下来的所有字符大小写直到遇到 \E 。下面是一个简单的例子,将 <H1> 标签对中的文字改为大写:

文本
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>

正则表达式
(<[Hh]1>)(.*?)(</[Hh]1>)

替换
$1\U$2\E$3

结果
<BODY>
<H1>WELCOME TO MY HOMEPAGE</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>

分析

此模式“(<[Hh]1>)(.*?)(</[Hh]1>)”将段落标签内容分解成三个部分:开始标签、文本和结束标签。第二个模式中将这些内容放在了一起: $1 包含了开始标签,“ \U$2\E”转换了第二个子表达式到其大写形式, $3 中包含了结束标签。

 

小结

子表达式用来定义了一组字符。除了可以用来进行重复匹配以外(在前一章中已经演示过),子表达式还可以用来引用。这种引用被称为后向引用(非常遗憾的是,后向引用的语法并不相同)后向引用在文本匹配和替换操作中都很有用。

 

亦歌亦行 @ http://searun.iteye.com

分享到:
评论
1 楼 jimode2013 2013-09-11  
您好,请问改变大小写的元字符在javascript中支持吗?

相关推荐

Global site tag (gtag.js) - Google Analytics