原始发表时间:2009-04-05
今天遇到一个需求,是将oracle 的语句替换为hsqldb 的语句,其中oracle 语句中调用了trim 函数,本来想自己写个名为trim 的静态方法而后通过hsqldb 的“create alias” 映射到hsqldb 当中,结果发现hsqldb 中已经将trim 设置为保留字,并且还没有提供实现(晕……不提供功能还站着地方~~ )
思前想后要不然就替换吧,hsqldb 当中有ltrim 和rtrim 这两个函数,我可以将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 》(绝无轻蔑之意,只是这本书写的太漂亮了,所以忍不住取个外号,唉~~ 坏习惯了~~ ),无果……
在网上乱逛帖子,从4 月4 日的晚上到4 月5 日的上午,差不多4 、5 个钟头(不算睡觉、看片、打CS 的时间)都花在这个问题上,突然看到一个关键字“嵌套”(英文nested ),在某个帖子里看到Java 的正则表达式并不支持嵌套匹配,有搜到一篇文章是.Net 中如何实现嵌套匹配的(该文链接为http://blog.csdn.net/appoFeng/archive/2008/07/07/2620998.aspx ),看得我直眼馋,为虾米Java 没有呢?这个可以有啊!!网上帖子一直表示“这个真没有!!”
火大了,在电脑的资料翻电子书,终于翻到另外一件宝贝O'Reilly 的《Mastering.Regular.Expressions 》 2Nd.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 字符串在进行匹配替换之前,在字符串的开端处加上一个空格即可搞掂 ~~
所以方法是无限多,只是偶们还没有想到而已 ~~