指南 5 - 捕捉正则表达式组

捕捉组

Groovy最有用的特征之一,就是使用正则表达式来从一个正则表达式中“捕捉”出数据的能力。假设我们想从下面的数据中提取出“Liverpool, England”的位置数据:

locationData = "Liverpool, England: 53° 25? 0? N 3° 0? 0?"

我们可以使用字符串的split()函数,然后遍历结果,并剥去Liverpool和England之间的逗号,以及所有特殊的位置字符。或者我们可以通过一个正则表达式在一步中完成所有这些事情。完成这件事情的语法有一点奇怪,首先,我们必须定义一个正则表达式,并把所有我们感兴趣的部分放在圆括号中。

myRegularExpression = /([a-zA-Z]+), ([a-zA-Z]+): ([0-9]+). ([0-9]+). ([0-9]+). ([A-Z]) ([0-9]+). ([0-9]+). ([0-9]+)./

然后,我们需要定义一个“matcher”,它是通过=~操作符得到的:

matcher = ( locationData =~ myRegularExpression )

变量matcher包含了一个经过groovy增强的java.util.regex.Matcher。你可以像在Java中一样从一个Matcher对象中访问你的数据。获取数据的一个更groovy的方法是,把matcher当作一个数组来使用——准确来说是一个二维数组。简单来说,一个二维数组就是一个数组的数组。在这种情况下,数组的第一“维”表示正则表达式对字符串的各种匹配方案。在这个例子中,正则表达式只匹配了一次,所以在这个二维数组的第一维中只有一个元素。考虑下面的代码:

matcher[0]

这个表达式的结果为:

["Liverpool, England: 53° 25? 0? N 3° 0? 0?", "Liverpool", "England", "53", "25", "0", "N", "3", "0", "0"]

然后我们使用数组的第二维来访问我们感兴趣的捕捉组:

if (matcher.matches()) {
	println(matcher.getCount()+ " occurrence of the regular expression was found in the string.");
	println(matcher[0][1] + " is in the " + matcher[0][6] + " hemisphere. (According to: " + matcher[0][0] + ")")
}

注意,我们使用正则表达式的附加好处是,我们可以知道数据是否是格式良好的(well-formed)。也就是说,如果locationData包含了字符串“Could not find location data for Lima, Peru”,那么if语句将不会被执行。

非匹配组

有时候,使一个表达式成为一个组,而不把它标识为一个捕捉组,是可取的。你可以通过把表达式包围在圆括号中并以?:作为头两个字符来实现。例如,如果我们想重新调整一些人的名字的格式,并且忽略中名(middle name)——如果有的话,那么我们可以:

names = [
    "Graham James Edward Miller",
    "Andrew Gregory Macintyre"
]
 
printClosure = {
	matcher = (it =~ /(.*?)(?: .+)+ (.*)/);  // 注意中间的非匹配组
	if (matcher.matches())
		println(matcher[0][2]+", "+matcher[0][1]);
}
names.each(printClosure);

输出为:

Miller, Graham
Macintyre, Andrew

那样的话,我们总知道姓是第三个1)匹配组。

替换

你可以使用正则表达式完成的更简单但更有用的事情之一,就是用一个字符串替换匹配的部分。你可以使用java.util.regex.Matcher(“myMatcher = ("a" += /b/);”这个语句得到的就是这个类型的对象)的replaceFirst()和replaceAll()函数来实现。

假设我们想替换所有Harry Potter的名字,从而使我们可以把J.K. Rowlings的书当作Tanya Grotter的小说来重新出售(是的,有人尝试过,如果你不相信我的话可以Google一下)。

excerpt = "At school, Harry had no one. Everybody knew that Dudley's gang hated that odd Harry Potter "+
          "in his baggy old clothes and broken glasses, and nobody liked to disagree with Dudley's gang.";
matcher = (excerpt =~ /Harry Potter/);
excerpt = matcher.replaceAll("Tanya Grotter");
 
