多少年来许多的编程语言和工具都包含对规则表达式的支持NET基础类库中包含有一个名字空间和一系列可以充分发挥规则表达式威力的类而且它们也都与未来的Perl 中的规则表达式兼容
此外regexp类还能够完成一些其他的功能例如从右至左的结合模式和表达式的编辑等
在这篇文章中我将简要地介绍SystemTextRegularExpression中的类和方法一些字符串匹配和替换的例子以及组结构的详细情况最后还会介绍一些你可能会用到的常见的表达式
应该掌握的基础知识 规则表达式的知识可能是不少编程人员常学常忘的知识之一在这篇文章中我们将假定你已经掌握了规则表达式的用法尤其是Perl 中表达式的用法NET的regexp类是Perl 中表达式的一个超集因此从理论上说它将作为一个很好的起点我们还假设你具有了C#的语法和NET架构的基本知识
如果你没有规则表达式方面的知识我建议你从Perl 的语法着手开始学习在规则表达式方面的权威书籍是由杰弗里·弗雷德尔编写的《掌握表达式》一书对于希望深刻理解表达式的读者我们强烈建议阅读这本书
RegularExpression组合体 regexp规则类包含在SystemTextRegularExpressionsdll文件中在对应用软件进行编译时你必须引用这个文件例如csc r:SystemTextRegularExpressionsdll foocs命令将创建fooexe文件它就引用了SystemTextRegularExpressions文件
名字空间简介 在名字空间中仅仅包含着个类和一个定义它们是
Capture: 包含一次匹配的结果
CaptureCollection: Capture的序列
Group: 一次组记录的结果由Capture继承而来
Match: 一次表达式的匹配结果由Group继承而来
MatchCollection: Match的一个序列
MatchEvaluator: 执行替换操作时使用的代理
Regex编译后的表达式的实例
Regex类中还包含一些静态的方法
Escape: 对字符串中的regex中的转义符进行转义
IsMatch: 如果表达式在字符串中匹配该方法返回一个布尔值
Match: 返回Match的实例
Matches: 返回一系列的Match的方法
Replace: 用替换字符串替换匹配的表达式
Split: 返回一系列由表达式决定的字符串
Unescape:不对字符串中的转义字符转义
简单匹配 我们首先从使用RegexMatch类的简单表达式开始学习
Match m = RegexMatch(abracadabra (a|b|r)+);
我们现在有了一个可以用于测试的Match类的实例例如if (mSuccess)
如果想使用匹配的字符串可以把它转换成一个字符串
ConsoleWriteLine(Match=+mToString());
这个例子可以得到如下的输出: Match=abra这就是匹配的字符串了
字符串的替换
简单字符串的替换非常直观例如下面的语句
string s = RegexReplace(abracadabra abra zzzz);
它返回字符串zzzzcadzzzz所有匹配的字符串都被替换成了zzzzz
现在我们来看一个比较复杂的字符串替换的例子
string s = RegexReplace( abra @^\s*(*?)\s*$ $);
这个语句返回字符串abra其前导和后缀的空格都去掉了
上面的模式对于删除任意字符串中的前导和后续空格都非常有用在C#中我们还经常使用字母字符串在一个字母字符串中编译程序不把字符 \ 作为转义字符处理在使用字符\指定转义字符时@是非常有用的另外值得一提的是$在字符串替换方面的使用它表明替换字符串只能包含被替换的字符串
匹配引擎的细节
现在我们通过一个组结构来理解一个稍微复杂的例子看下面的例子
string text = abracadabraabracadabraabracadabra;
string pat = @
( # 第一个组的开始
abra # 匹配字符串abra
( # 第二个组的开始
cad # 匹配字符串cad
)? # 第二个组结束(可选)
) # 第一个组结束
+ # 匹配一次或多次
;
//利用x修饰符忽略注释
Regex r = new Regex(pat x);
//获得组号码的清单
int[] gnums = rGetGroupNumbers();
//首次匹配
Match m = rMatch(text);
while (mSuccess)
{
//从组开始
for (int i = ; i < gnumsLength; i++)
{
Group g = mGroup(gnums[i]);
//获得这次匹配的组
ConsoleWriteLine(Group+gnums[i]+=[+gToString()+]);
//计算这个组的起始位置和长度
CaptureCollection cc = gCaptures;
for (int j = ; j < ccCount; j++)
{
Capture c = cc[j];
ConsoleWriteLine( Capture + j + =[+cToString()
+ ] Index= + cIndex + Length= + cLength);
}
}
//下一个匹配
m = mNextMatch();
}
这个例子的输出如下所示
Group=[abra]
Capture=[abracad] Index= Length=
Capture=[abra] Index= Length=
Group=[cad]
Capture=[cad] Index= Length=
Group=[abra]
Capture=[abracad] Index= Length=
Capture=[abra] Index= Length=
Group=[cad]
Capture=[cad] Index= Length=
Group=[abra]
Capture=[abracad] Index= Length=
Capture=[abra] Index= Length=
Group=[cad]
Capture=[cad] Index= Length=
我们首先从考查字符串pat开始pat中包含有表达式第一个capture是从第一个圆括号开始的然后表达式将匹配到一个abra第二个capture组从第二个圆括号开始但第一个capture组还没有结束这意味着第一个组匹配的结果是abracad 而第二个组的匹配结果仅仅是cad因此如果通过使用?符号而使cad成为一项可选的匹配匹配的结果就可能是abra或abracad然后第一个组就会结束通过指定+符号要求表达式进行多次匹配
现在我们来看看匹配过程中发生的情况首先通过调用Regex的constructor方法建立表达式的一个实例并在其中指定各种选项在这个例子中由于在表达式中有注释因此选用了x选项另外还使用了一些空格打开x选项表达式将会忽略注释和其中没有转义的空格
然后取得表达式中定义的组的编号的清单你当然可以显性地使用这些编号在这里使用的是编程的方法如果使用了命名的组作为一种建立快速索引的途径这种方法也十分有效
接下来是完成第一次匹配通过一个循环测试当前的匹配是否成功接下来是从group 开始重复对组清单执行这一操作在这个例子中没有使用group 的原因是group 是一个完全匹配的字符串如果要通过收集全部匹配的字符串作为一个单一的字符串就会用到group 了
我们跟蹤每个group中的CaptureCollection通常情况下每次匹配每个group中只能有一个capture但本例中的Group则有两个captureCapture和Capture如果你仅需要Group的ToString就会只得到abra当然它也会与abracad匹配组中ToString的值就是其CaptureCollection中最后一个Capture的值这正是我们所需要的如果你希望整个过程在匹配abra后结束就应该从表达式中删除+符号让regex引擎知道我们只需要对表达式进行匹配
基于过程和基于表达式方法的比较 一般情况下使用规则表达式的用户可以分为以下二大类第一类用户尽量不使用规则表达式而是使用过程来执行一些需要重复的操作第二类用户则充分利用规则表达式处理引擎的功能和威力而尽可能少地使用过程
对于我们大多数用户而言最好的方案莫过于二者兼而用之了我希望这篇文章能够说明NET语言中regexp类的作用以及它在性能和复杂性之间的优劣点
基于过程的模式
我们在编程中经常需要用到的一个功能是对字符串中的一部分进行匹配或其他一些对字符串处理下面是一个对字符串中的单词进行匹配的例子
string text = the quick red fox jumped over the lazy brown dog;
SystemConsoleWriteLine(text=[ + text + ]);
string result = ;
string pattern = @\w+|\W+;
foreach (Match m in RegexMatches(text pattern))
{
// 取得匹配的字符串
string x = mToString();
// 如果第一个字符是小写
if (charIsLower(x[]))
// 变成大写
x = charToUpper(x[]) + xSubstring( xLength);
// 收集所有的字符
result += x;
}
SystemConsoleWriteLine(result=[ + result + ]);
正象上面的例子所示我们使用了C#语言中的foreach语句处理每个匹配的字符并完成相应的处理在这个例子中新创建了一个result字符串这个例子的输出所下所示
text=[the quick red fox jumped over the lazy brown dog]
result=[The Quick Red Fox Jumped Over The Lazy Brown Dog]
基于表达式的模式
完成上例中的功能的另一条途径是通过一个MatchEvaluator新的代码如下所示
static string CapText(Match m)
{
//取得匹配的字符串
string x = mToString();
// 如果第一个字符是小写
if (charIsLower(x[]))
// 转换为大写
return charToUpper(x[]) + xSubstring( xLength);
return x;
}
static void Main()
{
string text = the quick red fox jumped over the
lazy brown dog;
SystemConsoleWriteLine(text=[ + text + ]);
string pattern = @\w+;
string result = RegexReplace(text pattern
new MatchEvaluator(TestCapText));
SystemConsoleWriteLine(result=[ + result + ]);
}
同时需要注意的是由于仅仅需要对单词进行修改而无需对非单词进行修改这个模式显得非常简单
常用表达式
为了能够更好地理解如何在C#环境中使用规则表达式我写出一些对你来说可能有用的规则表达式这些表达式在其他的环境中都被使用过希望能够对你有所帮助
罗马数字
string p = ^m*(d?c{}|c[dm]) + (l?x{}|x[lc])(v?i{}|i[vx])$;
string t = vii;
Match m = RegexMatch(t p);
交换前二个单词 string t = the quick brown fox;
string p = @(\S+)(\s+)(\S+);
Regex x = new Regex(p);
string r = xReplace(t $$$ );
关健字=值
string t = myval = ;
string p = @(\w+)\s*=\s*(*)\s*$;
Match m = RegexMatch(t p);
实现每行个字符
string t = ********************
+ ******************************
+ ******************************;
string p = {};
Match m = RegexMatch(t p);
月/日/年 小时:分:秒的时间格式 string t = // ::;
string p = @(\d+)/(\d+)/(\d+) (\d+):(\d+):(\d+);
Match m = RegexMatch(t p);
改变目录(仅适用于Windows平台)
string t = @C:\Documents and Settings\user\Desktop\;
string r = RegexReplace(t@\\user\\ @\\user\\);
扩展位转义符 string t = %; // capital A
string p = %([AFaf][AFaf]);
string r = RegexReplace(t p HexConvert);
删除C语言中的注释(有待完善)
string t = @
/*
* 传统风格的注释
*/
;
string p = @
/\* # 匹配注释开始的定界符
*? # 匹配注释
\*/ # 匹配注释结束定界符
;
string r = RegexReplace(t p xs);
删除字符串中开始和结束处的空格
string ta = leading;
string pa = @^\s+;
string ra = RegexReplace(ta pa );
string tb = trailing ;
string pb = @\s+$;
string rb = RegexReplace(tb pb );
在字符\后添加字符n使之成为真正的新行
string t = @\ntest\n;
string r = RegexReplace(t @\\n \n);
转换IP地址 string t = ;
string p = ^ +
@([]?\d\d|[]\d|[])\ +
@([]?\d\d|[]\d|[])\ +
@([]?\d\d|[]\d|[])\ +
@([]?\d\d|[]\d|[]) +
$;
Match m = RegexMatch(t p);
删除文件名包含的路径
string t = @c:\filetxt;
string p = @^*\\;
string r = RegexReplace(t p );
联接多行字符串中的行 string t = @this is
a split line;
string p = @\s*\r?\n\s*;
string r = RegexReplace(t p );
提取字符串中的所有数字
string t = @
test
test
test
;
string p = @(\d+\?\d*|\\d+);
MatchCollection mc = RegexMatches(t p);
找出所有的大写字母 string t = This IS a Test OF ALL Caps;
string p = @(\b[^\Waz_]+\b);
MatchCollection mc = RegexMatches(t p);
找出小写的单词
string t = This is A Test of lowercase;
string p = @(\b[^\WAZ_]+\b);
MatchCollection mc = RegexMatches(t p);
找出第一个字母为大写的单词
string t = This is A Test of Initial Caps;
string p = @(\b[^\Waz_][^\WAZ_]*\b);
MatchCollection mc = RegexMatches(t p);
找出简单的HTML语言中的链接
string t = @
<html>
<a >first tag text</a>
<a >next tag text</a>
</html>
;
string p = @<A[^>]*?HREF\s*=\s*[]? + @([^ >]+?)[ ]?>;
MatchCollection mc = RegexMatches(t p si);