Jakarta-ORO正则表达式库

1.简介:

Jakarta-ORO是最全面以及优化得最好的正则表达式API之一,Jakarta-ORO库以前叫做OROMatcher,是由DanielF. Savarese编写,后来他将其赠与JakartaProject,读者可在Apache.org的网站下载API包。

许多源代码开放的正则表达式库都是支持Perl5兼容的正则表达式语法,Jakarta-ORO正则表达式库也不例外,他与Perl5正则表达式完全兼容。

2.对象与其方法:

★PatternCompiler对象:
我们在使用Jakarta-OROAPI包时,最先要做的是,创建一个Perl5Compiler类的实例,并把它赋值给PatternCompiler接口对象。Perl5CompilerPatternCompiler接口的一个实现,允许你把正则表达式编译成用来匹配的Pattern对象。

PatternCompilercompiler=new Perl5Compiler();

Pattern对象:
要把所对应的正则表达式编译成Pattern对象,需要调用compiler对象的compile()方法,并在调用参数中指定正则表达式。举个例子,你可以按照下面这种方式编译正则表达式"s[ahkl]y"

        Pattern pattern=null;
        try {
                pattern=compiler.compile("s[ahkl]y ");
        } catch (MalformedPatternException e) {
                e.printStackTrace();
        }

在默认的情况下,编译器会创建一个对大小写敏感的模式(pattern)。因此,上面代码编译得到的模式只匹配"say""shy""sky""sly",但不匹配"Say""skY"。要创建一个大小写不敏感的模式,你应该在调用编译器的时候指定一个额外的参数:
pattern=compiler.compile("s[ahkl]y",Perl5Compiler.CASE_INSENSITIVE_MASK);

Pattern对象创建好之后,就可以通过PatternMatcher类用该Pattern对象进行模式匹配。

PatternMatcher对象:

PatternMatcher对象依据Pattern对象和字符串展开匹配检查。你要实例化一个Perl5Matcher类并把结果赋值给PatternMatcher接口。Perl5Matcher类是PatternMatcher接口的一个实现,它根据Perl5正则表达式语法进行模式匹配:
PatternMatchermatcher=new Perl5Matcher();

PatternMatcher对象提供了多个方法进行匹配操作,这些方法的第一个参数都是需要根据正则表达式进行匹配的字符串:

  1. booleanmatches(String input, Patternpattern):当要求输入的字符串input和正则表达式pattern精确匹配时使用该方法。也就是说当正则表达式完整地描述输入字符串时返回真值。

  2. booleanmatchesPrefix(String input, Patternpattern):要求正则表达式匹配输入字符串起始部分时使用该方法。也就是说当输入字符串的起始部分与正则表达式匹配时返回真值。

  3. booleancontains(String input, Patternpattern):当正则表达式要匹配输入字符串的一部分时使用该方法。当正则表达式为输入字符串的子串时返回真值。

但以上三种方法只会查找输入字符串中匹配正则表达式的第一个对象,如果当字符串可能有多个子串匹配给定的正则表达式时,那么你就可以在调用上面三个方法时用PatternMatcherInput对象作为参数替代String对象,这样就可以从字符串中最后一次匹配的位置开始继续进行匹配,这样就方便的多了。

PatternMatcherInput对象作为参数替代String时,上述三个方法的语法如下:

  1. booleanmatches(PatternMatcherInput input, Pattern pattern)

  2. booleanmatchesPrefix(PatternMatcherInput input, Pattern pattern)

  3. booleancontains(PatternMatcherInput input, Pattern pattern)

Util.substitute()方法:
查找后需要要进行替换,我们就要用到Util.substitute()方法,其语法如下:

public static String substitute(PatternMatcher matcher,
       Pattern pattern,Substitution sub,String input,
       int numSubs)

前两个参数分别为PatternMatcherPattern对象。而第三个参数是个Substiution对象,由它来决定替换操作如何进行。第四个参数是要进行替换操作的目标字符串,最后一个参数用来指定是否替换模式的所有匹配子串(Util.SUBSTITUTE_ALL),或只进行指定次数的替换。

在这里我相信有必要详细解说一下第三个参数Substiution对象,因为它将决定替换将怎样进行。