matcher = (excerpt =~ /Harry/);
excerpt = matcher.replaceAll("Tanya");
println("Publish it! "+excerpt);

在这个例子中,我们通过两步来完成,一步是替换Harry Potter的全名,一步是替换他的名字。

Reluctant操作符

操作符?、+和*都是默认为“greedy”。也就是说,它们会尝试匹配尽可能多的输入。有时候这并不是我们想要的。考虑下面的五世纪教皇的列表:

popesArray = [
    "Pope Anastasius I 399-401",
    "Pope Innocent I 401-417",
    "Pope Zosimus 417-418",
    "Pope Boniface I 418-422",
    "Pope Celestine I 422-432",
    "Pope Sixtus III 432-440",
    "Pope Leo I the Great 440-461",
    "Pope Hilarius 461-468",
    "Pope Simplicius 468-483",
    "Pope Felix III 483-492",
    "Pope Gelasius I 492-496",
    "Pope Anastasius II 496-498",
    "Pope Symmachus 498-514"
]

下面是尝试解析各个教皇的名字(不包含序号或修饰语)和年份的第一个正则表达式:

/Pope (.*)(?: .*)? ([0-9]+)-([0-9]+)/

把这个正则表达式拆分为:

/ Pope (.*) (?: .*)? ([0-9]+) - ([0-9]+) /
表达式的开始 Pope 捕捉一些字符 非捕捉组:空格和一些字符 捕捉一个数 - 捕捉一个数 表达式的结束

我们希望第一个捕捉组只包含各个的教皇的名字,但是就如同它的结果所示,它捕捉了太多内容。例如,第一个教皇分解为:

/ Pope (.*) (?: .*)? ([0-9]+) - ([0-9]+) /
表达式的开始 Pope Anastasius I 399 - 401 表达式的结束

明显,第一个捕捉组捕捉了太多的内容。我们只想它捕捉Anastasius,而修饰语应该被第二个捕捉组捕捉。解决这个问题的另外一种方法是,第一个捕捉组应该捕捉尽可能少的输入,从而使后面的部分仍旧可以得到一个匹配。在这个例子中,它会匹配任何字符,直到下一个空格。Java的正则表达式允许我们通过*、+和?操作符的“reluctant”版本来这么做。为了使这些操作符的任意一个变为reluctant,只需要简单的在它的后面添加一个?(也就是*?、+?和??)。所以我们新的正则表达式为:

/Pope (.*?)(?: .*)? ([0-9]+)-([0-9]+)/

现在让我们通过输入中最复杂的一个——教皇Hilarius(一个喜欢开玩笑的人)前面的一个——来看一下我们新的正则表达式,分解如下:

/ Pope (.*?) (?: .*)? ([0-9]+) - ([0-9]+) /
表达式的开始 Pope Leo I the Great 440 - 461 表达式结束

这就是我们想要的。

要测试这个正则表达式,我们会使用这段代码:

popesArray = [
    "Pope Anastasius I 399-401",
    "Pope Innocent I 401-417",
    "Pope Zosimus 417-418",
    "Pope Boniface I 418-422",
    "Pope Celestine I 422-432",
    "Pope Sixtus III 432-440",
    "Pope Leo I the Great 440-461",
    "Pope Hilarius 461-468",
    "Pope Simplicius 468-483",
    "Pope Felix III 483-492",
    "Pope Gelasius I 492-496",
    "Pope Anastasius II 496-498",
    "Pope Symmachus 498-514"
]
 
myClosure = {
	myMatcher = (it =~ /Pope (.*?)(?: .*)? ([0-9]+)-([0-9]+)/);
	if (myMatcher.matches())
		println(myMatcher[0][1]+": "+myMatcher[0][2]+" to "+myMatcher[0][3]);
}
popesArray.each(myClosure);

同样,试试用原来的正则表达式运行这段代码,来看看哪些输出是错误的。

1) 译者注:原文为第二个,但是下标为2的应该是第三个
 
wiki/getting_started_guide/tutorial_5.txt · 最后更改: 2008-08-25 12:20 由 johnny
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki