您现在的位置:首页 >> 基础算法 >> window基础 >> 内容

Delphi中TPerlRegEx类主要用法(3)

时间:2011/9/3 15:35:34 点击:

三. 使用和编写正则表达式要点

使用正则表达式,通常是用作三种用途:校验字符串,提取信息,处理字符串.

当用作校验时,通常是对正文整体校验,例如通常是判断正文是否正确的邮件地址,而不是判断正文是否含有正确的邮件地址.因此应在正则表达式的两端加上行开始锚点^与行结束锚点$.如果待校验的文字允许两端有空格,则应该在锚点前后用' *'或'\s*'(允许空格与TAB)匹配进去.

设计正则表达式的要点在于分段.对要匹配的内容分好段,就能够容易地各个击破.通常在要匹配的文字中会有一些分段的提示,例如逻辑上的单位、重复出现的模式或者不能连续重复出现的字符(串)。

以设计校验输入数字的正则条件为例,可以先列出符合条件的情况:
1234 / 12.34 / -12.34 / 12.3e4 / 12.3e-4 / .12E-34
可以看出,逻辑上的单位有:符号,整数部分,小数点,小数部分,e(或E),指数符号,指数部分
技术上的分段标志有:

符号:在开始与E后面各可能出现一次
小数点:只能出现一次,若出现,其后必须有小数部分。
e:只能出现一次,若出现,其后必须有指数部分。

所有“若出现,其后必须有。。。”的都可以考虑分为一组。可得初步方案: [+\-]?\d*(\.\d+)?([Ee][+\-]*\d+)?

但这个设计有问题,前半段的 \d*(\.\d+)? 是可以匹配空串的,而需求是如果有整数部分,则小数部分可选。如果无整数部分,则必须有小数部分,直观的做法是改为(\d+(\.\d+)?|\.\d+)。再认真观察一下,可以发现这个选择式无论任何情况,都是以\d+结尾,而我们实际上并不关心这个\d+是匹配到整数部分还是小数部分,至于前面的小数点与整数部分都是可选的。因此,这部分可以改写为 \d*\.?\d+

所以最终的校验式是:^ *[+\-]?\d*\.?\d+([Ee][+\-]*\d+)? *$

使用正则表达式提取信息是一个难点,但也是体现正则表达式强大实力的一个方面。提取信息的正则表达式必须要考虑四个方面:不误判(应该有一定语法检验能力),不漏判,子串能匹配到正确位置。一些结构复杂或具有循环结构的正文,可能需要多次处理或使用开发语言的循环结构来辅助提取。具体技巧我现在还觉得比较模糊,以下仅举几个例子:

1. 查找并分析 XX1>XX2<XX3<=XX4=XX5 这样的简写不等式,其中XXn是不包括>,<,=,!,空格,TAB符号的任何字符串,式子两端与元素之间允许有空格或TAB

由于这个不等式可能在上下文中,我们需要先在正文中把合语法的不等式隔离出来,否则下面的循环部分就会匹配到下一条不等式的部分。在这个例子中还算简单,找到 '\b[^<>=!\s]+((不等符号)[^<>=!\s]+)+\b' 就可以了(其中不等符号在下面解释)。但这样只能匹配到整个式子而不能分别提取式子中的子串信息,当出现(...)+时,对应子串内容是只是最后一个匹配到的串。

对每个匹配到的结果,因此这里需要分开两次提取,第二次需要使用循环来辅助。

先取最开始的子串,这个很容易,直接 '^\s*([^<>=\s]+)'就可以了,注意^ *是为了去掉开头多余的空格与TAB。真正的XX1在匹配到的子串1中。

接着开始分析 '((不等符号)[^<>=!\s]+)+' 部分。整个(...)+结构需要在外部用开发语言的循环来逐次提取。先列出合法的不等符号:>=,>,=,<=,<,<>,==,!>,!<,!>=,!<=,!=。因此不等符号部分应该是(!?>=?|!?<=?|!?==?|<>)。

因此,要分析篇正文,就需要:
1)A匹配 '\b[^<>=!\s]+(\s*(!?>=?|!?<=?|!?==?|<>)\s*[^<>=!\s]+)+\b' 找出不等式。
2)    对每个A匹配结果, B匹配'^\s*([^<>=!\s])',提取子串1记录为变量名称。
3)    接着B匹配'\s*(!?>=?|!?<=?|!?==?|<>)\s*([^<>=!\s]+)',提取子串1记录为符号,子串2记录为变量名称。
4)    从3)开始循环直到找不到B匹配结果
5)从1)开始循环直到找不到A匹配结果

2. 提取电话号码

这是不久前帮朋友做的一个小程序。事情是这样的:他的公司需要撒网式找澳大利亚酿酒公司合作伙伴,他的任务就是把网上查到的酿酒公司的联系方式记录入库。记录联系方式的数据库是把公司名称,地址,电话号码,传真号码,邮箱等信息分开不同字段储存的。于是他必须用鼠标在网页准确选下各种信息然后粘贴到数据表中,不但工作效率低,而且据说由于鼠标精确动作太多,手腕酸得不得了。于是找我帮忙写个小程序,需求是他可以把联系方式部分整个复制下来,我的程序自动提取出有关信息。以下是提取电话号码信息功能的设计过程:

我看了一下联系方式的可能情况,发现有时里面会包括多个电话号码,而朋友的数据库只记录一个号码。因此我决定把他选定文本中所有电话号码都提取出来,列在一个ComboBox中让他选择。在正文中标记为“Phone”或"Ph"或"P"的优先列在前面。

电话号码的写法五花八门,先考虑电话的标记,一般有以下几种:
Phone, Phone:, (Phone), P, P-, PH 等等
因此可把匹配标记的正则表达式设计为 '(?-i)(?:\(?Phone|Ph?)[-:]?\s*\)?)'。这种写法有个缺陷是无法保证两边括号能匹配(例如能匹配到'(Phone:')。但在这里的目的并不是校验,括号不匹配并不影响我提取电话号码,只求简单写成这样就够了,否则就要写成:'(?:Phone|Ph?)[-:]?|\((?:Phone|Ph?)[-:]?\s*\))' ,麻烦得多。

然后考虑电话号码本身,一个完整的固定电话号码可能是这样的:
+61 2 1234 5678 , (61 2) 1234 5678 , (61-)2-12345678, 61 (0) 2 12345678
而也可能省略国家代码(+61)或洲区号(02)简写成
02 1234 5678 , 1234 5678, 2-1234-5678
也有可能不按主号码四字一断的做法,写成
02 123 456 78 等等

构建正则表达式的过程如下:

匹配国家代号:(?:[+( ]*61[-) ]*)? 可以看出,这个式子如果用来校验是不合格的,它能匹配到'(++61-)-'这样的正文。但我在这里是为了提取公司网页上的信息,公司不会在自己的联系方法信息里放入这样的乱码。提取信息时,根据需求,在不误判的前提下可以假定输入信息不会出现太离谱的错误。

匹配区号:(?:\(? *0?\)? *(\d) *[-)]? *)? 同上,这个式子也可能匹配到不合法的正文。这里如果把区号与主体号码合并处理会简单很多。但我在这里想把区号提取出来,作为判断省份的一个依据。

匹配电话号码主体:(?:\d{5,}(?:[ -]\d+)*|\d{1,4}(?:[ -]\d+)+) 如果仅考虑匹配电话号码,用\d*(?: [ -]\d+)*就行了。但澳大利亚的邮政编码刚好是四位数字,而且地址中的信箱号有可能是3到4位数字。因此这里用了个麻烦的写法,如果连续数字小于5位,则后面必须跟一个连接号或空格,然后再跟数字,才能匹配到。

因此优先选取的号码的正则表达式是:(?-i)(?:\(?Phone|Ph?)[-:]?\s*\)?)((?:[+( ]*61[-) ]*)?(?:\(? *0?\)? *(\d) ?[-)]?\s*)?(?:\d{5,}(?:[ -]\d+)*|\d{1,4}(?:[ -]\d+)+))
电话号码在子串1,区号在子串2

第一轮扫描正文,一边找到匹配,一边把其替换成''。完成后,就可以用((?:[+( ]*61[-) ]*)?(?:\(? *0?\)? *(\d) ?[-)]?\s*)?(?:\d{5,}(?:[ -]\d+)*|\d{1,4}(?:[ -]\d+)+)) (去掉了匹配电话标志部分)对替换后的正文,找出所有没有电话标志,但符合格式的子串。

上一页123下一页

作者:网络 来源:转载
共有评论 0相关评论
发表我的评论
  • 大名:
  • 内容:
  • 盒子文章(www.2ccc.com) © 2024 版权所有 All Rights Reserved.
  • 沪ICP备05001939号