Substiution:
Substiution
是一个接口类,它为你提供了在使用Util.substitute()方法时控制替换方式的手段,它有两个标准的实现类:StringSubstitutionPerl5Substitution。当然,同时你也可以生成自己的实现类来定制你所需要的特殊替换动作。

StringSubstitution
StringSubstitution
实现的是简单的纯文字替换手段,它有两个构造方法:

StringSubstitution()->缺省的构造方法,初始化一个包含零长度字符串的替换对象。

StringSubstitution(java.lang.Stringsubstitution)->初始化一个给定字符串的替换对象。

Perl5Substitution
Perl5Substitution
StringSubstitution的子类,它在实现纯文字替换手段的同时也允许进行针对MATH类里各匹配组的PERL5变量的替换,所以他的替换手段比其直接父类StringSubstitution更为多元化。

它有三个构造器:

Perl5Substitution()

Perl5Substitution(java.lang.Stringsubstitution)

Perl5Substitution(java.lang.Stringsubstitution, int numInterpolations)

前两种构造方法与StringSubstitution一样,而第三种构造方法下面将会介绍到。

Perl5Substitution的替换字符串中可以包含用来替代在正则表达式里由小扩号围起来的匹配组的变量,这些变量是由$1,$2,$3等形式来标识。我们可以用一个例子来解释怎样使用替换变量来进行替换:

假设我们有正则表达式模式为b/d+:(也就是b[0-9]+:),而我们想把所有匹配的字符串中的"b"都改为"a",""则改为"-",而其余部分则不作修改,如我们输入字符串为"EXAMPLEb123:",经过替换后就应该变成"EXAMPLEa123-"。要做到这点,我们就首先要把不做替换的部分用分组符号小括号包起来,这样正则表达式就变为"b(/d+):",而构造Perl5Substitution对象时其替换字符串就应该是"a$1-",也就是构造式为Perl5Substitution"a$1-"),表示在使用Util.substitute()方法时只要在目标字符串里找到和正则表达式"b(/d+):"相匹配的子串都用替换字符串来替换,而变量$1表示如果和正则表达式里第一个组相匹配的内容则照般原文插到$1所在的为置,如在"EXAMPLEb123"中和正则表达式相匹配的部分是"b123",而其中和第一分组"(/d+)"相匹配的部分则是"123",所以最后替换结果为"EXAMPLEa123-"

有一点需要清楚的是,如果你把构造器Perl5Substitution(java.lang.Stringsubstitution,int numInterpolations)

中的numInterpolations参数设为INTERPOLATE_ALL,那么当每次找到一个匹配字串时,替换变量($1$2等)所指向的内容都根据目前匹配字串来更新,但是如果numInterpolations参数设为一个正整数N时,那么在替换时就只会在前N次匹配发生时替换变量会跟随匹配对象来调整所代表的内容,但N次之后就以一致以第N次替换变量所代表内容来做为以后替换结果。

举个例子会更好理解:

假如沿用以上例子中的正则表达式模式以及替换内容来进行替换工作,设目标字符串为"Tankb123: 85 Tank b256: 32 Tank b78:22",并且设numInterpolations参数为INTERPOLATE_ALL,而Util.substitute()方法中的numSub变量设为SUBSTITUTE_ALL(请参考上文Util.substitute()方法内容),那么你获得的替换结果将会是:
Tanka123- 85 Tank a256- 32 Tank a78- 22

但是如果你把numInterpolations设为2,并且numSubs依然设为SUBSTITUTE_ALL,那么这时你获得的结果则会是:
Tanka123- 85 Tank a256- 32 Tank a256- 22

你要注意到最后一个替换所用变量$1所代表的内容与第二个$1一样为"256",而不是预期的"78",因为在替换进行中,替换变量$1只根据匹配内容进行了两次更新,最后一次就使第二次匹配时所更新的结果,那么我们可以由此知道,如果numInterpolations设为1,那么结果将是:
Tanka123- 85 Tank a123- 32 Tank a123- 22

3.应用示例:

刚好前段时间公司准备出一个《伊索预言》的英语学习互动教材,其中有电子档资料的整理工作,我们就以此为例来看一下Jakarta-OROJDBC2.0API结合起来对数据库内的资料进行简单提取与整理的实现。假设由录入部的同事送过来的存放在MSSQLSERVER 7数据库里的电子档的表结构如下(注:或许在不同的DBMS中有相应的正则表达式的应用,但这不在本文讨论范围内):

表名:AESOP,表中每条记录包含有三列:
ID
int):单词索引号
WORD
varchar):单词
CONTENT(varchar)
:存放单词的相关解释与例句等内容

其中CONTENT列中内容的格式如下:
[
音标][词性](解释){(例句一/例句解释/例句中该词的词性:单词在句中的意思)(例句二/例句解释/例句中该词的词性:单词在句中的意思)}

如对应单词Kevin,CONTENT中的内容如下:
['kevin][
名词](人名凯文){(Kevinloves comic./凯文爱漫画/名词:凯文)(Kevin is living in ZhuHai now./凯文现住在珠海/名词:凯文)}

我们的例子主要针对CONTENT列中内容进行字符串处理。

查找单个匹配:
首先,让我们尝试把CONTNET列中的[音标]字段的内容列示出来,由于所有单词的记录中都有这一项并且都在字串开始位置,所以这个查找工作比较简单:

  1. 确定相应的正则表达式:/[[^]]+/]

这个是很简单的正则表达式,其意思是要求相匹配的字符串必须为以一对中括号包含的所有内容,如['kevin][名词]等,但内容中不包括"]"符号,也就是要避免出现"[][]"会作为一个匹配对象的情况出现(有关正则表达式的基础知识请参照有关资料,这里不再详述)。

注意,在Java中,你必须对每一个向前的斜杠("/")进行转义处理。所以我们要在上面的正则表达式里每个"/"前面加上一个"/"以免出现编译错误,也就是在JAVA中初始化正则表达式的字符串的语句应该为:

Stringrestring=" //[[^]]+//]";

并且在表达式里每个符号中间不能有空格,否则就会同样出现编译错误。

  1. 实例化PatternCompiler对象,创建Pattern对象

PatternCompilercompiler=new Perl5Compiler();

Patternpattern=compiler.compile(restring);

  1. 创建PatternMatcher对象,调用PatternMatcher接口的contain()方法检查匹配情况:

  PatternMatcher matcher=new Perl5Matcher();
        if (matcher.contains(content,pattern)) {
                 //处理代码片段
        }

这里matcher.contains(content,pattern)中的参数content是从数据库里取来的字符串变量。该方法只会查到第一个匹配的对象字符串,但是由于音标项均在CONETNET内容字符串中的起始位置,所以用这个方法就已经可以保证把每条记录里的音标项找出来了,但更为直接与合理的办法是使用booleanmatchesPrefix(PatternMatcherInput input, Patternpattern)方法,该方法验证目标字符串是否以正则表达式所匹配的字串为起始。

具体实现的完整的程序代码如下:

package RegularExpressions;
//import……
import org.apache.oro.text.regex.*;
//使用Jakarta-ORO正则表达式库前需要把它加到CLASSPATH里面,如果用IDE//JBUILDER,那么也可以在JBUILDER里直接自建新库。

public class yisuo{
  public static void main(String[] args){
  try{     
//使用JDBC DRIVER进行DBMS连接,这里我使用的是一个第三方JDBC 
//DRIVERMicrosoft本身也有一个面向SQLSERVER7/2000的免费JDBC 
//DRIVER,但其性能真的是奇差,不用也罢。
        Class.forName("com.jnetdirect.jsql.JSQLDriver");
          Connection con=DriverManager.getConnection
          ("jdbc:JSQLConnect://kevin:1433","kevin chen","re");
          Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
          ResultSet.CONCUR_UPDATABLE);
//为使用Jakarta-ORO库而创建相应的对象
String rsstring=" //[[^]]+//]"; 
          PatternCompiler orocom=new Perl5Compiler();
          Pattern pattern=orocom.compile(rsstring);
          PatternMatcher matcher=new Perl5Matcher();
          ResultSet uprs = stmt.executeQuery("SELECT * FROM aesop");
          while (uprs.next()) {
Stirng  word=uprs.getString("word");
          Stirng  content=uprs.getString("content");
            if(matcher.contains(content,pattern)){
          //if(matcher.matchesPrefix(content,pattern)){
                MatchResult result=matcher.getMatch();
                Stirng pure=result.toString();
                System.out.println(word+"的音标为:"+pure);
            }
          }
       }
  catch(Exception e) {
             System.out.println(e);
       }
  }
}

