想必很多读者都写过给程序代码按语法着色的程序。而这在一段时间以前是一件很困难的事。你需要写大量代码分析语法——而这往往又是最困难的部分。直到,正则表达式(Regular Expression)的出现,我们才可以从繁重的工作中解脱。正则表达式提供了一系列方法(标准、模式),使我们能够高效地创建、比较和修改字符串,以及迅速地分析大量文本和数据以搜索、移除和替换文本模式 [1] 。DotNET Framework 提供了 System.Text.RegularExpression 命名空间来实现他们承诺的功能。
1. 正则表达式 [2]
首先,我想先简单介绍一下正则表达式。
正则表达式最早是由数学家Stephen Kleene于1956年提出,他是在对自然语言的递增研究成果的基础上提出来的。具有完整语法的正则表达式使用在字符的格式匹配方面上,后来被应用到熔融信息技术领域。自从那时起,正则表达式经过几个时期的发展,现在的标准已经被ISO(国际标准组织)批准和被Open Group组织认定。
正则表达式并非一门专用语言,但它可用于在一个文件或字符里查找和替代文本的一种标准。它具有两种标准:基本正则表达式(BRE),扩展正则表达式(ERE)。ERE包括BRE功能和另外其它的概念。
先进已有xsh,egrep,sed,vi以及在UNIX平台下的程序实现了正则表达式。它们可以被很多语言采纳,如HTML 和XML,这些采纳通常只是整个标准的一个子集。随着正则表达式移植到交叉平台的程序语言的发展,它的功能也日益完整,使用也逐渐广泛。
2. 相关的表达式
有关正则表达式我只能说这么多了——它是一个不小的知识体系,不可能用只言片语就解释清楚。这里我只介绍与C#语法分析相关的结个匹配串。详细内容请参见本Blog站的收藏 Regular Expression Specification [ The Open Group ] 。 另外,如果你已经对正则表达式有了相当的了解,那你可以略过下面每一条的解释,以尽快完成全文。
i> 字符串 "(\\?.)*?"
正则表达式中除 . $ ^ { [ ( | ) * + ? \ 外,其他字符与自身匹配。在上面的式子中,两边的quotation mark就是指匹配字符串两边的引号。“\\”表示一个“\”字符。后面紧跟的“?”表示匹配零个或一个字符。“.” 与除 \n 之外的任何字符匹配。
“()”表示捕获匹配的子字符串。使用 () 的捕获根据左括号的顺序从1 开始自动编号。捕获元素编号为零的第一个捕获是由整个正则表达式模式匹配的文本。括号后面的“*”表示存在一个或多个这样的子字符串。即“*”是作用于“(\\?.)”的。
“?”的存在使空字符串也可以被捕获。
ii> 逐字字符串 @"(""|.)*?"
匹配类似于 @"Hello ""World ""!" 的字符串。
与用 | (垂直条)字符分隔的任何一个术语匹配;例如, cat|dog|tiger 。使用最左侧的成功匹配。
iii> C# 文档信息中的xml元素 ///\s*<.*>
匹配C#自动化XML文档。“\s”表示任何空白字符。需要注意的是,请不要随意修改大小写。因为在正则表达式是大小写敏感的,在它的通配符中,大小写字符往往表示完全相反的意思。比如,“\S”表示任何非空白字符。(下面的 “\Z”也是这样)
iv> C# 文档信息中的内容 ///\s?.*
v> 空行 ^\s*\Z
“^”指定匹配必须出现在字符串的开头或行的开头。而“\Z”表示指定匹配必须出现在字符串的结尾或字符串结尾的 \n 之前。
vi> C# 注释 //.*
vii> C# 关键字 (abstract|where|while|yield){1}(\.|(\s)+|;|,|\(|\[){1}
篇幅所限,这儿只列出了很少几个关键字(C#有至少80个关键字 ^_^)。需要注意的是,解析器会匹配左边第一个成功项。因此,具有包含关系的单词应注意顺序:包含者要放在被包含者之前。例如:(in|int) 解析其会查不到 int,所以应该是 (int|in)。
除此之外还有,所有的括号 (\{|\[|\(|\}|\]|\)) 。 |
3. 相关类与其成员 [3]
[Serializable]
public class Regex : ISerializable
// 表示不可变的正则表达式。 |
Regex 类包含若干静态方法,使您无需显式创建 Regex 对象即可使用正则表达式。使用静态方法等效于构造 Regex 对象,使用该对象一次然后将其销毁。
Regex 类是不可变(只读)的,并且具有固有的线程安全性。可以在任何线程上创建 Regex 对象,并在线程间共享。
以上摘自微软的开发文档。我们还需要用到它的几个成员:
// 在指定的输入字符串中搜索 Regex 构造函数中指定的正则表达式匹配项。
public Match Match(
string intput
) |
对于 Match 类
[Serializable]
public class Match : Group
// 表示单个正则表达式匹配的结果。有关 Group 的详细信息请参见微软开发文档。 |
我们会用到它的下列成员
// 原始字符串中发现捕获的子字符串的从零开始的起始位置。
public int Index { get; }
// 捕获的子字符串的长度。
public int Length { get; }
// 通过匹配捕获的实际子字符串。
public int Value { get; }
// 获取一个值,该值指示匹配是否成功。
public bool Success { get; }
// 获取由正则表达式匹配的组的集合。
public virtual GroupCollection Groups { get; }
// 从上一个匹配结束的位置(即在上一个匹配字符之后的字符)开始
// 返回一个包含下一个匹配结果的新 Match。
public Match NextMatch(); |
以及 Group 类的相应成员(上面列出的 Match 的成员中,前四个属性都是由 Group 类继承而来,因此这些成员将不再一一列出)。
匹配字符串必须在 Regex 类的实例初始化的时候指定。你可以使用构造函数创建一个实例,使用它,然后销毁它。或者直接使用静态方法,这等效于创建实例。不过,经过测试,我发现静态方法要稍稍慢于编译的 Regex 对象。请看下面的一组测试数据:
4. 撰写代码
我们现在需要对第三节中列出的C#语言元素进行分析。我所采取的是逐行分析(如果要采取多行分析,则相关表达式需要进行修改 [4] )。
using System.Text.RegularExpression;
// Some other codes ... ...
// 首先创建 Regex 实例(以字符串的解析为例)。
Regex DoubleQuotedString = new Regex( "\"(\\\\?.)*?\"" );
// 然后去匹配字符串。
Match m;
for( m = DoubleQuotedString.Match( strSomeCodes ) ; m.Success ; m.NextMatch() ) {
foreach( Group g in m.Groups ) {
// Do some drawings
}
} |
剩下的事就是写着色代码了。
5. 源代码
注:
[1] "能够……文本模式" 引自 .NET Framework 常规参考 中的 正则表达式语言元素
[2] 正则表达式简介 此处有关正则表达式的简介参考自 ZDNet China 技术与开发 中的相关内容。
[3] 本节中出现的类与函数的签名与注释均出自微软文档。
[4] 多行分析 详情请参见 .NET Framework 常规参考 正则表达式语言元素 | |