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的全名,一步是替换他的名字。
操作符?、+和*都是默认为“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);
同样,试试用原来的正则表达式运行这段代码,来看看哪些输出是错误的。