Skip to end of metadata
Go to start of metadata

捕获组(Capture groups)

Groovy 中最有用的功能之一是能够使用正则表达式来“抓取”正则表达式在(应该是文本中吧)的数据。比如我们想从以下数据中提取英国利物浦的位置数据:

可以用 string 的 split() 函数,然后通过 Liverpool 和 England 之间的逗号剥离字符串,还要处理所有表示位置的字符。或者我们使用正则表达式一步搞定。实现的语法有点怪。首先,需要定义一个正则表达式,把任何我们感兴趣的信息放在括号中。

接下来,再使用 =~ 操作符定义一个“matcher”:

matcher 变量包含一个由 groovy 增强的 java.util.regex.Matcher。你完全可以像在 Java 中使用 Matcher 对象那样处理数据。要想 Groovy 化的使用 matcher 来处理数据,那就是用数组(准确的说是一个二维数组)。二维数组简单地说就是一个数组的数组。在这里,数组的第一“维”对应的是字符串中对正则表达式的每一次匹配。本例中,正则表达式只匹配了一次,所以在二维数组的第一维中只有一个条目。注意以下代码:

这个表达式可以求值为:

这样,就可以使用数组的第二维来访问捕获组,这才是我们感兴趣的数据:

注意,使用正则表达式提取数据的好处是我们可以检查数据的格式是否良好(或者说数据格式是否符合要求)。也就是说,如果 locationData 是“Could not find location data for Lima, Peru”字符串,“if” 中的语句是不会执行的。

非匹配组(Non-matching Groups)

有时需要用标识一个组,但它又不作为“捕获组”。可以用“?:”开头并用括号括住的表达式来实现。例如,想要改变人名的格式,忽略中间名(middle name),可以:

输出:

这样,我们总是能通过第二个匹配组得到姓氏。

替换(Replacement)

使用正则表达式的一个最简单也是最有用的功能就是替换字符串中匹配的部分。可以通过 java.util.regex.Matcher(这是你通过类似这样“myMatcher = ("a" += /b/);”的方式得到的对象的类) 的 replaceFirst() 和 replaceAll() 来做到这一点。

比如我们要替换所有出现的“哈里·波特”名字,这样就可以把 J·K·罗琳 的书转售为 塔妮亚·格勒特 小说(真有人这么干,不信你去 Google 一下)。

在这种情况下,应该分两步做,一个是哈里·波特的全名,另一个是他的名字。

勉强操作符(Reluctant Operators)

“? + *” 操作符默认是“贪婪”的。也就是说,他们尽可能多的尝试匹配更多的字符。有时,这并不是我们想要的。注意下面十五世纪教皇的列表:

第一个尝试的正则表达式是取出名字(不包括序号和修饰语)和每个教皇的年代,表达式可能是这样:

拆分:

/

Pope

(.*)

(?: .*)?

([0-9]+)

-

([0-9]+)

/

表达式开始

Pope

捕获一些字符

非捕获组:空格和其它一些字符

捕获数字

-

捕获数组

表达式结束

我们希望第一个捕获组只匹配教皇的名字,但事实证明,它捕获了过多的内容。例如第一个教皇的信息拆分为:

/

Pope

(.*)

(?: .*)?

([0-9]+)

-

([0-9]+)

/

表达式开始

Pope

Anastasius I

 

399

-

401

表达式结束

很明显,第一个捕获组匹配了太多内容。我们只想匹配“Anastasius”,修饰语应该在第二个捕获组匹配。另一种方式是第一个捕获组尽可能少的匹配数据,以使后面的内容仍可匹配。在这种情况下,应该匹配到下个空格前的任何字符。Java 正则表达式允许使用“勉强”版的 “* + ?” 操作符。为了使这些操作符中的一个是“勉强”的,只需要在后面加一个“?”(成为 *? +? 和 ??)。所以新的正则表达式应该是:

现在来看新表达式处理最复杂的输入,也就是 hilarius 教皇(真是一个爱开玩笑的人)前面那个,拆分为:

/

Pope

(.*?)

(?: .*)?

([0-9]+)

-

([0-9]+)

/

表达式开始

Pope

Leo

I the Great

440

-

461

表达式结束

这正是我们想要的。

完整测试代码应该是:

也可以用原先的正则表达式运行这段代码,可以看到支离破碎的输出。

  • No labels