`

使用Java正则表达式硬编码实现有限层的嵌套匹配

阅读更多

原始发表时间:2009-04-05

 

     今天遇到一个需求,是将oracle 的语句替换为hsqldb 的语句,其中oracle 语句中调用了trim 函数,本来想自己写个名为trim 的静态方法而后通过hsqldb 的“create alias” 映射到hsqldb 当中,结果发现hsqldb 中已经将trim 设置为保留字,并且还没有提供实现(晕……不提供功能还站着地方~~

     思前想后要不然就替换吧,hsqldb 当中有ltrimrtrim 这两个函数,我可以将SQL 语句中的trim() 替换为ltrim(rtrim()) 这样不就结了?一瞬间很佩服自己点子来的够快(不过高兴的太早了,后面就进入了梦魇阶段……-_-bbb

     由于trim() 函数中可能会有其他的文本内容,于是乎很自然的想到用正则表达式,pia pia 写好正则表达式进行替换(这个比较简单就不给出代码了),脑袋灵光一闪,突然想到万一有人在trim() 里面写很复杂的语句,或者trim() 函数嵌套使用呢?

比如有人写了这样一些语句:

示例 1

trim(case when a=1 then '0' when trim(a)='' or trim(a) is null then '1' else '2' )

   

 

示例 2

trim(trim(trim(' 123 ')))

   

 

    看到这个我有一种不祥的预感,翻了一遍Apress 出版的可爱的“小人书”《Java.Regular.Expressions.Taming.the.java.Dot.util.Dot.regex.Engine 》(绝无轻蔑之意,只是这本书写的太漂亮了,所以忍不住取个外号,唉~~ 坏习惯了~~ ),无果……

    在网上乱逛帖子,从44 日的晚上到45 日的上午,差不多45 个钟头(不算睡觉、看片、打CS 的时间)都花在这个问题上,突然看到一个关键字“嵌套”(英文nested ),在某个帖子里看到Java 的正则表达式并不支持嵌套匹配,有搜到一篇文章是.Net 中如何实现嵌套匹配的(该文链接为http://blog.csdn.net/appoFeng/archive/2008/07/07/2620998.aspx ),看得我直眼馋,为虾米Java 没有呢?这个可以有啊!!网上帖子一直表示“这个真没有!!”

    火大了,在电脑的资料翻电子书,终于翻到另外一件宝贝O'Reilly 的《Mastering.Regular.Expressions2Nd.Edition (网上看到最新的是第三版,该死的网络,要不然我早就拿到第三版了)~~

    因为网上讲Perl 也实现了嵌套调用,而且Jakarta-ORO 项目正是实现Perl 的正则表达式引擎的项目,于是乎我又报了一点希望的看ORO 的文档,不过它的文档有够简单的,没办法继续google ,在网上看到的帖子鲜有介绍这方面信息的,目光出溜出溜着回到了《Mastering.Regular.Expressions 》,心里默念,主啊,给个面子,给点提示吧~~ 哥哥我快被你整挂了~~

    翻到书中Perl 相关的章节(还好报了最后一点希望,要不然永远不知道还有硬编码这个方法可以实现嵌套匹配的),翻到第7 章的第8 节“Fun With Perl Enhancements” (这个标题真是漂亮,我最爱增强特性了),文字部分就不讲了,大家看看Figure 1. Capturing parentheses ,有没有觉得这张图想什么?我觉得有点像蒙娜丽莎(边想着边躲避面前飞来的板砖),我的意思是这张图就是解决问题的幸运钥匙(比喻的好……像小学生的造句……)

    这个图的小节标题是“ 7.8.1 Using a Dynamic Regex to Match Nested Pairs” ,正是让我们使用 Perl 的动态正则表达式匹配成对嵌套的内容(翻译的有点蹩脚 ~~ -_-#

    参考文章的思路,写成了下面的代码,用来将 trim() 函数翻译成 ltrim(rtrim())

 

public static void main(String[] args) {
    String exp = "([^lr])trim" + createExpRecursive("\\(([^()]*|([^()]*", "[^()]*)*)\\)", 3);
    System.out.println("生成的正则表达式内容:" + exp);
    Pattern pattern = Pattern.compile(exp);
    String result = "trim(case \n\twhen a=1 then '0' "
                + "\n\twhen trim(case when a=1 then trim(' 123 ') "
                + "\n\telse trim('abc ') end)='' or trim(b) is null then '1' else '2'"
                + "\n)";
    Matcher m = pattern.matcher(result);
    while (m.find()) {
        for (int i = 0; i < m.groupCount(); i++) {
            System.out.println("grp " + i + " :" + m.group(i));
        }
        result = m.replaceAll("$1ltrim(rtrim($2))");
        System.out.println("replaced result step by step: \n----------------\n" 
                            + result + "\n----------------\n");
        m.reset(result);
    }
    System.out.println("final res: \n----------------\n" 
                            + result + "\n-----------------\n");
}

private static String createExpRecursive(String start, String end, int depth) {
    if (depth == 1) {
        return start + "\\([^()]*\\)" + end;
    } else {
        return start + createExpRecursive(start, end, depth - 1) + end;
    }
}

  

 

    也许大家会纳闷,为虾米正则表达式的开头是 ([^lr]) 其实是为了防止反复匹配替换的时候陷入死循环,因为咱们执行完第一次替换后,会出现 ltrim(rtrim(...)) ,没有这个限定条件的话,会一直不停的进行匹配替换。

    另外有个小问题,大家执行的时候会发现第一个 trim 并没有被替换掉,这是因为……(原因简单就不赘述了 ~~ :)),解决的方法是另外再写个正则表达式,只用来匹配以 trim 打头的情况(唉 ~~ 这是我第一反应想到的方法,够白痴吧 ~~ )其实只要你对需要处理的 result 字符串在进行匹配替换之前,在字符串的开端处加上一个空格即可搞掂 ~~

    所以方法是无限多,只是偶们还没有想到而已 ~~

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics