VBA正则表达式入门与提高

正则表达式入门与提高

目录

目录 ...................................................................................................................................................................................... 1 正则表达式入门与提高 ....................................................................................................................................................... 1 内容提要 .............................................................................................................................................................................. 2 第一篇 基础篇 ...................................................................................................................................................................... 4

一、正则表达式概论----理解正则表达式 .................................................................................................................. 4

(一)正则表达式方案处理文本的基本思路 ................................................................................................... 4 (二)正则表达式的基本组成单元—元字符(序列)......................................................................................... 5 (三)用正则处理文本的一个例子 ................................................................................................................... 7 二.正则与VBA的交互—正则表达式的实现 ............................................................................................................. 8

1.定义变量代码段 ................................................................................................................................................ 9 2.目标文本字符串赋值代码段 ............................................................................................................................ 9 3.创建正则对象代码段 ........................................................................................................................................ 9 4.设置对象的pattern属性 ................................................................................................................................ 10 5.设置对象的其它属性 ...................................................................................................................................... 10 6.应用对象的方法代码段 .................................................................................................................................. 12 三.正则元字符----字符表示法 ................................................................................................................................... 20

(一)对于一些常用的不可打印字符,规定了专用的元字符序列 ...................................................................... 20 (二)普通字符组:肯定字符组[a-z]及否定字符组[^a-z] ..................................................................................... 21 (三)字符组缩略表示法 ...................................................................................................................................... 21 (四)几乎能匹配任何字符的元字符:英文句点 ................................................................................................. 22 (五)控制字符表示法:\\cChar .............................................................................................................................. 22 (六)ASCII码表中字符的八进制转义表示法:\\num ........................................................................................... 22 (七)ASCII码表中字符的十六进制转义表示法: \\xnum ..................................................................................... 23 (八)Unicode码表中字符的十六进制转义表示法:\%unum ................................................................................ 23 (九)元字符字面字符表示法:转义符”” ............................................................................................................ 23 (十)引用前面括号捕获的文本--反向引用 .................................................................................................. 24 四.正则元字符----字符(串)连续出现次数表示法..................................................................................................... 25 五.正则元字符—-字符(串)位置表示法 .................................................................................................................... 27

(一)单词分界符\\b ............................................................................................................................................... 28 (二)非单词边界\\B .............................................................................................................................................. 29 (三)肯定顺序环视与否定顺序环视 .................................................................................................................. 29

第二篇 元字符(序列)进阶篇 ............................................................................................................................................. 34

一、元字符与字符集 ................................................................................................................................................. 34 二、^$的位置到底在哪里 ......................................................................................................................................... 35 三、字符组中元字符转义规则 ................................................................................................................................. 36 四、字符组与多选结构”|” ..................................................................................................................................... 36 五、否定顺序环视与否定字符组 ............................................................................................................................. 37 六、环视的多角度理解与应用 ................................................................................................................................. 38

(一)用作锁定特定位置的文本字符串 ......................................................................................................... 38 (二)用作对特定字符串是否存在的判断 ..................................................................................................... 39

第三篇 正则匹配的工作原理 ............................................................................................................................................ 41

一、匹配的基本术语 ................................................................................................................................................. 41

1. 匹配................................................................................................................................................................ 41

VBA平台的正则学习参考资料

2. 正则”引擎” ................................................................................................................................................ 41 3. 引擎”眼”中的目标文本—位置和字符 .................................................................................................... 41 4. 子表达式 ........................................................................................................................................................ 42 二、匹配总原则 ......................................................................................................................................................... 42 三、正则表达式匹配的基本过程 ............................................................................................................................. 42

1. 在正则制导下,引擎从目标文本的开始处,依次进行匹配尝试. ................................................................. 42 2. 引擎依次在目标文本的每一个位置上,尝试整个正则表达式中的所有子表达和组成元素,直到匹配失败,才移动到下一个位置. ................................................................................................................................... 43 3. 匹配优先量词总是匹配尽可多的字符 ........................................................................................................ 44 4 . 忽略优先量词总是匹配尽可能少的字符 ................................................................................................... 45 四、穷尽所有可能途径找到匹配---回溯 ................................................................................................................. 46

(1)多选结构的回溯 ............................................................................................................................................ 46 (2)量词”?”的回溯 .......................................................................................................................................... 47 (3)量词”*”的回溯 .......................................................................................................................................... 48 五. 回溯的总结 .......................................................................................................................................................... 48 六. 回溯与效率 .......................................................................................................................................................... 49 七.灾难回溯................................................................................................................................................................ 50 第四篇 技巧篇................................................................................................................................................................... 52

一. 匹配具有多种形态结构的字符串 ...................................................................................................................... 52

1. 匹配下列文本中的


标签,它可能呈现的形式例举如下: ..................................................................... 52 2. 匹配浮点数,它可能有下列几种呈现形式: .................................................................................................. 53 3. 匹配某范围内的数据 .................................................................................................................................... 53 讨论: ................................................................................................................................................................... 54 二、匹配特定位置上的字符串 ................................................................................................................................. 54 三、匹配其内部由相似结构字符串构成的字符串 ................................................................................................. 56 四、匹配一段文本,这段文本中不能包含特定字符串 ............................................................................................ 58 五、匹配一对特殊字符界定的之间的字符串,但其内部包含两端的界定字符 ................................................. 59

例1 目标文本 ..................................................................................................................................................... 59 例2 目标文本 ..................................................................................................................................................... 60 讨论: ................................................................................................................................................................... 62

结束语 ................................................................................................................................................................................ 63

正则表达式入门与提高

VBA则表达式入门与提高

有一位美国佬编写的<<精通正则表达式>>专业书籍是世人公认的正则权威著作.但它不太适合初学者,尤其是没有相关编程语言背景及书中所及的种种计算机技术知识的读者.其中很多晦涩难懂的内容在VBA中用不上或者对你来说根本无用的,而初学者的你却根本不知道怎样取舍.事实上,本人还没有发现一本针对VBA平台的正则专业书籍.网络可见到少量VBA正则教程,但内容多是”点到为止”.

鉴于此,有此贴文.在这里感谢本论坛liucqa老师的鼓励,在之前本人写过一篇正则对象操作的短文,liucqa老师留贴建议多写一点,但一致未能成文.此帖算是对liucqa老师的交代.

1

VBA平台的正则学习参考资料

内容提要

顺利迈越初期学习正则障碍的最好方法是:首先鸟览正则的全貌,在头脑中建立正则地图的概貌,然后”按图索骥”描绘充实每个细节.

“基础篇”的第一章希望为你构建一个正则体系的\地图“轮廓.第二章详细解读了正则在VBA中的实现(即正则对象的操作).第三至第六章分类介绍VBA中可使用的全部元字符(序列).它相当于VBS程序员手册中的正则内容范围.但充实了更多在实践中会遇到的细节

“元字符进阶篇”,讨论了元字符的应用环境以及对若干个常用元字符(序列)的深入辨析和应用探索. “原理篇”:正则表达式工作原理是最重要同时也是最难以掌握的知识.研究这个问题有时的确很枯燥,然而弄懂正则表达式的工作原理,才是真正理解正则的关键.正则工作原理可以让你根据任务编制出正确高效的正则表达式,也可以帮助你理解别人编写的正则表达式,另外也帮你分析”为什么是这个匹配结果?”的真正原因,从而更精准地调较正则表达式.

“技巧篇”:不要指望背诵单词和掌握语法就可以写出漂亮的文章.掌握正则的方法也是需要大量阅读与实践的,本篇提供了一些现实世界的实例,供你参考和探讨.望它能给你一些编制正则表达式的灵感.

目录

第一篇 基础篇

一、 正则表达式概论—理解正则表达式

二、 正则表达式与VBA的交互—正则表达式的实现 三、 正则元字符—字符的表示法 四、 正则元字符—数量的表示法 五、 正则元字符—位置的表示法 六、 正则元字符—分组及控制表示法 第二篇 进阶篇 一、 元字符与字符集 二、 ^$的位置到底是哪里 三、 字符组内部元字符转义规则 四、 字符组与多选结构“|” 五、 否定环视与否定字符组 六、 顺序环视的多角度理解与应用 第三篇 原理篇

2

正则表达式入门与提高

一、匹配的基本术语 二、匹配总原则

三、正则表达式匹配的基本过程 四、回溯 五、回溯的总结 六、回溯与效率 七、灾难回溯 第四篇 技巧篇

一、匹配具有多种形态结构的字符串 二、匹配特定位置上的字符串

三、匹配其内部由相似结构字符串构成的字符串 四、在一大段文本中,匹配一对特定字符串之间的字符串

五、匹配一对特殊字符界定的之间的字符串,但其内部包含两端的界定字符3

VBA平台的正则学习参考资料

第一篇 基础篇

一、正则表达式概论----理解正则表达式

文本处理是一项常见的工作任务,比如:在一段文本或数据中,查找、替换、提取、验证、分离和删除等特定字符或字符串。在几乎所有文本编辑器中(如word/excel/VBE等)都提供了字符串的查找/替换功能;在编程语言的世界里更是提供了丰富的字符处理函数和方法。VBA中有Find(查找某字符串)、Replace(用一字符串去替换文本中的另一字符串)、LIke(判断某字符串是否存在)等等。

编程语言本身提供的字符处理函数或方法,具有用法简单、处理快速和使用便捷的特点。不过这些函数或方法也存在很大缺陷:它们通常都是对非常具体的字面文字进行操作,假如要处理某一类具有某些相似特征的字符或字符串,就显得力不从心了。举个例子,要求在一大段文本中,查找所有的符合规范的电子邮箱。如果用VBA本身提供的字符处理函数来处理,显然不是一件容易的事。可见,在现实的世界里对复杂动态文本的处理,仅靠编程语言本身是不够的。为此,人们找到了一种功能更为强大的文本处理解决方案----正则表达式方案。

正则表达式是强大、便捷、高效的文本处理工具。利用它使用者可以描述和分析任何复杂的文本,配合编程语言或文本编辑器提供的支持,正则表达式能够查找、替换、提取、验证、添加、删除、分离和修整各种类型的文本和数据。当今主流编程语言(如:java/C#/C++/.net/php/pcre/perl等)几乎都提供了对正则表达式的支持;有些文本编辑器(如Dreamweaver)在编辑查找框中也可直接输入正则表达式,实现不限于字面文字的搜索与替换.VBA虽然只是对正则提供简单支持,但是它也可以完成一些用VBA函数或方法难以处理的文本处理任务。

(一)正则表达式方案处理文本的基本思路

1、显然,无论进行何种文本处理操作,首先要在目标文本中找出指定的字符串,而要查找它们必须得描述出该字符串的特征。比如,你要验证用户输入的是否是一个正确的电子邮箱,肯定不可能去枚举世界上所有存在的电子邮箱,因而首先得依据电子邮箱规范,建立一个电子邮箱的模式,然后比照该模式到文本中去查找验证,从而判断目标文本中是否存在与模式相吻合的字符串(这个过程也称之匹配过程,查找到的结果叫”匹配”)。一个简单的电子邮箱模式可以表示为:

^\\S+@\\S+$

这个代码模式就是电子邮箱的正则表达式,所以正则表达式是一种可以在许多现代应用程序和编程

4

正则表达式入门与提高

语言中使用的特殊形式的代码模式。编制这样的代码模式,也就是编制正确高效的正则表达式,是我们学习和研究正则表达式的主要任务。

2、如何将编制好的正则表达式应用于编程语言,实现我们真正的需要,这是学习和使用正则的第二个问题,在这一点上,不同的编程语言其实现方式是不一样的.庆幸的是,较之编制正则表达式,掌握它们是非常简单的事。我们会在本篇的第二章“正则与VBA的交互”中详细论述。

(二)正则表达式的基本组成单元—元字符(序列)

从电子邮箱的正则表达式(^\\S+@\\S+$)可以看到,正则表达式是由一些”特殊字符”组成的。人们常常把这些组成正则表达式的”特殊字符”称之为元字符。元字符是正则表达式事先规定或约定的,用来表示字符、位置、数量和控制的专用符号。在组成正则表达式的元素中,有的是由两个或多个特殊字符组成一个单元,表示单一意义。如上面电子邮箱正则中,”\\S”表示一个非不可见字符,我们可以称之为元字符序列.在正则表达式中也可以有字面字符,如邮箱正则的字符“@”,在这时表示的是字面上”@”.所以从形式上观察,正则表达式是由元字符、元字符序列或字面字符组成的,用于描述字符或字符串特征的一个代码模式.正则表达式也可以仅由字面字符组成,如”正则ABC”.

你是否有一种似曾相识的感觉?对!这不是什么新鲜的想法.远古的DOS时代,前辈门就曾用*号代表任意多个字符,用?号代表一个任意字符,那时称之为“通配符”;当下的VBA中Like函数的参数里有更多的特殊字符或结构,用来描述字符或字符串模式.不过,正则表达式里,那些”特殊字符”更多,语法规则更丰富,可以认为,它相当于是一门”微型”语言.

接下来,本章会把所有的”元字符(序列)”分类展示给你,不是要你立马记住或掌握它,目的是让你有个概貌,避免在以后的学习中迷失方向.

1.正则表达式规定了多种方法或符号用来表示世界各国使用的文字字符。如:

下面列举了VBA中正则表示字符的所有元字符(序列),在以后的章节中会详细介绍. (1) 常用不可打印字符:\\n、\\t、\\f、\\r、\\v (2) 八进制转义:\\num (num是一个八进制数) (3) 十六进制转义:\\xnum (num是一个十六进制数) (4) Unicode转义:\%unum (num是unicode代码点)

5

VBA平台的正则学习参考资料

(5) 控制字符:\\cchar (char是A-Z之间的任意字母) (6) 普通字符组:[a-z]和[^a-z]

(7) 几乎能匹配任何字符的元字符:英文句点 (8) 字符组缩略表示法:\\w、\\d、\\s、\\W、\\D、\\S

2.表示字符或字符串数量(连续出现的次数)的元字符:*、?、+、{n}、{n,m} 例:

3.表示位置的元字符(序列):^、$、\\b、\\B、(?=?)、(?!...)例:

4.在正则表达式中起分组、捕获和控制作用的元字符(序列): (?)、(?:?)、\\1、?|?|?、*?、+?、??、{num,num}? 例:

捕获9.PNG (22.65 KB, 下载次数: 7)

捕获.PNG (22.47 KB, 下载次数: 4)

6

正则表达式入门与提高

(三)用正则处理文本的一个例子

我们已经认识了几个简单的元字符(序列),并能用它们构建一些实用的正则表达式,那么,怎样把它们应用于VBA中呢?,下面我们用正则在VBA中来完成一个简单的任务:

目标文本:”正则表达式其实很简单 “ 任务:删除目标文本中行尾空格. 分析:

1.\\s可表示空格,+表示出现一个或多个字符,所以可用”\\s+”表示连续多个空格.$表示一行的行尾,于是可用以下正则表达式描述行尾的若干空格:

\\s+$

2.我们把上面的正则代码表达式作用于目标文本,查找与模式吻合的字符(串),并用空字符替换,从而达成实现删除空格任务。

下面是完整的VBA代码: Sub Test()

Dim regx, S$, Strnew$ S = \正则表达式其实很简单 \

Set regx = CreateObject(\ regx.Pattern = \ regx.Global = True Strnew = regx.Replace(S, \ MsgBox Strnew End Sub

这个简单的例子说明了正则实现的一般步骤:

1、 创建变量:这个例子中,变量regx是一个对象,S是字符串变量;Strnew也是字符串变量. 2、 把目标文本赋值给变量S 3、 创建一个正则对象regx

4、 设置正则对象regx的pattern属性,即把正则表达式以字符串形式赋值给pattern. 5、 设置正则regx对象的其它属性,例子中设置Global属性为真

6、 应用对象提供的方法,实现相应功能.例子中,利用regx对象的Replace方法实现替换. 7、 输出处理后的字符串.

到这里,你已经完全了解了用正则处理文本的基本过程和思路,以及在VBA中使用正则的代码框架.以

7

VBA平台的正则学习参考资料

后的任务是全面掌握正则的所有元字符和它们的工作原理,另外还需要进一步了解正则对象的各种属性和方法.

要提醒的是,”基础篇”的应用实例或许并不是解决该任务的最佳方案,也或许是一些看似很无聊的例子,但请不要忽视它们.正是透过这些简单的实例,揭示了概念的本质.

二.正则与VBA的交互—正则表达式的实现

在继续学习正则元字符特性或编制自己的正则表达式时,常常需要对其测试.你可以用一些专门的正则测试工具(推荐RegxBuddy);也可以自己编制VBA代码进行测试。不过建议初学者,经常编写VBA代码进行测试,这样可以提高今后实际应用正则的能力。所以,在进一步学习正则元字符特性之前,我们先介绍正则与VBA的交互的相关知识。你可以快速阅读或越过本章内容,在以后具体应用时,再经常回头查阅。当然也可以用上一章学到的知识详细研究本章内容,在以后的学习中专注于正则表达式本身.

用正则处理文本,是通过正则表达式与程序设计语言的交互来实现的。其交互方式在不同编程语言中分为三大类:

一是集成式。Perl语言本身内建正则操作符,可以直接作用于正则表达式.操作符作用于正则表达式就像数学的+-号作用于数字一样.不需要构建正则对象。例如:任务是要把变量$text保存的文本中的空行替换为标签(

)。

正则表达式 ^$ 表示空行.

在Perl语言中,可以用一句代码实现替换:$text=~ s/^$/

/g

二是函数式处理。Java等语言,为正则处理提供了一些便捷函数,以节省工作量.用户不需要首先创建一个正则对象,而是用静态函数提的临时对象来完成正则处理,处理完后把临时对象抛弃. 正则表达式对于函数相当于一个参数, 这种方式的优点是”随手”可用,但不适宜在对时间要求很高的循环中使用.所以java也提供了下面讲到的面向对象的程序式处理.

三是面向对象的程序式处理。这是大多数编程语言的正则处理方式。VBA平台采用的也是这种方式。面向对象的程序式处理方式,首先必须创建一个正则对象的实例,然后设置对象必要的属性,最后用对象的方法来完成指定的任务。(提示:不同编程语言的正则对象具有的属性和方法,其项目多少或功能强弱有所不同,所以,在VBA中使用正则如果发现没有某种其它语言的方法或属性,请不要感到困惑)

在上一章中,我们给出了一个用VBA删除行尾空格的正则处理例子,它代表了一般的代码框架模式,下面再看一看它的结构特点,并对每一部分的代码段进行剖析:

Sub test()

8

正则表达式入门与提高

Dim regx,S$,Strnew$ 1.定义变量代码段

S=”正则表达式其实很简单 “ 2.目标文本字串变量赋值代码段 Set regx=createobject(“vbscript.regexp”) 3.创建正则对象代码段 Regx.pattern=”\\s+$” 4.设置正则对象的pattern属性代码段 Regx.global=true 5.设置正则对象的其它属性代码段 Strnew=regx.replace(s,””) 6.应用正则对象方法代码段 Msgbox strnew 7.处理返回值代码段 End sub

1.定义变量代码段

不必讲解了吧.

2.目标文本字符串赋值代码段

目标文本,可能存在于文本文档、Word文档、HTML文档或Excel文档等文档之中。正则对象并不能直接作用于这些文档,只能作用于它们的副本。所以用VBA正则处理这些文档,必须首先从这些文档中读出字符串并赋值于字符变量。如果任务是修改文本,那么,你可能需要编写额外的代码将修改后的文本字符串重新写回原文档中.

例:假如目标文本存在于当前表格A1单元格中.可使用下列代码赋值于字符变量S S=Activesheet.[a1]

目标文本也可能分别存在于一个数组中,那么,你可能需要通过循环逐一处理.

你也可以直接以输入的方式,赋值给字符变量,就像上面的例子.这时特别注意的是:半角双引号是VBA语言中的保留字符,如果目标文本中本身含有半角双引号,则必须转义,转义方法是:用重复的双引号表示一个双引号.

例:目标文本为:”我们用”汗牛充栋”、”学富五车”形容一个人读的书、拥有的知识多。”. 将之赋值给S的代码为:

S=”我们用””汗牛充栋””、””学富五车””形容一个人读的书、拥有的知识多。”

3.创建正则对象代码段

文本处理的各种操作,都是通过操作正则对象来完成的.所以必须创建正则对象.VBA创建或声明正则对象有两方式:早期绑定和后期绑定,你可以根据自己喜好选择其一:

9

VBA平台的正则学习参考资料

早期绑定: (需要在VBE--工具--引用中勾选Microsoft VBScript Regular Expressions 5.5) Dim regx AS RegExp

Set regx=new regexp (或dim regx as new regexp) 后期绑定:

Set regex = CreateObject(\

利用上述两种方式创建或声明正则对象,实际上是调用Microsoft VBScript脚本的regexp正则对象。Microssoft VBScript脚本,包含在Internet Eeplorer 5.5以及之后的版本中.该脚本中的正则表达式执行的是ECMA-262第3版所规定的标准,与JavaScript脚本中的正则执行标准是相同的。1.0版只是为了向后兼容的目的,功能很弱。

(提示:在VBA中也可调用JavaScript(Jscript)或ruby等脚本中的正则对象,Jscript的元字符及特性与VBscript是一样的,但它的方法或属性要多一点,或者说对正则的支持更强一些.ruby本人不懂,不太了解它的元字符集,只是看到论坛上有人使用)

4.设置对象的pattern属性

语法:object.pattern=”正则表达式” Object是一个正则对象.

把自己编制的正则表达式,以字符串的形式赋值给pattern属性。注意要用英文双引号将正则表达式包围起来.

并且要在对象名与属性名之间用英文点号隔开.属性名pattern是保留字,固定不变的,对象名是用户自定义的。

接下来的两个步骤是对正则对象的操作,通过设置或使用正则对象的属性和方法,以实现对文本的处理.正则对象的属性和方法不多,列表于下:

5.设置对象的其它属性

除Pattern属性外,正则对象还有其它三个属性,其属性值有False和True,默认值都是False。如果要使用默认属性,可以不用显示设置;如果要改变默认属性,则需要显示设置:

Global 当属性值为False时,只要在目标文本中,找到一个匹配时,即停止搜索。如果想要找出目标文本

10

正则表达式入门与提高

中的所有匹配,那么需要把它的属性值设置为True。

IgnoreCase 设置对英文字母大小写是否敏感。默认值False, 对大小写敏感;设置为True,忽略大小写. MultiLine 它影响且只影响元字符^和$的意义。值为False,无论目标文本是多少行,整个文本中则只有一个开始位置,^表示第一行的开始;只有一个行结束位置,$表示文本末尾位置。值为True,那么,^和$分别表示每一行的行首和行尾位置。

下面来完成一个简单的任务,再具体认识各属性的使用方法: 有一两行的文本: Aaa Bbb 任务要求:

1.在文本开始和结束处,分别插入一个”@”符号; 2.在文本每行的开始和行尾分别插入”@”符号。 正则表达式:

^|$ 表示匹配行开始或结束位置 任务1代码: Sub test1() Dim reg, s$

s = \这里用vblf 表示行之间的换行符 Set reg = CreateObject(\ reg.Pattern = \ reg.Global = True s = reg.Replace(s, \ MsgBox s End Sub 讨论:

Msgbox 最后显示的结果为: @Aaa Bbb@

代码中修改了global的默认属性值,设置为true;目的是保证能找到并替换全部的开始或结束位置。如果保持默认属性,则只会在开始处插入一个@号。

正则对象Reg的其它两个属性保持为默认。因为本任务无关乎字母大小问题,所以IgnoreCase属性

11

VBA平台的正则学习参考资料

无需要设置为Ture(当然如果设置为true,对最后结果也无影响);由于Mutiline属性保持默认,其值为False,所以整个文本只有一个开始位置和一个结束位置。

代码中使用了对象reg的replace方法,它的作用是,将在目标文本中找到的匹配(开始和结束位置)替换为”@”字符,在这里实际上是插入。然后把修改后的文本返回,重新赋值给字符变量S。

任务2代码: Sub test2() Dim reg, s$

s = \

Set reg = CreateObject(\ reg.Pattern = \ reg.Global = True reg.MultiLine = True s = reg.Replace(s, \ MsgBox s End Sub 讨论:

任务2代码与任务1代码唯一区别是修改了mutiline默认属性,设置为True。这就意为着,该文本的每一行都存在一个开始位置和结束位置。所以Msgbox最后显示的结果为:

@Aaa@ @Baa@

6.应用对象的方法代码段

VBScirpt正则对象的方法共有三个:你可以根据任务要求选择使用一个或多个方法. (1)TEST方法

语法:Object.Test(string)

Test方法只是简单测试目标文本中,是否包含正则表达式所描述的字符串。如果存在,则返回True,否则返回False。

例:用代码检测用户的输入是否是一个电子邮箱。 Sub ChkEmail()

12

正则表达式入门与提高

Dim reg, s$

s = InputBox(\请输入一个电子邮箱:\ Set reg = CreateObject(\ reg.Pattern = \ If reg.Test(s) Then

MsgBox \你输入的电子邮箱格式正确: \ Else

MsgBox \你输入的电子邮箱格式不正确!\ End If End Sub 讨论:

代码从用户那里获得字符串,然后赋值与字符变量S。验证邮箱的正则表达式非常简略,元字符序列\表示不是空格的任意一个字符,后面紧跟一个+号表示一个以上字符。这个表达式事实上只验证了用户的输入里,在字符串之间是否有一个@符号。它甚至认为”0@中”都是正确的。下面给出一个更为严格的电子邮箱正则表达式:“^[\\w.-]+@[\\w.-]+$”当然要严格按电子邮箱规范写出正则表达式,可能就十分复杂,由于我们刚刚接触正则,就不在详细讨论了。

这里要关注的是,test方法的语法,在方法与正则对象之间也是用英文点号隔开,作为参数,目标字符串用英文括号包围。在这个例子中,如果Test返回的是true,表示目标文本S中找到了正则模式的匹配。则显示正确结果,否则显示错误提示。

(2)Replace方法

替换在目标文本中用正则表达式查找到的字符串。 前面例子中语句体现其语法:s=reg.replace(s,”@”)

后面括号中的参数S,代表前面代码中设置的目标文本字符串.也就是正则表达式将要作用的目标文本.”@”是用来替换的字符串参数.前面的s是Replace方法返回的结果,它是目标文本被替换后的一个副本. 如果没有找到匹配的文本,将返回与目标文本一样的一个副本.

下面继续讨论Replace方法的第二个参数:

例子中\是一个字面字符,要用一对双引号包围起来。第二个参数还可以是变量、表达式。如果是变量或函数则不能用双引号包围,这一点和VBA代码规则是一致的.

上一章我们知道了如果在正则表达式中使用了元字符序列()括号,那么被圆括号包围的内容会存储在

13

VBA平台的正则学习参考资料

特殊变量$1中。在有些编程语言中,可以直接在正则代码外使用$1变量,而VBScript中可以并只可以在Replace方法中,作为第二参数来调用。

例子:在目标文本中的数字数据后增加上单位:KG

目标文本:“他们体重分别是:张三56,李四49,王五60。”

结果文本要求: “他们体重分别是:张三56KG,李四49KG,王五60KG。” 正则表达式:(\\d+) 替换文本: $1KG Sub testrep() Dim reg, s$

s = \他们体重分别是:张三56,李四49,王五60。\ Set reg = CreateObject(\ reg.Pattern = \ reg.Global = True s = reg.Replace(s, \ MsgBox s End Sub 讨论:

用正则表达式(\\d+),Replace方法将在目标文本中找到三个匹配,其值分别是56,49,60。并分别把每个值保存于每一个匹配对象的$1变量中。

替换文本:”$1KG”表示每一个匹配中的$1变量值与字面字符”KG”联结,组成新字符串,用来替换找到的数据字符串。

$1是一个很特殊的变量,它由美元符号与数字编号组成.如果正则表达式中有两个或两个以上的捕获性括号,则按照左括号”(“从左到右顺序编号,自动命名为$1,$2,$3?.,共支持99组.要指出的是,如果找到多个匹配,那么每个匹配中的特殊变量名是一样的.这个例中共有三个匹配其值分别为56,49,60.第一个匹配的变量名是$1,第二和第三个匹配的变量名仍然是$1,只是每个匹配中$1保存的值是不一样的.

最后一点,作为替换参数的一部分,$1变量与字面字符共同组成替换字符串时,它们之间不用 & 符号连接,并且 $1 必须放在一个双引号中;而如果是用其它普通变量与字面字符联结组成替换文本时,则必须用 & 符号联接,这一点与VBA代码使用方法相同.

在Replace方法的第二个参数中,还有几个很少用到的特殊变量:

14

正则表达式入门与提高

一个较特殊的状况,如果上面所述的特殊变量符不是作为变量使用,而是要以它们作为字面字符的替换文本,那么就要对它们转义,方法是在它们之前加一个美元符号$.如$$&

(3)Execute方法

在目标文本中执行正则表达式搜索。 语法:set mh=object.execute(s)

其中mh是用户自定的对象变量,S是值为目标文本的字符串变量.object是正则对象.

Execute方法会作用于目标文本(S),并返回一个叫作\的集合对象,在这里是mh.在这个集合对象中包含它找到的所有叫做\的成功匹配对象(Matches集合最多可容纳65536个匹配对象). 如果未找到匹配,Execute 将返回空的 Matches 集合。Matches集合有两个只读属性:索引(Item)和成功匹配的次数(Count).

Matches集合中包含的匹配对象Match有四个只读属性:Value/firstindex/length/submatches

值得一提的是,Submatches属性是一个集合属性,集合中元素个数与正则表达式中使用的捕获性括号的个数相同,每个元素的值就是括号包围起来的内容.它也有两个只读属性:item和Count

下面用树状图来表示它们之间的关系,并在接下来的内容中继续逐一讨论它们的用法.

15

VBA平台的正则学习参考资料

<1>Matches集合的Item和Count属性

利用Matches集合的Item属性可以得到它包含的每个Match对象;利用Count属性可以得到成功匹配的个数.

Matches集合对象中元素(成功匹配)的索引编号从0开始.我们可以用遍历集合的方式或索引方法读取每一个匹配值.

例:从一段文本中提取所有英文单词.

目标文本:”苹果:iphone_5s;诺基亚:Nokia_1020” 结果要求:分别提取出iphone_5s和Nokia_1020

代码: Sub test2()

Dim reg, k, mh, strA$

strA = \苹果:iphone_5s;诺基亚:Nokia_1020\ Set reg =CreateObject(\

16

正则表达式入门与提高

reg.Pattern = \ reg.Global = True Set mh = reg.Execute(strA) For Each mhk In mh Debug.Print mhk.value Next End Sub 讨论:

通过语句Set mh = reg.Execute(strA),Execute方法返回一个集合对象mh,在这个集合对象里包含两个匹配对象,代码中用遍历方法取出每一个匹配对象的值.

Execute方法返回的集合对象mh,有两个属性:

1)Count: Execute方法成功匹配的次数,也可理解为mh集合对象中包含的成功匹配对象的个数.语法: N=mh.count 本例中n值为2

2)Item: 索引,可以通过索引值,返回集合对象中指定的匹配对象.语法: Set mhk=mh.item(0) K=mhk.value

用索引返回第一个Match对象即mhk. 本例中k为第一个Match对象的值(iphone_5s). 同样的方法可以得到第二匹配的值.

由于Item和Value属性是集合的默认属性,所以上面两个语句也可简写为: K=mh(0)......第一个匹配对象的值(iphone_5s) M=mh(1)...........第二个匹配对象的值(Nokia_1020) 上面代码中遍历集合也可以用索引法遍历: For i=0 to mh.count-1 Debug.print mh(i).value Next i

<2>Match对象的属性

Execute方法返回的集合对象中包含的也是对象元素,即match对象,match对象有四个属性: FirstIndex:匹配对象所匹配字符串的起始位置。 Length:匹配对象所匹配字符串的字符长度。 SubMatches:匹配对象所匹配结果中的子项集合。 Value:匹配对象所匹配的值。

17

VBA平台的正则学习参考资料

在本例中:索引为0,即第一个匹配对象的属性值为:

K=mh(0).value k的值为iphone_5s,value是默认属性可简写为k=mh(0)

sn=Mh(0).firsindex sn的值为3,表示在目标字符串中,位置3上找到该匹配iphone_5s.(位置是从0开始的)

Ln=mh(0).length ln值为9,即iphone_5s的字符长度 <3>Match对象的Submatches属性

匹配对象match的Submatches是一个集合属性,它包含正则表达式中用圆括号捕捉到的所有子匹配.它为用户提供了返回$1特殊变量值的方法.

集合Submatches有两个固有属性:Count和Item.可以通过Item得到集合中的每个值,它实际就是在正则表达式中用圆括号捕获的内容;Count值是集合中元素个数,实际上就是正则表达式中捕获性圆括号的个数.

下面给一个实例来说明:

目标文本:给定一个标准邮箱地址:J3721@163.com

要求:从邮箱中分别提取出:用户名j3721,服务器域名163.com 正则表达式: ^(\\w+)@(.+)$ 代码: Sub test5()

Dim reg, mh, strA$, username$, domname$ strA = \

Set reg = CreateObject(\ reg.Pattern = \ Set mh = reg.Execute(strA)

N=mh(0).submatches.count ‘n值等于2 username = mh(0).submatches(0) ‘j3721 domname = mh(0).submatches(1) ‘163.com End Sub 讨论:

正则表达式中,\\w+表示匹配@前面的所有英文单词字符;@后面的点号是一个元字符,表示匹配除换行符外的所有字符之一,后面紧跟+号,即”.+”表示匹配@后面除了换行符外的所有字符.用括号包围起来,用户名和域名就会自动分别保存在变量$1和$2中.

前面已经知道VBA不能在replace之外直接调用$1或$2,而这个例子告诉我们可以用match对象的

18

正则表达式入门与提高

submatches集合属性来提取.

在这个例子中,execute方法返回的集合对象mh中,mh中只有一个匹配对象Match,即mh(0);mh(0)对象的属性submatches(0),返回第一个括号中的内容,即j3721.而submatches(1),返回第二个括号中的内容.submathches集合也有count属性,所以如果有很多子项需要提取,也可用遍历或索引方法返回每一个特殊变量值.最后再给一例子:

下面的代码演示了如何从一个正则表达式获得一个 SubMatches 集合以及它的专有成员: 正则表达式(一个邮箱地址): (\\w+)@(\\w+)\\.(\\w+)

如果你没有进一步了解元字符,可能不懂其中含义,不过没关系,在这里你只要知道,该代码的任务是显示电子邮箱dragon@xyzzy.com,用户名和组织名.

Function SubMatchTest(inpStr) Dim oRe, oMatch, oMatches Set oRe = New RegExp ' 查找一个电子邮件地址

oRe.Pattern = \ ' 得到 Matches 集合

Set oMatches = oRe.Execute(inpStr) ' 得到 Matches 集合中的第一项 Set oMatch = oMatches(0) ' 创建结果字符串。

' Match 对象是完整匹配 — dragon@xyzzy.com retStr = \电子邮件地址是: \ ' 得到地址的子匹配部分。

retStr = retStr & \电子邮件别名是: \ retStr = retStr & vbNewline

retStr = retStr & \组织是: \ SubMatchTest = retStr End Function Sub SubMatchesTest()

MsgBox(SubMatchTest(\请写信到 dragon@xyzzy.com 。 谢谢!\End Sub

19

VBA平台的正则学习参考资料

(前面第一篇的第一/二部分,概要阐述了正则表达式的基本思想,并对正则在VBA中的实现(也就是Regexp对象操作)作了详细讲解.接下来的第二至第六部分我们将集中介绍VBA中(本质上是Regexp对象中)可使用的全部元字符(序列),有的称为元字符的\特性\有的叫作正则\语法\反正它的基本属性就是用来描述字符的特殊字符.)

三.正则元字符----字符表示法

人类自然语言所用字符极其丰富多样,我们已经知道正则表达式是用元字符及它们的组合来描述这些字符以及这些字符组成的特定结构的.需要指出的是,正则没有统一标准,它是分流派的.在不同语言平台上(或同语言平台而版本不同),正则元字符的多少往往不同,同一元字符的特性也可能存在一定的差异.正是这个原因,在你参考各种正则资料的时候,尤其要注意这个问题.前面已经阐明,目前VBA平台上,用的是VBScript提供的正则对象,执行的是ECMA-262所规定的标准.

VBscript提供的元字符可方便地描述ASCII码表中的字符集.ASCII码表基本上包括了英文语系所用的所有字符.(如果你不熟悉,可以上网查查,大体上了解有哪些字符).也支持代码点不超过四位十六进制数的Unicode表的字符集.这解决了包括汉字在内的世界各国的官方文字的表示问题.为方便用户在不同环境下使用,同一字符往往有多种等价表示方法.

下面是对表示字符的元字符及序列的分类介绍:

(一)对于一些常用的不可打印字符,规定了专用的元字符序列

用正则表达式来描述一段字符串,即使它是看不见的,也必须毫无遗漏地表示出来.比如,空格,空白字符和一些不可打印字符,所以,在元字符(序列)中都有它们的表示方法.

20

正则表达式入门与提高

(二)普通字符组:肯定字符组[a-z]及否定字符组[^a-z]

肯定字符组表示方括号内列出的任意一个字符.否定字符组是在左方括号后紧跟着一个脱字符”^”,表示匹配括号内未列出的任一字符.例:

讨论:

1.如果字符范围在ASCII码表或Unicode字符表中是连续分布的,可以只用起止字符表示范围,中间用”-“连接.

2.用”-“连接的字符范围,前面必须是起点字符,如不能把[a-z]写为[z-a] 3.字符组内的字符顺序无关紧要,如[^ieou]与[^eiuo]是一个意思. 4.如果”-“字符中最左或最右位置,它表示匹配字面字符”-“.如[ieou-] 5.如果”^”没有紧跟在”[“之后,它也只表示字面字符”^”.

(三)字符组缩略表示法

对于一些常用的字符类,正则提供了简略表示法:

21

VBA平台的正则学习参考资料

提示:肯定类字符缩略表示法,只能表示ASCII码表中的字符,而该表中的否定元字符序列,它们可以匹配文本中unicode字符.

实例:

正则表达式 \\W\\W\\s\\w\\w\\w

它表示匹配一个非单词字符,紧跟又是一个非单词字符,再是一个空格,最后连续三个单词字符.可以匹配:”正则 ABC”,”规则 1_3”等.

(四)几乎能匹配任何字符的元字符:英文句点

英文点号”.”可以匹配除换行符(\\n)外的任意字符之一.它不限于ASCII码表中的字符,只要能显示于电脑上的字符都能匹配.在VBA中,英文句点等价于[^\\n].

(五)控制字符表示法:\\cChar

在ASCII码表中,十进制代码为1-26的字符全是控制字符,可以用”\\c”连接字母A-Z之一来表示. 即\\cA-\\cZ分别表示代码为1-26的控制字符.如”\\cM”,表示回车符.

(六)ASCII码表中字符的八进制转义表示法:\\num

VBscript正则可用字符的八进制编码转义来表示ASCII字符:它的序列由反斜线和字符的八进制编码组成.例:

提示:八进制转义法表示字符范围:\\0--\\377,包括ASCII码表和扩展表中的256个字符.该表示法主要用于难以输入的字符.

22

正则表达式入门与提高

(七)ASCII码表中字符的十六进制转义表示法: \\xnum

一个小写的\\X后跟两个大写十六进制数字可以匹配ASCII字符集中的一个字符.

提示:可匹配范围\\x00--\\xFF,但一般用\\x00--\\x7F表示ASCII字符集中的前128个字符.\\x80-\\xFF(ASCII扩展码表字符),一般用Unicode代码点表示法替代.

(八)Unicode码表中字符的十六进制转义表示法:\%unum

Unicode码一般用称之为代码点来表示一个字符.它是一个十六进制数.

正则表达式中,用“\%u”序列后面紧跟字符unicode代码点(十六进制数)表示该字符. 例:

提示:

1.\%unum表示法可表示代码点在U+0000--U+FFFF范围内的unicode字符.Unicode本身在发展中,已经出现超过4位的代码点,VBscript正则是不支持,但它们是很难遇到的字符.

2.用字符组[?]表示Unicode字符范围时,也可直接用表中起点字面字符与终点字面字符表示.比如大陆中文字符在Unicode表中,起始代码点是U+4e00,是表示中文字符”一”,终点代码点U+9fff,该代码点未定义,而”龥”字在终点附近,所以我们也可以用”[一-龥]”表示所有中文字之一.顺便提示一点,汉字在Unicode中的分布,基本无规律.不要想像\一\后面就是\二\等等.

3.有些流派的正则可以用Unicode的属性来方便表示不同类别的汉字或其它语言文字,但VBscript 5.5是不支持的.

(九)元字符字面字符表示法:转义符”\\”

正则表达式用了一部分字符作为元字符,那么怎样在正则表达式中表示这些元字符的字面字符呢?它

23

VBA平台的正则学习参考资料

提供了一个称为转义元字符,即反斜杠来表示.例:表示字面点号可用”\\.”;表示反斜杠的字面字符,用”\\\\”表示.下面列举了如果匹配字面字符必须转义的元字符:

提示:

1.注意并没有包括”]”,”-“,和”}”;

2.普通字符组[?]内部,元字符的转义有自己的规则,在下一章专门介绍. 例:正则表达式: \\d\\.\\d

可以匹配0.2,3.1?.等小数.

(十)引用前面括号捕获的文本--反向引用

正则中,用形如\\1,\\2?的元字符序列表示前面捕获性括号内的字串(块),”\\1”叫反向引用.如: (abc)\\1

可以匹配目标文本:abcabc,这里\\1实际引用的是它左边括号中的内容”abc”. (abc)(defg)\\2

可以匹配目标文本:abcdefgdefg ,这里\\2指的是从左至右第二个括号中的内容. 实例:删除一段英文中重复的单词. 目标文本:This this Is is an example. 结果文本:this is an example 正则表达式: (\\w+)\\s+\\1 代码: Sub testrep() Dim reg, s$

s = \

Set reg =CreateObject(\ reg.Pattern = \ reg.Global = True reg.IgnoreCase = True s = reg.Replace(s, \

24

正则表达式入门与提高

MsgBox s End Sub 讨论:

正则表达式中,”\\w+”表示匹配连续出现的单词字符.在本例中,它会匹配至空格为止;”\\s+”匹配一个或多个空格; “\\1”它会匹配左边第一个出现的括号中的相同内容.所以,该正则会匹配诸如”1 1” “1111 1111”之类的文本.

代码中Global属性设置为True,表示搜索所有匹配;IgnoreCase属性设置为True,表示忽略大小写,如”this” 与”THIS”视为同一单词.

代码使用了正则对象的Repalce方法,用$1替换找到的匹配. 这个例中,共找到两个匹配”This this”与”Is is”;而$1在前面已经说过它保存的是第一个捕获性括号内的内容.在本例中分别是This与Is.最终通过替换实现了删除重复单词的目的.

在VBA中使用反向引用要注意:

1. 是表示八进制转义的字符还是反向引用?

细心朋友已经发现,反向引用与字符的八制转义表示法,其结构是一样的.那么怎样区分它们呢?其规则是:

“\\num”:假如num是一个可以看作八进制的数字.

如果num的值大于正则左前边捕获性括号个数,那么,它是一个八进制转义符; 如果num的值小于或等于正则左前边捕获性括号个数,那么,它是一个反向引用;

显然,如果它左前边没有捕获性括号,那么,它肯定是一个八进制转义符了.如果num数字中含有超过8,或9的数字,那么,它一定是反向引用.

2. \\1,\\2,?编号是根据前面”左半圆括号”从左至右出现的顺序确定的.

四.正则元字符----字符(串)连续出现次数表示法

上一章介绍表示字符的元字符或序列都只代表一个字符.要表示连续多个字符,正则表达式提供了下表中的元字符(序列):

25

VBA平台的正则学习参考资料

讨论:

1.这些元字符(序列)叫”量词”,它作用于它前面紧挨的字符或字符串.作用的范围:可用()标示.如果没有(),那么它只作用前面紧挨的一个字符;如果要作用它前面多个字符,必须用()标明范围.例:

3.慎用可选项量词

显然,上例中浮点数的正则不能匹配这样表示的浮点数如:”.123”.可能你已经想到用下列表达式了: \\d*(\\.\\d+)?

的确它能匹配所有形式的浮点数了.但是由于表达式中整数部分与小数部分都是可选的,这意谓着什么也不匹配,也能匹配成功.也就是说无论目标文本是空字符还是任意字符(串),正则引擎都会报告匹配成功(不过匹配结果都是空值).

4.它们都是贪婪量词,即总是尝试匹配尽可能多的字符.比如:可选项量词\它的下限是0即不匹配,上限是1. 如果有符合要求的字符(串),它则选择匹配一个字符(串),不会选择不匹配.

例:正则表达式 .*

26

正则表达式入门与提高

它总是匹配一行中换行符前所有的文本. 实例:提取科室名 目标文本:

姓名:张三 科室:人事科(科长) 姓名:李四 科室:保卫科(干事) 正则表达式: 科室:(.*) 代码: Sub testname() Dim reg, mh, s$

s = \姓名:张三 科室:人事科(科长)\姓名:李四 科室:保卫科(干事)\ Set reg =CreateObject(\ reg.Pattern = \科室:(.*)\ reg.Global = True Set mh = reg.Execute(s) MsgBox mh(0).SubMatches(0) MsgBox mh(1).SubMatches(0) End Sub 讨论:

当用\科室:(.*)\去匹配文本时,在第一行成功匹配到:\科室:\后,正则的(.*)部分将依次匹配到后面跟着的所有字符,直到英文句点不能匹配的换行符.所以括号捕获到的内容是:人事科(科长).

而Global属性设置为true,表示找到第一个匹配后,只要还有文本没有尝试,那么,它将继续找出其它所有匹配结果. 在这个例子,找到两个匹配分别为mh(0)和mh(1). 其科室名即括号捕获的内容分别保存在这两个匹配的特殊变量$1中,VBA可以利用Match对象的submatches属性提取它们.(提示:如果不明白代码意思,请回到第一篇第二章查阅)

五.正则元字符—-字符(串)位置表示法

正则提供了一些表示位置元字符(序列),它可以锚定特定字符(串),有时使用它们可以提高匹配效率.除了前面介绍的^和$外,还有:

27

VBA平台的正则学习参考资料

(一)单词分界符\\b

在英文环境中,匹配一个字边界,即字与空格间的位置。例如,“er\\b”匹配“never”中的“er”,但不匹配“verb”中的“er”。

它匹配这样一个位置:该位置一边是英文单词字符,另一边不是.也可以理解为该位置两边的字符,其中一个可以被\\w匹配,另一个字符则可被\\W匹配.所以在英文环境中,可匹配四种位置 (本文中英文单词字符指[a-zA-Z0-9_]):

1) 在字符串的第一个字符前的位置(如果字符串的第一个字符是一个“单词字符”) 2) 在字符串的最后一个字符后的位置(如果字符串的最后一个字符是一个“单词字符”) 3) 在一个“单词字符”和“非单词字符”之间,其中“非单词字符”紧跟在“单词字符”之后 4) 在一个“非单词字符”和“单词字符”之间,其中“单词字符”紧跟在“非单词字符”后面 下面这个例子可让你明白单词分界符的用途: 目标文本:He captured a catfish for his cat 正则表达式1: cat

正则表达式2: \\bcat\\b

任务要求:用字符串fat,替换文本中的cat. 代码: sub test() dim s$

with createobject(\ .pattern=\分别用上面两个表达式测试 .global=true

s=.replace(\

28

正则表达式入门与提高

end with msgbox s end sub

你能分析结果差异的原因吗?

(二)非单词边界\\B

非字边界匹配。“er\\B”匹配“verb”中的“er”,但不匹配“never”中的“er”。 它是\\b取反.\\B总是匹配两个同时被\\w或\\W匹配的字符之间的位置.它匹配下列位置: 1)在目标文本的第一个字符之前如果第一个字符不是单词字符; 2)在目标文本的最后一个字符之后,如果最后一个字符不单词字符; 3)在两个单词字符之间; 4)在两个非单词符之间; 5)空串

在非英文环境中,没有单词边界;全部是非单词边界.所以应用范围很窄.

(三)肯定顺序环视与否定顺序环视

以一个实例来讨论: 1) (?=98) 2) (?!98)

第一个是肯定环视,表示子字符串”98”前面的位置;第二个是否定环视,表示不是子字符串”98”的位置.

例:

目标文本:”window97升级为window98”

如果把它们作为正则表达式作用于该文本,则该文本中只有”98”前一个位置上才能被(?=98)匹配;除这个位置外,其它所有位置都可以被(?!98)匹配.它的工作原理是:

在每个位置上查找该位置后是否跟着一个字符9,再然后再跟着一个字符8.如果是,则(?=98)报告匹配成功,(?!98)报告匹配失败;反之,(?=98)报告失败,(?!98)报告成功.

利用它们可以锚定特定字符串,如:正则表达式 Window(?=98)

29

VBA平台的正则学习参考资料

表示匹配后面跟着字符串”98”的字符串”window”.如果用它作用于上面目标文本,那么它只能匹配window98前面的”window”.

而正则表达式: Window(?!=98)

表示匹配后面没有跟着字符串”98”的字符串”window”.如果用它作用上面目标文本,那么它它只能匹配widow97前面的”window”

环视只是简单地测试其中子表达式能否在当前位置匹配后面的文本.无论是什么样的结果,它都不会”占有”被测试的文本.例:

目标文本:”正则ABC” 正则:(?!=\\W+).{2} 代码: Sub test() Dim re,mh,s$ S=”正则ABC”

With createobject(“vbscript.regexp”) .pattern=”(?=\\W+).{2}” Set mh=.Execute(s) End with For each k in mh Debug.print k ' “正则” next End sub 讨论:

匹配结果是:”正则”.我们来分析一下匹配过程:

在文本的开始位置,正则引擎首先尝试(?!=\\W+),即检查该位置后面有无一个或一个以上的非英文单词字符.结果它找到\正则\二个字符是非英文字符,引擎报告第一个子表达式匹配成功;接着尝试第二个子表达式:”.{2}”,即匹配两个任意字符,这两个字符就是”正则”.正则表达式中子表达式尝试完毕,最后报告成功而结束.

我们看到(?!=\\W+),并没有”消耗”掉”正则”字符串,如果消耗了,那么结果应该是”AB”.

最后要指出一点的是:虽然环视表达式中有圆括号,但它是非捕获性的.并且圆括号与?、!或=是一个不可分割的整体.

30

正则表达式入门与提高

(二)捕获性括号(?)与非捕获性括号(?:?).

1.捕获型括号(?.)

前面已经多次遇见过捕获性括号”(?)”, 总结前面使用圆括号有二个理由: 1) 分组,标明量词的作用范围.或限制多选结构,标明选择符”|”的作用范围. 例:一文本中,有多行文本,提取行全部以”AA”开头的连续3行. 正则表达式: (^AA.*\\n){3}

^AA 锁定行特征,即行开始处有字符AA;

.* 匹配行中其它字符至换行符,英文句点”.”是不能匹配换行符的; \\n 匹配换行符;

^AA.*\\n 匹配一个完整行;

(^AA.*\\n){3} 用()分组标明后面量词{3}的作用范围,即行重复3次. 2) 捕获文本,将括号内容的内容保存在特殊变量中. 在VBA中,捕获性括号保存的内容有三种方式引用: (1) 在Replace方法中通过$1,$2?方式引用; (2) 在正则表达式中,通过\\1,\\2?方式引用.

(3) 可以用匹配对象的Submatches集合索引号提取其内容. 3) 关于重复分组

如果在分组的结束之后放一个量词,那么整个分组就会被重复.如(abc){3}与abcabcabc是相同的. 那么最后捕获性括号捕获的内容是什么呢? 例:正则表达式 (\\d\\d){1,3} 目标文本:123456

最后匹配结果是123456.$1的值,即捕获性括号捕获到的内容是56.你可以编制代码测试,用submatches属性提取该值验证.得到这个结果的原因是:

分组的匹配在每次引擎退出该分组的时候被捕获,并会覆盖该分组在之前匹配的任何文本.(\\d\\d){1,3}会匹配一个包含2个,4个或6个数字的字符串.引擎会退出该分组3次.当这个正则表达式匹配到123456的时候,捕获分组中保存的是56,因为该分组的最后一次循环存储的是56.另外两次匹配12和34被覆盖了.

2.非捕获型括号(?:?)

捕获性括号保存内容时,是会付出处理成本的.所以,如果只需要让它起分组作用,而不必保存之中内容.

31

VBA平台的正则学习参考资料

正则表达式提供了一个非捕获性括号的字符序列:(?:?.),在形式上比捕获性括号多一个”?:”,并紧跟在左括号之后,它们是一个整体.

例:

(?:中国|China) (?:.*)

使用非捕获性括号可提高一定的匹配效率,特别是在使用循环的时候. 再次指出的是,虽然环视中也有圆括号,但在那里是非捕获性的.

(三)抑制量词的贪婪性: ?

前面已经知道,所有量词都是贪婪的,或称之为匹配优先的.他们总是匹配尽可能多的字符.下面例子可以了解什么是贪婪:

目标文本:“This is a first test” 要求:匹配提取标签 正则表达式: <.*> 代码: Sub test() Dim mh, s$

s = \ WithCreateObject(\ .Pattern = \ .Global = True Set mh = .Execute(s) End With MsgBox mh(0) End Sub 讨论:

在正则表达式中,使用了量词\它表示可匹配0个或多个字符.执行该代码结果显示:first.为什么不是我们希望的结果呢?下面分析它的匹配过程:

32

正则表达式入门与提高

当用正则表达式\作用于目标文本时,首先用\去目标文本中尝试,结果在\标签\后面找到了\紧接着开始在下一个位置尝试”.*”,\可匹配任意非换行符,\可以表示可以连续0次或多次.由于量词\匹配是匹配优先的,当匹配模式”.*”时,它会匹配至行的末尾,直到遇到换行符为止.下来该尝试表达式中的\了,结果余下的字符是换行符,不能匹配字符”>”,在此匹配失败;

在原理中我们会讲到,这时引擎会回退,直至回退到右边第一次出现”>”字符,匹配成功.最后报告成功匹配结果:first

为了解决这类问题,正则引入了一个元字符”?”,将它紧跟在量词之后,如”.*?”,则可抑制量词的贪婪性.让它变成忽略优先量词.忽略优先量词总是匹配尽可少的字符

同上例,正则表达式修改为: <.*?>

其它代码不变,最后结果显示:,得到了我们希望的结果.(提示:从效率上看,本例用正则表达式”<[^>]*>”效率更高.其原因会在”原理篇”中会谈到)

上面的例子告诉我们,在匹配优先的量词后,紧跟一个”?”,则变成了”忽略优先量词”.它们总是匹配尽可少的字符.再举一例,在字符串“oooo”中,“o+?”只匹配单个“o”,而“o+”匹配所有“o”

如果你已经读到了这里,那么恭喜你.你应该有很厚的正则功底了.

尽管现在的你可以用正则来处理一些文本工作,但难免给人以”花架子”的印象.因为也许你用正则完成的任务,远不如用VBA自身函数或方法处理来得简洁快速.正则是用来处理,VBA难以为任的工作.所以,你需要进一步深入下去.

33

VBA平台的正则学习参考资料

第二篇 元字符(序列)进阶篇

一、元字符与字符集

为了能在计算机中存储,处理和显示字符,在计算机初期出现了ASCII编码系统,它是用一系列代码表示字符,例如:十进制数65代表字符A等.ASCII代码表中共有256个字符(包括扩展代码表), 它主要用于显示现代英语和其他西欧语言.随着计算机的发展,为了处理显示其它国家或地区的文字,又出现了很多地区性编码系统,如汉字编码GB 2312-80等.地区性代码在电脑中的处理容易出现兼容性问题,于是相关国际组织制定了一个称之为Unicode的字符编码系统. Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求;Unicode定义了大到足以代表人类所有可读字符的字符集.

字符编码是一个很大的课题,它不是正则研究的范畴.这里提到它,目的是提醒你在使用正则元字符(序列)表示字符时,需要注意它们的使用范围,正则表达式是在英文语系下诞生的,所以很多元字符(序列)可能只适合于ASCII代码表字符集.很多正则教程的默认语境也是英语环境下的表述.为此总结如下:

说明:表中结论适合VBscript或JavaScript正则.在一些高版本的Java, .Net等语言中,正则有很大发展.不仅增加了更多的元字符种类,而且扩展了原有元字符的特性,如”\\d”还可以匹配非英文数字.如可以匹配全角的数字;而”\\b”也可以处理Unicode字符集等等.

34

正则表达式入门与提高

二、^$的位置到底在哪里

当正则对象的Multiline属性为False时(默认值),脱字符^与美元符$的意义是明确的,即分别表示文本开始和结束位置.

而当Multiline属性为True时,情况变得不那么简单了.特别是在匹配多行文本时,如果不仔细搞清楚它们的意义,就会遇到麻烦.

先来看”行”的定义, 不同操作系统对文本文件“行结束符”有不同定义.看到一种说法:Windows下按Enter键是 \\n\\r,Unix下是\\n,Mac下是\\r,在MS的系统中纯文本格式按一个回车键是输入了两个字符,一个回车一个换行。在LINUX系统中则只是一个回车.

但现实是:在用VBA正则处理文本行的实践中,我们发现,无论是从文本文件中读出的字符串,或word文档读出的字符串,或其它方式提供的字符串,一个文本行,往往结尾有\\r\\n或\\n或\\r的情况.

本人通过实践发现,VBA中正则对行的理解是根据\\n或\\r来确定.总结如下: “^”:

只要行尾出现\\r或\\n或\\r\\n,那么就会在文本行开始处创建一个”^”.所以无论行结束符是\\r\\n,或是\\n,或是\\r,那么行开始处一定有一个”^”位置符.

“$”:

只要行尾出现\\r或\\n,那么就会在\\r或\\n字符前面紧挨着创建一个”$”位置.所以,如果行结尾符是\\r\\n两个字符组成的,那么,就会创建两个”$”位置.

另外,无论文本结束处有无\\r或\\n,^与$是始终存在的.它代表文本的开始和结束位置. 所以,在处理多行文本时,应用锚点^和$,尤其是应用$时,应该注意文本行结束处到是什么字符. 在Multiline为True的前提下,如果要匹配连续多行文本,下列正则表达式就可能出问题: (^.*$){1,5}

1. 假如文本行的结束符是”\\r”,那么,该表达式就会匹配整个文本.因为”.”可以匹配回车符,”*”是匹配优先的.

2. 假如文本行的结束符是”\\r\\n”,那么,该表达式只能匹配到一行内容,尽管文本有多行.这是因为上面说到的原因,在这种情况下,一行中有两个结束符.

在匹配多行文本时,最好使用下面的方式: (^.*?(?:\\r?\\n|\\r)){1,5} 或 (^[^\\r\\n]+(?:\\r?\\n|\\r)){1,5}

当然,也可以事先测试一下文本的结束符是什么,后面不用选择结构.

35

VBA平台的正则学习参考资料

三、字符组中元字符转义规则

前面已经知道,在正则表达式中,表示元字符的字面意义时,往往需要用反斜线”\\”转义.那么,在普通字符组中,表示元字符的字面意义时,如何处理呢?

普通字符组中表示元字符的字面意义有自己的规则,总结如下: 1. 在字符组中需要转义的元字符: \\ ] [ ^ -

例:[\\\\|\\]],可匹配字符”\\”或”]”或”|”.

在上面五个需要转义的字符中,除反斜杠外其它四个字符在某些位置上也可不必转义,例如: [][^-] ,可分别匹配它们四者之一.虽然如此,建议使用以上五个字符时都进行转义,让代码更清晰.

2. 字母或数字字符如果在字符组中使用反斜线转义,要么出现错误,要么创建一个正则表达式记号.如把”\\b”放入字符组中[\\b],它表示匹配一个退格符(\\x8)

3. 表示字符的元字符序列如\\n,\\w等,在元字符中是不能转义的.它们意义与在字符组外是一致的.例:[\\n\\x20]可匹配一个换行符或空格.

4. 表示位置,数量或控制的元字符(序列),放入字符组中,不再有原来意义.例,让”^”紧跟在字符组的”[“后,表示否定字符组,放在字符组的其它位置,它表示匹配一个字面上脱字符;”$”放入字符组中也是表示字面字符”$”.

例:正则表达式: [*+^$|(){}?]

可以匹配下列字符之一:* ,+ ,^ ,$ ,| ,( ,) ,{ ,} ,?

5. 英文双引号无论在字符组内外,都要用重复的双引号转义.如[“”]匹配一个双引号(”).

四、字符组与多选结构”|”

在某些情况下,普通字符组与”|”匹配效果是一样的,如 [abc]与a|b|c

都是表示匹配字符a或b 或 c 而更多的情况是差异极大:如: [中国|成都] (中国|成都)

前者表示匹配字符”中/国/|/成/都”五个字符之一 ;后者表示匹配连续字符串”中国”或”成都.”

36

正则表达式入门与提高

它们两者的区别:

1. 多选结构允许子表式表示一个更复杂的字符串,而普通字组只能表示一个字符.

2. 即使多选结构的子表达式是单个字符的情况,两者的匹配机理也是不一样的.大多数正则引擎会对字符组提供非常好的优化,使用垂直竖线的多选结构要求引擎使用在计算上代价很高的回溯算法,而字符组则只使用非常简单的搜索算法.

结论:如果匹配是多个字符之一最好选择字符组方式,而如果要匹配多个字符串之一就只能用多选结构方式了.

五、否定顺序环视与否定字符组

看一个例子: (?!u) [^u]

前者是否定顺序环视,表示该位置之后不能匹配字符”u”;后者是否定字符组,表示匹配一个非”u”字符.现在我们用下列两个正则表达式分别作用于目标文本的每个单词,观察匹配结果:

目标文本: Iraqi Iraqian Miqra Qasida Qintar Qoph Zaaqqum Iraq

正则表达式: Q(?!u) Q[^u]

选项:设置IgnoreCase为true 下面是结果列表:

37

VBA平台的正则学习参考资料

可出看出两者的特点:

q(?!u)表示只要字符q后面不跟着字符u,匹配则成功.它有两种情况,一是后面跟着一个不是u的字符,二是q后面不存在任何字符(如iraq).

另外从匹配结果再次看出,环视是不”消耗”字符的,即环视中子表达式匹配的结果(u),并不会出现在最终的匹配结果中.

q[^u]表示字符q后面必须跟着一个不是u的字符,才会匹配成功.可见[^u]虽然是否定字符组,但它要匹配一个字符是”肯定”的.也就是说否定字符组表示的是匹配一个未列出的字符,而不仅仅是”不要匹配列出的字符”

六、环视的多角度理解与应用

环视按引擎往前(右)看还是往后(左)看,分为顺序环视和逆序环视.VBscript正则只支持往前看的顺序环视.环视有多种叫法,如”零长度断言”等.

对肯定顺序环视(?=exp)来说:从位置的角度理解,表示该位置是子表式exp左面紧挨的位置;从”断言”的角度理解,它断言自身出现的右边能匹配文本exp.

否定环视则是相反的情况.

从匹配过程来理解,顺序环视是从左至右查看文本,尝试匹配exp,如果能够匹配,那么肯定顺序环视(?=exp)报告成功,而否定顺序环视(?!exp)报告失败.反之,如果不能够匹配,那么肯定顺序环视报告失败,而否定顺序环视报告成功.

从环视的应用来说,主要有两个方面:

(一)用作锁定特定位置的文本字符串

例1:将文本中的”windows98,windows97,windowsxp”中的字符串”windows”替换为”win” 正则:

Windows(?=98|97|xp)

这个正则表达式锁定了字串”windows”: 在目标文本中,只有字符串”98”或”97”或”xp”前面的”wordows”才会得到匹配.它排除了在其它位置上存在的”windows”字符串.

38

正则表达式入门与提高

例2:查找后面不跟着某个特定单词(如Cat)的任意单词 正则:

\\b\\w+\\b(?!\\W+cat\\b)

这个正则表达式首先用”\\b\\w+\\b”去匹配一个单词,如匹配成功,紧接着尝试表达式(?!\\W+cat\\b),它的意思是查找该位置之后有无这样的字符串:若干个非单词字符(如空格)后跟着单词”cat”.

如果没有,则环视部分报告匹配成功,于是整个正则表达式匹配尝试结束,最终成功匹配,匹配结果为第一部分找到的单词.

反之,如果有,则环视部分报告匹配失败,于是整个正则表达式匹配失败,最终结果是,第一部分找到的单词不是符合要求的.

要注意的是在环视的子表达式中,Cat后用了单词边界\\b,是避免把某一个单词内部的cat字符当成单词了.

例3:查找不重复的单词 正则:

\\b(\\w+)\\b(?!.*?\\b\\1\\b)

前面我们介绍过查找相邻重复单词的正则表达式: \\b(\\w+)\\s+\\1\\b

你能否写一个查找相邻或不相邻的重复单词的正则表达式呢?

(二)用作对特定字符串是否存在的判断

例1:搜索同时包含多个单词的文本行,如:搜索包含单词one和two的文本行 正则:

^(?=.*?\\bone\\b)(?=.*?\\bwo\\b).+$

例2:查找除某个单词(cat)之外的任意单词 \\b(?!cat\\b)\\w+

例3:查找不包含另一个单词(cat)的单词 \\b((?!cat)\\w)+\\b

例4:匹配不包含某个单词(cat)的整行 ^((?!\\bcat\\b).)*$

请自己仔细解读这几个可在实际工作应用的经典正则表达式.

小结:与锁定文本功能不同,用作判断时,环视结构是放在前面的,匹配尝试时,它将在指定位置判断是否存在特定的字符串(即尝试匹配环视结构内的子表达式),如果环视报告成功则继续下一字符的匹配尝试;如

39

VBA平台的正则学习参考资料

果环视报告失败,则宣布整个匹配过程失败.

例1中指定位置是从行开始位置尝试子表达式的匹配; 例2中是从每个单词边界尝试子表达式的匹配;

例3和例4是在单词内部或行内部的每个位置尝试匹配子表达式,所以效率较低.另外,如果不需要提取文本,例3和例4中的捕获括号可修改为非捕获性括号.

40

正则表达式入门与提高

第三篇 正则匹配的工作原理

一、匹配的基本术语

1. 匹配

一个正则表达式能”匹配”一个字符串,其实是指这个正则表达式能在字符串中找到匹配文本.

2. 正则”引擎”

引擎本来是指发动机的核心部分, 现也用作IT方面的术语,指经包装过的函数库,方便别人调用,如搜索引擎、图形引擎等。

一个正则表达式不仅仅是一个代码模式,更是用正则符号写出的程序.当我们把它赋值给正则对象的属性时,其内部进行了所谓的”编译”,即对正则表达式进行语法分析,建立一个语法分析树,根据这个树生成一个正则引擎.

正则引擎有DFA和NFA类型之分,而VBscript中使用的是传统NFA引擎.这种引擎是所谓的”非确定有穷自动机(即NFA)”计算机算法的实现.本文接下来讨论的是NFA引擎的工作原理.(这是大多数语言平台上的选择,较流行的MySQL,使用的是DFA)

这是不是让人觉得模糊和深奥,本人也以为是.不过没关系,现在你只需要知道,在正则匹配的过程有一个”东东”在”操控”匹配形为,它就是”引擎”.而且我们也不打算从匹配算法理论角度去解读原理,而是从运用的角度去考察匹配的各种形为.

尽管如此,你应该从正则”引擎”的形成过程,明白一个事实:引擎受制于你编写的正则表达式.合理的正则表达式可以让引擎马力十足,而有问题的正则表达式则可能让引擎慢如蜗牛,更甚者,可能导致引擎空转或熄火.

换一种法,就是匹配是否成功或效率高低都掌握在你手里,这也是我们要研究原理的意义.

3. 引擎”眼”中的目标文本—位置和字符

对于一段目标文本,在引擎眼中,它是没有单词/句子/段落/等等复杂概念意义的,它看到的只是”位置”与”字符”:如

41

VBA平台的正则学习参考资料

4. 子表达式

在表述过程中,时常出现”子表达式”叫法.子表达式是整个正则表达式的一部分.如:括号中的内容,或由”|”分隔的多选分支,或环视内的表达式等.

二、匹配总原则

说到原理,总让人觉得它该是若干条高度概括的结论.不幸的是对正则原理来说貌似任何概括都对理解和利用没有指导意义.如果一定概括,那么,想到的就两条匹配原则:

1. NFA是以正则表达式为主导从文本开始处依次匹配的 2. 如果有必要,NFA总是穷尽所有途径,找到匹配

事实上,只要我们把握了各元字符(序列)及组合的匹配过程,也就把握了匹配原理.

三、正则表达式匹配的基本过程

1. 在正则制导下,引擎从目标文本的开始处,依次进行匹配尝试.

引擎会从左向右在每一个位置上扫描目标文本,同时检查正则表达式的每一个元素。如果产生一个合法匹配,匹配过程就会停在这个位置.

例: 正则表达式: cat 目标文本:

He captured a catfish for his cat 过程:

引擎先比较<>和目标文本位置0上“H”,结果失败了。于是引擎再比较<>和“e”,也失败了。直到第四个字符(位置3),<>匹配了“c”。<>匹配了第五个字符(位置4)。到第六个字符<>没能匹

42

正则表达式入门与提高

配“p”,失败了。于是引擎重新从位置4开始,即引擎再继续从第五个字符重新检查匹配性。直到第十五个字符开始,<>匹配上了“catfish”中的“cat”.至此,引擎报告成功.引擎停在这个位置.

这时,如果global属性为False,那么,整个匹配结束.反之,引擎从该结束位置(即catfish后的位置上)开始,重复上述过程,继续前行,直至目标文本的最后一个位置.

结论:正则表达式”cat”,并不是匹配”单词cat”.它的本质意义是首先匹配一个字符”c”,紧接着一个字符”a”,再接着一个字符”t”的这样一个字符串.(你能用前面所学知识,只匹配目标文本的最后单词”cat”吗?)

2. 引擎依次在目标文本的每一个位置上,尝试整个正则表达式中的所有子表达和组成元素,直到匹配失败,才移动到下一个位置.

在目标文本中的每个字符位置会首先匹配该正则表达式的所有可能排列,如果都不能匹配,然后才会到下一个字符位置进行匹配尝试.

例: 正则表达式: fat|cat|belly|your 目标文本:

The drgging belly in decates that your cat is too fat 如果global属性False,最后结果是:belly. 为True,结果是:belly cat your cat fat

从这个结果中看到了吗?匹配结果单词出现的顺序并不是正则表达式的分支排列顺序,而是目标文本中出现的顺序.

分析过程:在”The drgging “之前的每个位置上,匹配都是失败的.这时,引擎移动到下一位置,即目标文件”belly”的”b”前面,而该位置上<>不能匹配b,结果失败,这时用下一个子表式的开始字母<>与”b”匹配,还是失败;当用再下一子表达式(belly)的开始字母”b”尝试时,匹配成功,接着测试正则后面的字符,最终在该位置上得到成功匹配”belly”,引擎在该位置上停止,它不会在此位置再去尝试下一个子表式.所以,我们得到了第一个结果是”belly”.

例:正则表达式

jane|janet 或 janet|jane 分别匹配目标文本: janet and jane

朋友们可以自己测试两种匹配结果.并分析原因. 结论:

43

VBA平台的正则学习参考资料

1. 正则匹配总是优先选择目标文本中最左端的匹配结果.

2.如果在目标文本的同一位置存在两个选择分支都可匹配的时候,分支的排列顺序将影响匹配结果.你可增加单词边界或改变排列顺序来达成自己的目标.

3.将目标文本中最可能会被找到的字符放在前面,会在性能方面有较小的提高.

3. 匹配优先量词总是匹配尽可多的字符

匹配优先量词: ? * + {n} {n,m}

它们存在匹配上限和下限,总是匹配尽可多的字符,直到”匹配上限”或没有符合要求的字符为止. 例: 正则表达式: .? 目标文本: Abc

选项:global为false 匹配结果是: A

分析: ?表示匹配0个或1个除换行符的任意字符,由于它是匹配优先,所以,选择了匹配1个字符,而没有选择匹配0个.

例:正则表达式 \\d+ 目标文本: March 1998 匹配结果:1998

分析:在位置6上成功匹配字符”1”,而”+”表示可以无限地继续匹配下去,所以直到字符”8”后面位置上匹配失败. 但”+”的意思是只要能匹配上一个字符就算匹配成功,所以引擎最后报告匹配成功,得到上面的结果.

例:正则表达式 ^Subject:(.*) 目标文本: Subject:

匹配结果:Subject:

44

正则表达式入门与提高

$1变量值为空.

分析:目标文本的开始位置直到”:”号,引擎只进行简单的逐一字符比较,每个位置上都是匹配成功的. 但正则表达式还有”.*”部分(注:括号不会改变匹配过程),而目标文本已经没有字符可匹配了,但”*”表示即使匹配到0个字符(即没有得到匹配)也是成功的.所以,引擎报告匹配成功,得到如上的结果.(如果正则表达式中用?代替*,是一样的结果;但如果用”+”代替”*”,则整个匹配过程失败,无匹配结果,你能分析原因吗?)

再给一个例了: 正则表达式: ^.*(\\d+) 目标文本: Copyright 2014

你能推测最后括号捕获到的结果吗?也就是$1的值是什么呢?

它不是我们想像的”2014”,而是只得到一个字符”4”.为什么是这个结果呢?

分析: “^.*” 中”*”号的贪婪性(人性?)会将目标文本的所有字符匹配,直至结尾,引擎检查”\\d+”时,结果没有字符可匹配了.引擎这时是否就报告整个正则表达式匹配失败呢?记得上面讲到一个匹配总原则:”引擎总是穷尽所有途径,找到匹配”. 根据这个原则,正则中,每个部分的匹配只要不越过它们的”底线”,就尽可能让整个匹配成功.所以”.*”首先交还一个字符”4”,让”\\d+”匹配,由于”\\d+”的底线是匹配一个字符就可成功,而对”.*”来说少一个字符没有导致自己匹配失败.引擎这时发现每部分都匹配成功了,赶快报告匹配结果吧.于是就有了上面的结果.

如果这个例子中正则表达式是 ^.*(\\d{4}) 或 ^.*(.*)

情况结果又如何呢?

结论:最后的几个例子告诉我们:慎用表达式”.*”或”.+”,在编写正则表达式时,如果可能尽量用较具体的表达式代替\如:上例,假如我们希望的结果是提取\那么,可用\\D+代替\

4 . 忽略优先量词总是匹配尽可能少的字符

忽略优先量词: ?? *? +? {n}? {n,m}?

它们忠实贯彻”总设计师”的战略决策:”摸着石头过河!”.在匹配的过程中,它们总是自己先得到最少匹配,观察一下,正则表达式中,紧跟它后面的元素或子表达式,如果后面子表达式不需要(不能匹配),自己才

45

VBA平台的正则学习参考资料

继续匹配下一个字符.

例:正则表达式: <.*?> 目标文本: aaa 匹配结果:

分析: 在开始位置,”<”匹配成功,接下来”.*?”部分以最少匹配0个字符(即不匹配),宣布匹配成功;让它后面紧跟的”>”部分尝试匹配目标文本中的”e”,显然”>”匹配失败,于是,”.*?”选择匹配一个字符,从而成功匹配\引擎移到下一位置,先让”>”又尝试,还是失败,于是”.*?”再匹配”m”,匹配成功;移到下一位置,用”>”匹配目标文本在该位置上”>”,匹配成功.到此,正则表达式中的所有子表达式都得到了成功匹配,所以,引擎宣布整个匹配结束.结果.

当然,如果global属性设置为True,那么引擎将一如既往进行下去.从而,又得到一个结果 思考:用正则表达式 .*?

匹配文本:12d 匹配结果是什么? 如果用正则表达式 .*?d

匹配结果又是什么?

结论: 对忽略优先量词来说,只有因为它们匹配得太少,导致整个匹配失败的时候,它们才会匹配更多的字符.这个结论的意义是:在编制正则表达式时,注意避免各子表式都是可选项的情形.

四、穷尽所有可能途径找到匹配---回溯

实际上NFA能够找到所有可能的匹配,要归功于正则的一个最重要原理----回溯

如果正则表达式中,存在量词(无论匹配优先还是忽略优先)或多选结构,那么可能会产生”回溯”. 我们再重新审视引擎遭遇它们时的状况:

(1)多选结构的回溯

用cat|car匹配文本car and cat

根据前面知识分析,它将首先匹配到文本开始处的car, 前面说了原因:引擎会在每个位置上尝试正则表

46

正则表达式入门与提高

达式的所有分支,那么它是怎样来实现这一点的呢?即真正的表因是什么?

我们再来分析它的匹配过程: 当引擎处于文本开始这个位置时(其它位置也一样),它面临两种选择:一是用cat去尝试,二是用car去尝试.我们知道多选结构是首先用前面的cat去尝试,.如果用cat尝试成功了,那么引擎宣布匹配结束;这个例子的实际匹配情况怎样的呢?在开始位置0处”c”匹配成功,由于子表达式还没有匹配完,所以引擎移动到下一位置1,也匹配成功,继续移动到位置2,”t”不能匹配”r”,匹配失败.于是第一个分支在文本的位置0处匹配失败.

到这里我们知道引擎将从位置2回退到位置0处尝试下一个分支car.事实上,要做到这一点,引擎必须在尝试第一个分支cat之前,记住还有一个子表达式可以备用. 也就是说:在位置0处引擎遭遇多选结构时,它将在该处存储所有的可能途径,称为”备用状态”,以准备当前一个子表达式匹配失败,而能尝试下一个子表达式, 并能够确定回退的位置;在这同时正则表达式中也保存了一个”备用状态”,即第一个子表达式尝试失败后,该从正则表达式的哪个位置开始进行第二轮尝试.

我们把上面多选结构第一次尝试失败后,回退的过程叫”回溯”.

(2)量词”?”的回溯

例: 用正则表达式 ab?c

分别匹配目标文本 abc 或 ac

首先看匹配ac的情况:在ac位置0处匹配成功,引擎移动到位置1处,在这里,正则子表达式”b?”有两种选择,一是匹配0次b(即不匹配b),二是尝试一次. 所以,引擎将分别在文本和正则的位置1处留下”备用状态”;而”?”是优先匹配的,所以它将首先尝试匹配一次b,结果正则中的b不能匹配文本中的c, 于是引擎回溯尝试第二个选择,即不匹配b;回溯的位置是”备用状态”中存储的位置1. 显然不匹配始终是成功的.于是在位置1处,用正则中的下一个元素”c”来尝试,结果匹配成功.引擎停止,整个匹配报告成功.

再看匹配abc的情况.前面的情况与上面一样的,直到首先尝试匹配一次b时,结果匹配成功;引擎后移,匹配字符”c”,匹配成功.由于正则表达式的所有元素都得到成功匹配,这时将抛弃备用状态,引擎报告整个匹配成功.

在这个例中,只有存储备用状态过程,没有”回溯”过程;所以,“回溯”是当有多个选择,并且前面选择导致整体匹配失败时发生的.

思考:

1.如果改用忽略优先量词,即用正则表达式 ab??c

47

VBA平台的正则学习参考资料

分别匹配abc 或 ac,情况是怎样的?请自己分析. 2.如果目标文本是abx,请分析匹配过程.

(3)量词”*”的回溯

下面图片展示正则表达式 “.*” 匹配目标文本: ”ab””c”

请分析用上面的正则表达式匹配文本: ”a”bcdefghijk

从这个例子中你得到什么启示?你能调较这个正则表达式,提高它的匹配效率吗? 思考:

如果改用忽略优先量词的正则表达式 ”.*?”

匹配文本”ab””cd” ,结果是什么?请自己分析.

五. 回溯的总结

在匹配的过程中NFA引擎会依次处理正则各个子表达式或组成元素.当遇到量词或多选结构时,它将面临两种或多种尝试选择.这时它会存储备用状态.即选择其一,同时记住其它可能的路径.

不论选择那一种途径,如果它能匹配成功,而且正则表达式的余下部分也成功了,整个匹配即告完成.如

48

正则表达式入门与提高

果正则表达式余下的部分匹配失败,引擎就会回溯到备用状态所指示的位置,选择其他的备用分支继续尝试.这样引擎最终可能尝试表达式所有可能的途径,直到余下部分匹配成功,或所有途径尝试完毕都失败,而停下来.

回溯的几个要点:

(1) 如果需要在”进行尝试”和”跳过尝试”之间选择,对于匹配优先量词,引擎会优先选择”进行尝试”,而对于忽略优先量词”,会选择”跳过尝试”.

(2) 对于多选结构,引擎从正则表达式的开始依次尝试各子表达.

(3) 如果正则表达式中有多个量词或多选结构,就意谓着有多个”备用状态”.至于引擎回溯时使用哪个”备用状态”,原则是:距离当前最近存储的选项就是”当本地失败时”强制回溯时返回的.

(4) 使用匹配优先还是忽略优先量词,有时会得到不同的匹配结果.如分别用<.*>或<.*?> 去匹配文本aaa. 但如果只有一条可能的路径,那么无论使用匹配优先还是忽略量词,其最终匹配结果都一样,不一样的只是引擎在达到最终匹配之前需要尝试的次数.如:分别用<.*>或<.*?>去匹配文本.

(5) 在环视的内部,其子表达式也有可能包含量词或多选结构,它们的回溯原理与上面所讨论的完全一样.但要提醒一点的是当引擎退出环视时,它将抛弃内部的所有”备用状态”.

六. 回溯与效率

NFA中的回溯是一个好东西,可以为正则表达式带来更多的功能,它也是很多正则引擎选择NFA的原因。但从上面的介绍中,我们也明显感觉到,回溯也可能带来效率的问题。所以,在我们编制正则表达式时,如果涉及量词或多选结构,就必须关注正则表达式的形式,尽可能减少回溯次数。

例:下面三个正则表达式在效果上是等价的 Jeffrey|Jeffery Jeff(rey|ery) Jeff(re|er)y

这三个表达式在某些情况下,效率是不一样的.其中效率最高的是第三个表达式. 为了说明这个问题,我们假设用它们来匹配下列目标文本:

Jeffery and Jeffrey

分析:用第一个表达式匹配,在位置0处留下”备用状态”,它的第一个分支在位置4处”r”不能匹配文本中的”e”,于是回溯选择第二个分支重新从位置0处开始尝试,最后匹配成功.找到文本的成功匹配: Jeffery

如果用第三个表达式匹配,当匹配到位置4时,留下”备用状态”. 用第一分支”er”尝试失败,这时回

49

VBA平台的正则学习参考资料

溯,用第二分支”er”尝试匹配成功.可以看出它的回溯与第一个表达式不同:不需要重新回退到位置0处. 从而减少了找到匹配的尝试次数.

在使用量词时,也要关注回溯导致的效率问题,如果可能尽量用替代方法. 前面多次遇到的一个例子: 提取目标文本中引号的内容:

本帖”正则表达式入门与提高”对你有用吗? 下面三个正则表达式都能实现目标: “”.*”” “”.*?”” “”[^””]+”” 分析:

第一个表达式是通过回溯来实现目标的.即需要回退6步.

第二个表达式在匹配引号内的字符时,每个位置都需要回溯一次,所以,共有10次回溯. 比较第一/二个表达式的效率高低,与具体文本中,引号内字符数与右引号后字符数多少有关. 而第三个表达式,只有一次回溯,它发生在字符匹配右双引号失败时,回退一步.

七.灾难回溯

有时不小心使用量词,则可能带来灾难性的后果. 现在我们用下面正则表达式 \\d+

匹配目标文本 123456X

显然,引擎最后将报告整个匹配失败.那么 正则表达式必须计算多少个路径才能得出此结论呢? 它会在此字符串开头处开始计算,发现字符 1 是一个有效的数字字符,与此正则表达式匹配。然后它会移动到字符 2,该字符也匹配。因此,在此时,此正则表达式与字符串 12 匹配。接下来,它会尝试 3(匹配123),依次类推,直到到达 X,该字符不匹配。

但是,由于我们的引擎是回溯 NFA 引擎,它不会在此点上停止。而是从其当前的匹配 (123456) 返回到其上一个已知的匹配 (12345),然后从那里再次尝试匹配。由于 5 后面的下一个字符不是此字符串的结尾,因此,此正则表达式不是匹配项,它会返回到其上一个已知的匹配 (1234),然后再次尝试匹配。按这种方

50

正则表达式入门与提高

式进行所有匹配,直到此引擎返回到其第一个匹配 (1),发现 1 后面的字符不是此字符串的结尾。此时,正则表达式停止,没有找到任何匹配。

总的说来,此引擎计算了六个路径:123456、12345、1234、123、12 和 1。如果此输入字符串再增加一个字符,则引擎会多计算一个路径。因此,此正则表达式相对于字符串长度的是线性算法,正则对象计算速度非常快,足以迅速拆分计算大量字符串(超过 10,000 个字符)。

现在我们把正则表达式修改为: (\\d+)

前面说过,括号并不会改变引擎匹配的路径,无非增加了一个处理括号的步骤. 但如果在括号后面增加一个”+”号,即: (\\d+)+ 情况会怎样呢?

分组表达式 (\\d+) 后面额外的 + 字符表明此正则表达式引擎可匹配任何数量的捕获组。此引擎按以前的方式进行计算,在到达123456 之后回溯到 12345。

这就是关键所在。此引擎不仅会检查到 5 后面的下一个字符不是此字符串的结尾,而且还会将下一个字符6 作为新的捕获组,并从那里开始重新检查。一旦此途径失败,它会返回到 1234,将 56 作为单独的捕获组,然后将5 和 6 分别作为单独的捕获组。最终结果是该引擎实际上完成了 32 个不同路径的计算。

现在,我们只要向此计算字符串再增加一个数字字符,该引擎将必须计算 64 个路径(翻了一倍)才能确定它不是匹配项。这会使正则表达式引擎执行的工作量呈指数增加.

你可以用30位数字在自己的电脑上测试. 需要强制引擎处理数亿个路径.如果电脑性能不好怕要处理好几天吧.

这就是传说中的”灾难回溯”! 也许没有人会写出这样的正则表达式,但其形式在构建复制正则表达式中也可能不经意地出现.这种形式就是在量词上再叠加量词.

有种网络攻击称之为”ReDoS”,就是利用这个原理瘫痪对方电脑. 现在有些语言平台对正则引擎作了优化,可以回避这个问题.但VBsrcipt中正则引擎没有.

51

VBA平台的正则学习参考资料

第四篇 技巧篇

文本处理归根到底是对特定串的捕捉(查找),所以我们讨论的所谓”技巧”聚焦于对特定字符串的描述.而现实中待处理的字符串在目标文本中的存在状态形形色色,与大多数文章以文本类型归类不同,本篇根据字符串结构特征分类进行讨论,试图从正则特性角度帮助初学者提高综合应用正则符号的能力.文中例举的是常见任务类型或任务的一部分,我们精选了一些较短小的,”天才”般构建的正则表达式进行讨论,相信通过你的仔细解读,一定会心有所悟,提升编写正则表达式的思维境界.也欢迎朋友们跟贴,把你所见所写的自认为有新意的正则表达式展示出来,供大家学习讨论.本篇的正则表达式有的来自世界级专家,也有的来自网络网友,在此一并致谢!

一. 匹配具有多种形态结构的字符串

在现实的世界中,有些数据类型具有多种呈现形式,如HTML标签,它可能有属性也可能没有,在一些位置上可能有若干空格也可能没有等等.

下面来讨论几个实例:

1. 匹配下列文本中的


标签,它可能呈现的形式例举如下:




正则表达式:

字符串中,””是标签的标志,是肯定出现的,所以用字面字符表示; 它的size属性可能出现也可能不出现,可用”?”描述,属性是一个相对独立的整体所以用括号包围,(?)?表示要么整体出现,要么都不出现;标签与属性之间至少必须有一个空格,用\\+表示;而其它有些地方可以没有空格也可以有多空格用\\s*表示.

52

正则表达式入门与提高

2. 匹配浮点数,它可能有下列几种呈现形式:

-56 0 +0.14 35.699 .123 正则表达式: [+-]?\\d+(\\.\\d+)?|\\.\\d+

+或-可用字符组[+-];它们可有可无可用[+-]?; 整数部分部分可用\\d+

小数部分可用\\.\\d+ ,小数部分可有可无,可用可选项元字符?,由于小数部分如果出现则小数点与后面的数据将同时出现,反之,同时不出现,所以用分组括号包围让?号整体作用即:(?:\\.\\d+)?

于是整合起来就是[+-]?\\d+(?:\\.\\d+)?

整数部分也不是必须出现的,即实际浮点数表示法,可以存在只是小数部分也合法.能否用[+-]?\\d*\\(\\.\\d+)?表示各种情形呢?这是不行的.因为在这个表达式中所有子表达式都可以不出现,就意味着什么都不匹配也匹配成功.也意味着什么都可以匹配.

它的解决办法是用选择符把它们作为两部分分别匹配两种可能的情况.即 [+-]?\\d+(\\.\\d+)?|\\.\\d+

在这里选择分支的前后顺序很重要,不能交换,你知道为什么吗?如果不明白建议回头再研究一下原理.

3. 匹配某范围内的数据

有时候我们需要匹配一个数值范围,如一天内的小时数,一月(年)内的天数,等等.遗憾的是正则表达式的元字符(序列)里没有专门用来表示数据范围的符号.

在正则中想要匹配包含多于一个数字的整数,就必须罗列出所有的数字组合.先看几个例子: 1-12 正则表达式: ^(1[0-2]|[1-9])$

1-22范围内有,1, 2, 3, ...12共有12个数字,事实上我们可以用多选结构把它们连接起来:1|2|...|12, 显然这不是我们需要的方案.

我们把1-12分为两部分:1-9和10-12. 第一部分可用字符组[1-9];第二部分可用表达式1[0-2],然后用多

53

VBA平台的正则学习参考资料

选结构组合起来,即实现了表示1-12范围内的数值.这时也要注意子表达式在多选结构中的顺序.

1-31 正则表达式: ^3[01]|[12][0-9]|[1-9]$

同样的思路把数值范围分为了三个部分,然后用多选结构组合起来,朋友们可自己去分析. 0-100

^100|[1-9][0-9]?$

在这里巧妙地用了一个可选项元素?,令第二个子表达式即可表示两位数字的数值,也可以表示只有一位数字的数值.这个表达式也可以写为

^100|[1-9][0-9]|[0-9]

从前面原理部分知道,多选结构采用的是回溯算法,所以这个表达式的效率不如用可选项元素?的效率高.

0-255 正则表达式:

^(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d$

这是一个IP4地址的各组数据允许的范围.我们把它分为四个独立的部分:第一25[0-5]表示250-255;第二2[0-4]\\d表示200-249;第三1\\d{2}表示100-199;第四[1-9]?\\d表示0-9和10-99

讨论:

(1) 表示多个字符之一时用字符组,表示多个字符串之一时用多选结构,表示可出现可不出现用可选项元素,表示可出现多次时用”+”或”*”. 所以在表示具有多种可能呈现状态的复杂字符串时,一般方法是把它们分组,然后选择字符组和量词连结起来;事实上,当在现实的世界中有较多的问题都可以把它转化为这种匹配类型.

(2) 一个正则表达式中,是不允许所有子表达式都是可选的情况.解决的方法是用多选结构把它们分为独立的多项.

(3) 使用正则表达式来匹配整数区间的所有技巧:你只需要对区间进行简单拆分,直到拆分之后的所有区间都只包含固定个数的彼此无关的数字为止.这个拆分思维很重要,在以后的技巧中我们还会遇到.

二、匹配特定位置上的字符串

54

正则表达式入门与提高

前几天看到一个正则求助的帖子,希望从一段包含汉字及数字的文本中提取所需数字数据.稍微麻烦的是文本中除了有要提取的数字数据外还有不需要提取的数字数据.很多坛友给出了正则表达式解法,但该求助者不满意,认为如果数字数据形式和在文本中位置改变了就不行了,于是坛友不断根据他的新要求修正正则表达式;有的回帖干脆直接用正则删除掉不要的数据与汉字,只留下需要的数据;事实上,坛友们的回帖解法方向都是正确的.结果求助者最后回帖十分遗憾地感叹到:”所有的回答都不尽我意,我是希望用正则表达式直接匹配要提取的数据!”

为什么他有这个想法呢?是因为他可能认为(也是较多初学者认为的)既然正则符号可以描述所有字符,那么一定可以描述要提取的数字数据(毕竟世界上没有一片相同树叶嘛).这种想法的误区是自觉不自觉地把正则符号孤立起来.

事实上,对于那些在特定文本中自己具有唯一数据结构特征的字符串,是可以通过直接描述它们,并加以处理的.但是如果要提取的字符串的结构特征在文本中不是唯一的,就必须以正则的思维将它们与之区别开来.而在正则中只能辅之以特定位置特征将提取字符串锁定.例如: ^\\s+可锁定行首空格;\\s+$可以锁定行尾空格等.

字符串在文本中的位置,可以用锚点^$,也可以用环视或者直接用与之相邻的其它特殊字符(串). 例1:从一段文本里的URL中抽取通信协议方案. 正则表达式

[a-z][a-z0-9+\\-.]*(?=://)

URL中 ”://” 部分的前面是通信方案,如:http之类的.在这里用环视(?=://)锁定的特定位置上的字符串,即提取的字符串”通信方案”,它的位置特征是后面紧跟特定字符串”://”.

前面的两个字符组共同表达了”通信方案”的数据结构特征:第1个位置上必须是一个英文字母;字母后面可以是第二个字符组中列出的字符的任意组合.

例2:在一大段文本中查找独立存在的任意十进制正整数. 正则表达式: (^|\\s)\\d+(?=$|\\s)

表示正整数很简单,即\\d+ .为了能在文本中把它与其它可能存在的正整数区分开来,按题目要求,在表达式的前面增加(^|\\s),后面增加(?=$|\\s),它们分别描述了要提取的正整数的位置特征: 即这个数的前面必须紧挨着行开始或者是一个不可见字符;后面必须紧跟着行尾或一个不可见字符.

例3:查找后面不跟着某个特定单词(如cat)的任意单词 正则表达式: \\b\\w+\\b(?!\\W+cat\\b)

单词可以用\\w+表示,在一段文本中要用单词分界行描述”单词”存在形态. 后面否定顺序环视

55

VBA平台的正则学习参考资料

(?!\\W+cat\\b)表示如果紧跟着一个或一个以上非单词字符(如空格) 并且后面还有一个单词cat,那么否定环视将报告匹配失败,于是整个匹配失败,否则报告匹配成功.即后面不存在单词cat,则整个匹配成功.

使用表位置的正则符号,不仅常用于锁定特定字符串,而且有时可提高匹配效率. 如:假如你要在一个有若干行的文本中,搜索行首的字符串”ABCD”, 那么最好加上脱字符”^”, 即^ABCD. 这样对于有的引擎来说,它只会在行首查找,如果在行首搜索失败,则引擎马上报告失败,它不会继续在文本行的其它位置搜索.

即使对没有作此优化的引擎,当在行首搜索失败进,它也只会在其它位置上开始搜索此位置是否开始位置,不是则马上放弃,移至下一位置.

如果没有在前面加上脱字符”^”,那么当在开始处搜索失败,引擎必须在其它每一个位置上搜索. 当遇到有连续出现的A或AB或ABC等字符串,那么它会按部就班地尝试下去,直至失败.这样显然增加了报告失败结论的匹配尝试次数.

三、匹配其内部由相似结构字符串构成的字符串

例1:匹配一个IPv4地址 正则表达式:

25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d(?:\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d){3} 看一个具体的IPv4地址: 61.135.136.142 我们知道IPv4地址有如下规范: (1) 有四组阿拉伯数字组成的数据; (2) 数据组之间用英文点号分隔; (3) 每组数据范围是:0-255

数值范围0-255,在上面已经遇见过,我们可以用25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d表示;后面三组数据其结构相似:都是由英文句点与数据组成.所以,我们用括号包围起来,然后跟上一个表范围的量词{3},它表示出现并且只出现三次括号内的字符串.

这时用了非捕获性括号(?:?),只起分组作用.事实上,凡是不需要存储的分组都应该有非捕获性括号.本篇中,有些地方我们用了捕获性括号,是为了让代码清晰一点,在实际应用中可修改.

例2:验证一个URL地址

严格按URL地址规范编制的正则表达式较复杂,下面是一个具有给出条件的正则表达式:必须包含一个域名,不允许用户名或口令

(https?|ftp)://[a-z0-9-]+(\\.[a-z0-9-]+)+([/?].+)?

我们先来看一个具体的IPv4地址:(它就是本页面的URL地址)

56

正则表达式入门与提高

http://club.excelhome.net/forum.php?mod=viewthread&tid=1128647&pid=7697090&page=6&extra=#pid7697090

它可分为三个部分:

(1) 通信协议.具体地址中是http:// .正则表达式中,用(https?|ftp)://表示.它可以匹配三种通信协议之一: http或https或ftp . 它是http|https|ftp更聪明的写法.后面的://是字面字符匹配也可作为识别IP地址的标志之一.

(2) 域. 具体地址中是club.excelhome.net .正则表达式中,用[a-z0-9-]+(\\.[a-z0-9-]+)+表示 . 第一个字符组表示域中第一个项允许出现的字母,量词”+”作用于字符组表示可以连续出现多次,但至少出现一次,在这里是不能用量词”*”的.注意后面括号内表示的一个具有特定结构的字符串,该字符串的结构特点是英文句点后跟着多个连续的英文字符或短线.我们用量词”+”作用于这个括号内的字符串,表示可以连续出现一次或多次.

(3) 最后是URL的路径或参数.正则中用 [/?].+ 表示. 这部分前面用了一个字符组规定紧跟”域”后的必须是正斜线”/”或问号”?”之一; 而”.+”表示抓取直到换行符之前的一切字符.正则表达式中,用量词”?”作用于 [/?].+ 部分,表示整个正则表达式也可以匹配没有参数或路径的URL.

例3:验证一个电子邮箱

下面给出一个最严格的电子邮箱正则表达式:

^[\\w!#$%&’*+/=?{|}~^-]+(?:\\.[!#$%&”*+/=?{|}~^-]+)*@(?:[a-z0-9-]+\\.)+[a-z]{2,6}$ 它看起来够复杂的,但其实整个结构很简单.共分为五个部分:

第一部分: 也就是第一个字符组[?.]+ 表示邮箱开始允许的字符及组合, 注意并没有包含句点; 第二部分: 是非捕获性括号内的内容,其结构为:(\\.[?]+)*. 括号的作用量词是”*”,表示可以不出现的.为什么要这部分呢?是因为英文名句点是可以作为邮箱用户名中字符的,但它不能在用户名开头或结尾,也不能出现连续的两个或两个以上句点. 我们通过\\.[...]+形式做到了这一点,即英文句点前或后必须至少一个其它字符,通过第一/二部分来表示邮箱用户名,可以严格验证用户名输入是否规范.

第三部分:即”@”,这是邮箱的标志符了.

第四部分:即”@”符号后括号内容.它表示子邮箱组织名,其结构为 [?]\\. 量词”+”作用于分组,表示至少出现一次;

第五部分:即[a-z]{2-6} . 是邮箱中的顶级域名.该表达式表示了它必须是由2-6个字母组成的字符.比如com

讨论:

我们看到,有些字符串其组成的内部,有相似结构,相似结构块的个数有可能是固定的如IP4地址,更多的是结构块的数目不定, 如URL的域部分和电子邮箱的组织名.这时我们可以用分组结合量词来表达它们.

57

VBA平台的正则学习参考资料

要指出的是,正则表达式编写的严格程度,总是与上下文环境联系在一起的,在实际应用中,应因地制宜. 比如明明是去匹配一个规范的电子邮箱,你就没有必要用上面的表达式了,而直接用^\\S+@\\S+$,也许更加快捷.

四、匹配一段文本,这段文本中不能包含特定字符串

我们曾遇到过匹配一对尖括号之间的字符串的例子.可以用<[^>]*>来表达.但如果这一对特定的字符串不是一个字符而是多个字符组成的呢?肯定是不能用否定字符组了,因为字符组内没有”词组”概念,只表达单个字符之一.这时我们可环视来解决这一问题.看下面的例子.

例1 有一目标文本,它也许是一大段文本中的一部分: ….ccccaaa/bbbdddd…..

要求: 提取标签及标签内的内容.即要求结果为:aaa/bbb 正则表达式: ((?!).)*

引擎找到连续字符串后,将在其后的每个位置上尝试匹配否定顺序环视:(?!), 它的意义是如果当前位置的后面没有连续字符串,那么匹配成功,并接着继续扫描正则的下一部分即”.”; 英文句点可以匹配除换行符处的所有字符.我们把它们用括号包围后让量词”*”作用,它的意思就是可以无限次重复((?!).)部分,直到它匹配失败.在上面目标文本中,显然”aaa/bbb”中每个字符之前位置上(?!)都是成功的.所以每个字符都会得到成功匹配.

而当引擎移到的”<”之前位置时,情况发生了变化:因为在该位置上环视的子表达式得到了成功匹配,而否定环视就会让引擎报告匹配失败.但这并不意味((?!).)*部分匹配失败,因为量词”*”表达的是即使括号()内匹配到0个字符(即不匹配),整个子表达式也算匹配成功.于是引擎继续依次扫描正则的最后部分,虽然目标文本中的已经被(?!)测试过,但它是不会”消耗”它们的,所以正则中的最后部分也得到成功匹配.这时引擎发现正则表达式中已经没有元素了,并且各子表达式或元素都匹配成功,于是报告整体匹配成功.

假如标签的结束标签不在同一行中怎么办呢? 元字符”.”是不能匹配换行符的.这时我们可以把”.”换为[\\s\\S]. 即

((?!)[\\s\\S])*

字符组[\\s\\S]可以匹配任意字符之一. 同样也可以用[\\w\\W]或[\\d\\D],它们都是等价的.这没有什么好解读的,它是常用技巧之一.可以理解为不可见字符与它的补集就构成了全部.

例2: 查找不包含另一单词(如cat)的任意单词

58

正则表达式入门与提高

\\b(?:(?!cat)\\W)+\\b

它的原理与例1是一样的.单词分界符”\\b”,限定了连续字符必须在两个字边界之内;这时用了\\W,即非英文单词字符代替”.”,目的是明显. 如果用”.”或[\\s\\S],那么它将匹配到文本中的所有字符.用了非捕获性括号是不想把匹配的单词放入特殊变量$中.因为匹配集合中就是所需要的单词,不必须用Submatches方法提取了.

例3 匹配不包含某个单词(如Cat)的整行 正则表达式 ^(?:(?!\\bcat\\b).)*$

注意例2,例3中单词边界符的应用差别. 讨论:

用上面的方法,其匹配效率是很低的.因为它将在每个位置上尝试(?!)或(?!cat).所以,如果可能,比如例2,3,我们可以用VBA的split方法生成单词数组,然后逐一检查.

五、匹配一对特殊字符界定的之间的字符串,但其内部包含两端的界定字符

我们要匹配一对双引号包围的内容,但被包围的内容中含有双引号;匹配一对括号内的内容,但括号内又嵌套括号??看下面的例子:(提示,如果直接输入VBE代码中进行测试,注意在字符变量与正则表达式中双引号的转义)

例1 目标文本

?.Needs a “2 \\” x3 \\” likeness” of ? 要求匹配结果:

“2 \\” x3 \\” likeness” 正则表达式: “”(\\\\.|[^\\\\””])+””

显然,不能用以前用过的办法:如””[^””]””或””.*?”” , 也不能用””.*”” ,因为文本的其它地方还可能出现双引号对.

编写正则表达式的重要技巧之一是: 集中关注在特定时刻真正容许匹配的字符.观察文本中匹配内容中的双引号特点:它始终与反斜线联结在一起.于是情况变得明朗起来,匹配内容实际上是由两部分组成的:

59

VBA平台的正则学习参考资料

一部分是 \\” ,即反斜线与双引号的组合;一部分是非反斜线和非双引号的任意字符. 用正则来表达这个意思就是:\\\\.|[^\\\\\我们用分组括号包围它们,然后用量词”+”作用(\\\\.|[^\\\\\表示可以重复匹配无限次这样的字符[^\\\\””]或字符串\\\\. ,直到它遭遇失败.

现在我们来看它的匹配过程:当在?.Need a 后的位置上时,它成功匹配双引号,接着尝试(\\\\.)部分与文本中”2”匹配失败,于是尝试[^\\\\””],匹配成功,重复分组,继续下一个位置......当引擎移动到likeness后的位置上时,此时文本的字符是一个双引号,而双引号既不能与(\\\\.)匹配也不能与[^\\\\””]匹配,所以此时正则括号部分匹配失败,回溯,而括号的作用量词是”+”,引擎报告整个量词作用的括号部分匹配成功,最后用双引号匹配文本中的双引号成功,完成整个匹配.

再给一个例子,请你自己尝试一下:匹配input.目标文本: ?..”>?..

例2 目标文本

Val=foo(bar(this),3.7)+2*(that-1)

要求:提取文本中括号中的内容,即提取出(bar(this),3.7)和(that-1)

仿例1的思路,真正要匹配的是两部分:一是内嵌的(?)部分; 二是其它普通字符.正则表达式: \\(([^()]|\\([^)]+\\))*\\)

匹配内嵌的(?)部分可用表达式:\\([^)]+\\);第二部分怎样表达呢?我们用了子表达式[^()]. 因为一当遇到内嵌”(“时,引擎将尝试第二个子表达式,即: \\([^)]+\\) ,该子表达式会一口气把内嵌括号匹配完.所以整个正则表达式能顺利完成任务.

我们能否用下列正则表达式呢? \\(([^()]*|(\\([^)]+\\))*)*\\)

这个表达式用量词*号作用[^()],可以一次把所有内嵌括号前或后的字符匹配完成,从而减少每次进入退出正则括号的处理次数;同样道理,用”*”作用\\([^]]+\\)部分,处理有多个内嵌括号的情况.这个主意看来不错.但这是绝对不行的,原理部分讨论过一个问题:灾难回溯.而这个表达式就符合灾难回溯的特征.量词被量词作用.

不过,下面这个表达式应该是一个效率更高的正则表达式: \\([^()]*(\\([^()]*\\))*[^()]*\\) 请自己解读吧.

例3 下面是一个由excel转换为csv文件格式的文本

Ten Thousand,1000,2710,,\’s \

60

正则表达式入门与提高

说明:excel转存为csv文本,如果原来内容中有双引号,它将自动转换为重复的双引号. 要求:提取由逗号分隔的各项数据

为了更清晰一点下面列出各项数据(共七项): The Thousand 1000 2710 空值 “10,000”

“It’s “”10 Grand””,baby” 10k

正则表达式:

(?:^|,)(?:\

除第一个数据项外,其所有数据之前都有逗号分隔,所以我们可以用”^”或”,”来分别锁定各数据,即用(?:^|,)(?.)形式分别提取各数据项,最后的结果中可能含有”,”,容易用VBA代码处理.

第二个问题是如何表达各数据项,在技巧一中,已经知道如果匹配项呈现多种状态,可以用多选结构与合适的量词来处理.现在来看它们的各种形态:一是由双引号包围的内容形式; 二是空值; 三是除一二外的形式.

第一种形态可以用例1,例2的方法:关注双引号内的字符实际只有两种类型即连续的双引号,转义后要用””””表达,其次是非双引号的字符用[^””]表达. 于是第一种形态可以表达为 “”(([^””]|””””)*)””,在本例中它可以匹配”10,000” 和\’s\

第三种形态实际就是非双引号[^””],而第二种形态空值,可与第三种形态合并即[^””]* ;量词”*”,可以表示不出现任何字符.

把它们整合起来就是:

(^|,)(“”(([^””]|””””)*)””|[^””]*)

这个表达式还有一个问题,即^或,匹配成功后,一当遇到非双引号部分时即尝试[^””]*时,它会匹配后面所有的字符. 如在位置0处它会匹配整个双引号前的部分即: Ten Thousand,1000,2710,,.所以为了迫使它在遇到逗号时停止,我们可以在[^””]中增加一个逗号即[^””,]*.最后的结果是:

(^|,)(“”(([^””]|””””)*)””|[^””,]*)

说明:你可以根据情况,将捕获性括号修改为非捕获括号.

61

VBA平台的正则学习参考资料

讨论:

集中关注在特定时刻真正容许匹配的字符,经常会解决多数问题,在具体处理的时候又利用了前面讨论过的”拆分”思想.即把需要匹配的字符(串),拆分成互不包含的若干部分.最后用多选结构及量词组合起来.

62

正则表达式入门与提高

结束语

看来没有什么人关注了,就在此打住了吧.

再次感谢liucqa老师、赵刚老师和香川才女的鼓励和肯定,也非常感谢前面各位朋友的捧场。对有位朋友将之转化为word文档所付出的劳动表示敬意,不过由于有时上帖的疏忽,所以修改了一些内容,并没有包括其中;另外转换的过程中,也有一些小问题,所以请慎用之.由于水平有限,帖中难免有错,热忱欢迎朋友们指正!

原贴地址:

Excel 正则表达式入门与提高-VBA平台的正则学习参考资料-ExcelVBA程序开发-ExcelHome技术论坛 - http://club.excelhome.net/thread-1128647-1-1.html

63

联系客服:779662525#qq.com(#替换为@)