输出结果为:kevin的音标为['kevin]

在这个处理中我是用toString()方法来取得结果,但是如果正则表达式里是用了分组符号(圆括号),那么就可以用group(intgid)的方法来取得相应各组匹配的结果,如正则表达式改为"(/[[^]]+/])",那么就可以用以下方法来取得结果:pure=result.group(0);

用程序验证,输出结果同样为:kevin的音标为['kevin]

而如果正则表达式为(/[[^]]+/])(/[[^]]+/]),则会查找到两个连续的方括号所包含的内容,也就找到[音标][词性]两项,但是两项的结果分别在两个组里面,分别由下面语句获得结果:

result.group(0)->返回[音标][词性]两项内容,也就是与整个正则表达式相匹配的结果字符串,在这里也就为['kevin][名词]

result.group(1)->返回[音标]项内容,结果应是['kevin]

result.group(2)->返回[词性]项内容,结果应是[名词]

继续用程序验证,发现输出并不正确,主要是当内容有中文时就不能成功匹配,考虑到可能是Jakarta-ORO正则表达式库版本不支持中文的问题,回看一下原来我一直用的还是2.0.1的老版本,马上到Jakarta.org上下载最新的2.0.4版本装上再用程序验证,得出的结果就和预期一样正确。

查找多个匹配:
经过第一步的尝试使用Jakarta-ORO后,我们已经知道了如何正确使用该API包来查找目标字符串里一个匹配的子串,下面我们接着来看一看当目标字符串里包含不止一个匹配的子串时我们如何把它们一个接一个找出来进行相应的处理。

首先我们先试个简单的应用,假设我们想把CONTNET字段内容里所有用方括号包起来的字串都找出来,很清楚地,CONTNET字段的内容里面就只有两项匹配的内容:[音标][词性],刚才我们其实已经把它们分别找出来了,但是我们所用的方法是分组方法,把"[音标][词性]"作为一整个正则表达式匹配的内容先找到,再根据分组把[音标][词性]分别挑出来。但是现在我们需要做的是把[音标][词性]分别做为与同一个正则表达式匹配的内容,先找到一个接着再找下一个,也就是刚才我们的表达式为(/[[^]]+/])(/[[^]]+/],而现在应为"/[[^]]+/] "

我们已经知道在匹配操作的三个方法里只要用PatternMatcherInput对象作为参数替代String对象就可以从字符串中最后一次匹配的位置开始继续进行匹配,实现的程序片段如下:

PatternMatcherInput input=new PatternMatcherInput(content);
            while (matcher.contains(input,pattern)) {
                result=matcher.getMatch();
                System.out.println(result.group(0)) 
            }

输出结果为:['kevin]
[名词]

接着我们来做复杂一点的处理,就是我们要先把下面内容:
['kevin][
名词](人名凯文){(Kevinloves comic./凯文爱漫画/名词:凯文)(Kevin is living in ZhuHai now. /凯文现住在珠海/名词:凯文)}中的整个例句部分(也就是由大括号所包含的部分)找出来,再分别把例句一和例句二找出,而各例句中的各项内容(英文句、中文句、词性、解释)也要分项列出。

第一步当然是要定出相应的正则表达式,需要有两个,一是和整个例句部分(也就是由大括号包起来的部分)匹配的正则表达式:"/{.+/}",

另一个则要和每个例句部分匹配(也就是小括号中的内容),:/(([^)]+/)

而且由于要把例句的各项分离出来,所以要再把里面的各部分用分组的方法匹配出来:"([^(]+)/(.+)/(.+):([^)]+) "

为了简便起见,我们不再和从数据库里读出,而是构造一个包含同样内容的字符串变量,程序片段如下:

try{
         String content="['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词:凯文) (Kevin is living in ZhuHai now./凯文现住在珠海/名词: 凯文)}";
         String ps1="//{.+//}";
         String ps2="//([^)]+//)";
         String ps3="([^(]+)/(.+)/(.+):([^)]+)";
         String sentence;
         PatternCompiler orocom=new Perl5Compiler();
         Pattern pattern1=orocom.compile(ps1);
         Pattern pattern2=orocom.compile(ps2);
         Pattern pattern3=orocom.compile(ps3);
         PatternMatcher matcher=new Perl5Matcher();
//先找出整个例句部分
            if (matcher.contains(content,pattern1)) {
            MatchResult result=matcher.getMatch();
            String example=result.toString();
            PatternMatcherInput input=new PatternMatcherInput(example);
        //分别找出例句一和例句二
            while (matcher.contains(input,pattern2)){
                result=matcher.getMatch();
                sentence=result.toString();
        //把每个例句里的各项用分组的办法分隔出来
                if (matcher.contains(sentence,pattern3)){
                  result=matcher.getMatch();
                  System.out.println("英文句: "+result.group(1));
                  System.out.println("句子中文翻译: "+result.group(2));
                  System.out.println("词性: "+result.group(3));
                  System.out.println("意思: "+result.group(4));
                }
            }
        }
       }
  catch(Exception e) {
             System.out.println(e);
       }

输出结果为:
英文句:Kevin loves comic.
句子中文翻译:凯文爱漫画
词性:名词
意思:凯文
英文句:Kevin is living in ZhuHai now.
句子中文翻译:凯文现住在珠海
词性:名词
意思:凯文

查找替换:
以上的两个应用都是单纯在查找字符串匹配方面的,我们再来看一下查找后如何对目标字符串进行替换。

例如我现在想把第二个例句进行改动,换为:Kevinhas seenLEONsevealtimes,because it is a good film./凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。

也就是把
['kevin][
名词](人名凯文){(Kevinloves comic./凯文爱漫画/名词:凯文)(Kevin is living in ZhuHai now. /凯文现住在珠海/名词:凯文)}

改为:
['kevin][
名词](人名凯文){(Kevinloves comic./凯文爱漫画/名词:凯文)(Kevin has seenLEONsevealtimes,because it is a good film./凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}

之前,我们已经了解Util.substitute()方法与Substiution接口,以及Substiution的两个实现类StringSubstitutionPerl5Substitution,我们就来看看怎么用Util.substitute()方法配合Perl5Substitution来完成我们上面提出的替换要求,确定正则表达式:

我们要先找到其中的整个例句部分,也就是由大括号包起来的字串,并且把两个例句分别分组,所以正则表达式为:"/{(/([^)]+/))(/([^)]+/))/}",如果用替换变量来代替分组,那么上面的表达式可以看为"/{$1$2/}",这样就可以更容易看出替换变量与分组间的关系。

根据上面的正则表达式Perl5Substitution类可以这样构造:
Perl5Substitution("{$1(Kevin has seen
LEONsevealtimes,because it is a good film./凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}")

再根据这个Perl5Substitution对象来使用Util.substitute()方法便可以完成替换了,实现的代码片段如下:

try{
   String content="['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)(Kevin lives in ZhuHai now./凯文现住在珠海/名词: 凯文)}";
   String ps1="//{(//([^)]+//))(//([^)]+//))//}";
   String sentence;
   String pure;
   PatternCompiler orocom=new Perl5Compiler();
   Pattern pattern1=orocom.compile(ps1);
   PatternMatcher matcher=new Perl5Matcher();
       String result=Util.substitute(matcher,
        pattern1,new Perl5Substitution(
       "{$1( Kevin has seenLEONseveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}",1),
        content,Util.SUBSTITUTE_ALL);
        System.out.println(result);
   }
  catch(Exception e) {
             System.out.println(e);
       }



输出结果是正确的,为:
['kevin][
名词](人名凯文){(Kevinloves comic./凯文爱漫画/名词:凯文)(Kevin has seenLEONsevealtimes,because it is a good film./凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}

至于有关使用numInterpolations参数的构造器用法,读者只要根据上面的介绍自己动手试一下就会清楚了,在此就不再例述。

4.总结

本文首先介绍了Jakarta-ORO正则表达式库的对象与方法,并且接着举例让读者对实际应用有进一步的了解,虽然例子都比较简单,但希望读者们在看了该文后对Jakarta-ORO正则表达式库有一定的认知,在实际工作中有所帮助与启发。

参考文献:

关于作者:陈广佳KevinChen,汕头大学电子信息工程系工科学士,台湾大新出版社珠海区开发部,现正围绕中日韩电子资料使用JAVA开发电子词典等相关项目。可通过Email:cgjmail@163.net于他联系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值