一、限定符:限定符提供了一种简单方法,用于指定允许特定字符或字符集自身重复出现的次数。限定符始终引用限定符前(左边)的模式,通常是单个字符,除非使用括号创建模式组。
(一)非显示限定符
1、
*,描述“出现 0 或多次”。
2、
+,描述“出现 1 或多次”。
3、
?,描述“出现 0 或 1 次”。
(二)显式限定符
显式限定符使用花括号 {n,m} 及其中的数字值表示模式出现次数的上下限。
如果仅指定一个数字,则表示次数上限,例如,x{5} 将准确匹配 5 个 x 字符 (xxxxx),如果数字后跟一个逗号,如 x{5,},表示匹配任何出现次数大于 4 的 x 字符。
二、元字符
.(句点或点)元字符是最简单但最常用的一个字符。它可匹配任何单字符。如果要指定某些模式可包含任意组合的字符,使用句点非常有用,但一定要在特定长度范围内。
^ 元字符可指定字符串(或行)的开始。
$ 元字符可指定字符串(或行)的结束。通过将这些字符添加到模式的开始和结束处,可强制模式仅匹配精确匹配的输入字符串。如果 ^ 元字符用在方括号 [ ] 指定的字符类的开头,将有特殊的含义。具体内容后。
\ (反斜杠)元字符既可根据特殊含义“转义”字符,也可指定预定义集合元字符的实例。同样,具体内容见下。为了在正则表达式中包括文字样式的元字符,必须使用反斜杠进行“转义”。例如,如果要匹配以“c:\”开始的字符串,可使用:^c:\\。注意,要使用 ^ 元字符指出字符串必须以此模式作为开始,然后用反斜杠元字符转义文字反斜杠。
|(管道)元字符用于交替指定,特别用于在模式中指定“此或彼”。例如,a|b 将匹配包含“a”或“b”的任何输入内容,这与字符类 [ab] 非常类似。
( ) 括号用于给模式分组。它允许使用限定符让一个完整模式出现多次。为了便于阅读,或分开匹配特定的输入部分,可能允许分析或重新设置格式。
三、字符类:
字符类是正则表达式中的“迷你”语言,在方括号 [ ] 中定义。在表达式中使用字符类时,可在模式的此位置使用其中任何一个字符(但只能使用一个字符,除非使用了限定符)。请注意,不能使用字符类定义单词或模式,只能定义单个字符。
通过在括号中使用连字符 - 来定义字符的范围。连字符在字符类中有特殊的含义(不是在正则表达式中,因此,准确地说它不能叫正则表达式元字符),且仅在连字符不是第一个字符时,连字符才在字符类中有特殊含义。要使用连字符指定任何数值数字,可以使用 [0-9]。小写字母也一样,可以使用 [a-z],大写字母可以使用[a-z]。连字符定义的范围取决于使用的字符集。因此,字符在(例如)ascii 或 unicode 表中出现的顺序确定了在范围中包括的字符。如果需要在范围中包括连字符,将它指定为第一个字符。例如:[-.?] 将匹配 4 个字符中任何一个字符(注意,最后的字符是个空格)。另请注意,正则表达式元字符在字符类中不做特殊处理,所以这些元字符不需要转义。考虑到字符类是与其他正则表达式语言分开的一种语言,因此字符类有自己的规则和语法。
如果使用字符 ^ 作为字符类的第一个字符来否定此类,也可以匹配字符类成员以外的任何字符。因此,要匹配任何非元音字符,可以使用字符类 [^aaeeiioouu]。注意,如果要否定连字符,应将连字符作为字符类的第二个字符,如 [^-]。记住,^ 在字符类中的作用与它在正则表达式模式中的作用完全不同。
四、预定义的集合元字符
元字符
等效字符类
\a
匹配铃声(警报);\u0007
\b
匹配字符类外的字边界,它匹配退格字符,\u0008
\t
匹配制表符,\u0009
\r
匹配回车符,\u000d
\w
匹配垂直制表符,\u000b
\f
匹配换页符,\u000c
\n
匹配新行,\u000a
\e
匹配转义符,\u001b
\040
匹配 3 位 8 进制 ascii 字符。\040 表示空格(十进制数 32)。
\x20
使用 2 位 16 进制数匹配 ascii 字符。此例中,\x2- 表示空格。
\cc
匹配 ascii 控制字符,此例中是 ctrl-c。
\u0020
使用 4 位 16 进制数匹配 unicode 字符。此例中 \u0020 是空格。
\*
不代表预定义字符类的任意字符都只作为该字符本身对待。因此,\* 等同于 \x2a(是文字 *,不是 * 元字符)。
\p{name}
匹配已命名字符类“name”中的任意字符。支持名称是 unicode 组和块范围。例如,ll、nd、z、isgreek、isboxdrawing 和 sc(货币)。
\p{name}
匹配已命名字符类“name”中不包括的文本。
\w
匹配任意单词字符。对于非 unicode 和 ecmascript 实现,这等同于 [a-za-z_0-9]。在 unicode 类别中,这等同于 [\p{ll}\p{lu}\p{lt}\p{lo}\p{nd}\p{pc}]。
\w
\w 的否定,等效于 ecmascript 兼容集合 [^a-za-z_0-9] 或 unicode 字符类别[^\p{ll}\p{lu}\p{lt}\p{lo}\p{nd}\p{pc}]。
\s
匹配任意空白区域字符。等效于 unicode 字符类 [\f\n\r\t\v\x85\p{z}]。如果使用 ecmascript 选项指定 ecmascript 兼容方式,\s 等效于 [ \f\n\r\t\v] (请注意前导空格)。
\S
匹配任意非空白区域字符。等效于 unicode 字符类别 [^\f\n\r\t\v\x85\p{z}]。如果使用 ecmascript 选项指定 ecmascript 兼容方式,\s 等效于 [^ \f\n\r\t\v] (请注意 ^ 后的空格)。
\d
匹配任意十进制数字。在 ecmascript 方式下,等效于 unicode 的 [\p{nd}]、非 unicode 的 [0-9]。
\d
匹配任意非十进制数字。在 ecmascript 方式下,等效于 unicode 的 [\p{nd}]、非 unicode 的 [^0-9]。
正则表达式是一门灵活性非常强的语言,匹配同样的字符串可能在不同的开发人员那里会得到不同的结果,在平常的时候也是用的时候看看相关资料,不用的时候就丢在脑后了,尽管在处理大部分情况下都能迅速处理,但是处理一些复杂的情况效率仍是不高,借着前阵子做过的一个项目涉及到正则表达式的机会,将有关资料阅读了一遍并结合了自己的体会,整理了几篇利用 C# 进行正则表达式编程的文章,一来加深自己的印象和理解,二来供博客上的读者学习借鉴。
在 .NET 中提供了对正则表达式的支持,并且提供了相关的类,分别有: Regex 、 Match 、 Group 、 Capture 、 RegexOptions 、 MatchCollection 、 GroupCollection 、 CaptureCollection 。它们之间的关联如下:
对它们描述如下:
Regex :正则表达式类,代表了一个不可变的正则表达式。
Match :代表了 Regex 类的实例的一次匹配结果,可以通过 Regex 的 Match() 实例方法返回一个 Match 的实例。
MatchCollection :代表了 Regex 类的实例的所有匹配结果,可以通过 Regex 的 Matches() 实例方法返回一个 MatchCollection 的实例。
Group :表示单个捕获组的结果。由于一次匹配可能包含 0 个、 1 个或多个分组,所以 Match 的实例中返回的是捕获组集合的结果,即 GroupCollection 。
GroupCollection :表示单个匹配中的多个捕获组的集合,可以通过 Match 的 Groups 实例属性返回 GroupCollection 的实例。
Capture :表示单个捕获中的一个子字符串。同 Group 一样,由于一个捕获中可能包含 0 个、 1 个或多个子字符串,所以 Group 的实例中返回的是子字符串集合的结果,即 CaptureCollection 。
CaptureCollection :默认表示按照从里到外、从左到右的顺序由捕获组匹配到的所有子字符串集合,可以通过 Group 或者 Match 的 Captures 实例属性返回 CaptureCollection 的实例。注意,可以使用 RegexOptions.RightToLeft 来改变这种匹配顺序。
RegexOptions :提供用于设置正则表达式选项的枚举值。 像上面提到的 RightToLeft 就是它的一个枚举值之一,除此之外还有 None 、 IgnoreCase 、 Multiline 、 ExplicitCapture 、 Compiled 、 Singleline 、 IgnorePatternWhitespace 、 RightToLeft 、 ECMAScript 及 CultureInvariant 。 RegexOptions 枚举值可以相加,比如我们想匹配不区分大小写的字符串“ abc ”并且还想提高一下执行速度,那么可以写如下代码:
RegexOptions options=RegexOptions.IgnoreCase|RegexOptions.Compiled;
Regex regex=new Regex("abc",options);
Regex 、 Match 、 Group 及 Capture 的关系及成员
从上图可以看出 Regex 类提供了许多静态方法,很多方法还提供了多种重载方式(在图中对存在多种参数重载的方法都以“ … ”表示),除此之外我们还会发现 Capture 、 Group及 Match 之间存在继承关系(说实在话刚开始用的时候我发现它们之间存在着很多相同的字段,这让我当时迷惑不已,希望大家看到这个图后不要再像我当初那样迷惑了)。
在使用 C# 中的正则表达式进行文本处理之前先花点时间了解一下 .NET 中有关正则表达式的类和它们之间的关系是有必要的,这篇就算是预热篇了,在开始学习正则表达式之前做做热身运动。虽然在 C# 中有关正则表达式的类不多,但是对于初学者来说还是容易引起混淆,从而出现不知道该用哪些类的哪些方法或者属性的情况,这篇算是做个初步介绍吧。下一篇就先讲述 Regex 类,利用 Regex 可以用来替换、分割和处理字符串。
C#正则表达式编程(二):Regex类用法
对于正则表达式的应用,基本上可以分为验证、提取、分割和替换。仅仅利用Regex类就可以实现验证和简单替换。
利用Regex类实现验证
经历2009年的备案和DNS停止解析风波之后,大部分的带有反馈性的网站和论坛都对一些敏感词进行了过滤,包含有这类敏感词的文章要么内容被替换要么被禁止发表,利用Regex类就可以实现这个功能,下面是一个例子:
view plaincopy to clipboardprint?
///
public void IsMatchDemo()
{
string source = "刘备ABC关羽ABc张飞Abc赵云abc诸葛亮aBC孙权abC周瑜AbC鲁肃aBc曹操许攸郭嘉需晃袁绍";
Regex regex = new Regex("孙权");
//if (Regex.IsMatch(source, "孙权"))
//下面这句和上面被注释掉的一句作用的同样的
if (regex.IsMatch(source))
{
Console.WriteLine("字符串中包含有敏感词:孙权!");
}
///
public void IsMatchDemo()
{
string source = "刘备ABC关羽ABc张飞Abc赵云abc诸葛亮aBC孙权abC周瑜AbC鲁肃aBc曹操许攸郭嘉需晃袁绍";
Regex regex = new Regex("孙权");
//if (Regex.IsMatch(source, "孙权"))
//下面这句和上面被注释掉的一句作用的同样的
if (regex.IsMatch(source))
{
Console.WriteLine("字符串中包含有敏感词:孙权!");
}
输出结果:字符串中包含有敏感词:孙权!
对于上面的例子,如果要检查的字符串中包含“孙权”这个关键词就会在控制台上输出提示,当然在实际的应用中可能是包含有被禁止的词语的内容不允许提交而不是仅仅提示了。不过这类情况仍有办法可以绕过,可以使用“孙-权”或“孙+权”来替换孙权从而来绕过验证。
对于中文字符串还比较好说,对于英文的字符串还要考虑每个字母的大小写情况了。比如我们禁止在内容中出现某个关键词(如太CCTV的CCTV,或者CCAV),难道我们要针对字符串中每个字母的大小写情况进行多种情况的组合验证?不,完全没有必要,下面就是一个例子:
view plaincopy to clipboardprint?
///
public void IsMatchDemoWithOption()
{
string source = "刘备ABC关羽ABc张飞Abc赵云abc诸葛亮aBC孙权abC周瑜AbC鲁肃aBc曹操DEF许攸郭嘉需晃袁绍";
Regex regex = new Regex("def",RegexOptions.IgnoreCase);
if (regex.IsMatch(source))
{
Console.WriteLine("字符串中包含有敏感词:def!");
}
}
///
public void IsMatchDemoWithOption()
{
string source = "刘备ABC关羽ABc张飞Abc赵云abc诸葛亮aBC孙权abC周瑜AbC鲁肃aBc曹操DEF许攸郭嘉需晃袁绍";
Regex regex = new Regex("def",RegexOptions.IgnoreCase);
if (regex.IsMatch(source))
{
Console.WriteLine("字符串中包含有敏感词:def!");
}
}
输出结果:字符串中包含有敏感词:def!
在上面的例子中,实例化Regex时采用了两个带参数的构造函数,其中第二个参数就是上一篇中提到的RegexOptions枚举,RegexOptions.IgnoreCase表示匹配字符串的时候不管大小写是否一致。
此外,在Regex中存在着一些功能相同的静态方法和实例方法,如:IsMatch()方法,在第一个例子中我还写出了两种方法的实例,如下:
view plaincopy to clipboardprint?
Regex regex = new Regex("孙权");
//if (Regex.IsMatch(source, "孙权"))
//下面这句和上面被注释掉的一句作用的同样的
if (regex.IsMatch(source))
Regex regex = new Regex("孙权");
//if (Regex.IsMatch(source, "孙权"))
//下面这句和上面被注释掉的一句作用的同样的
if (regex.IsMatch(source))
其实在.NET Framework中很多类都有这样类似的情况,在System.IO命名空间下还有File及FileInfo这样的静态类和非静态类的情况,其实它们提供了相似的功能,用小沈阳的话说“这是为什么呢”?有部分是出自效率的考虑,并且也有出自让代码编写方便和看起来简洁的因素。对于偶尔一半次为之的情况,建议使用静态方法,这样有可能会提高效率(因为采用静态方法调用的正则表达式会被内部缓存,默认情况下会缓存15个,可以通过设置Regex类的CacheSize属性来更改缓存个数),如果是要在循环中多次使用,那就采用实例方法吧。
使用Regex类进行替换
上面的处理仅仅是查看提交的内容中是否有被禁止的关键词,其实有时候还可以做到将被禁止的关键词进行替换,例如将上面用到的字符串中的任何形式的"ABC"替换成"|",下面就是一个例子:
view plaincopy to clipboardprint?
///
public void Replace()
{
string source = "刘备ABC关羽ABc张飞Abc赵云abc诸葛亮aBC孙权abC周瑜AbC鲁肃aBc曹操DEF许攸郭嘉需晃袁绍";
Regex regex = new Regex("abc", RegexOptions.IgnoreCase);
string result=regex.Replace(source, "|");
Console.WriteLine("原始字符串:" + source);
Console.WriteLine("替换后的字符串:" + result);
}
///
public void Replace()
{
string source = "刘备ABC关羽ABc张飞Abc赵云abc诸葛亮aBC孙权abC周瑜AbC鲁肃aBc曹操DEF许攸郭嘉需晃袁绍";
Regex regex = new Regex("abc", RegexOptions.IgnoreCase);
string result=regex.Replace(source, "|");
Console.WriteLine("原始字符串:" + source);
Console.WriteLine("替换后的字符串:" + result);
}
输出结果:
原始字符串:刘备ABC关羽ABc张飞Abc赵云abc诸葛亮aBC孙权abC周瑜AbC鲁肃aBc曹操DEF许攸郭嘉需晃袁绍
替换后的字符串:刘备|关羽|张飞|赵云|诸葛亮|孙权|周瑜|鲁肃|曹操DEF许攸郭嘉需晃袁绍
实际上有时候我们遇到的情况可能不仅仅这么简单,例如有时候我们希望将字符串中的任何形式的“ABC”及“DEF”实现HTML形式的加粗,也就是替换成abc及def这种形式,当然还保持和原来一致的大小写形式,代码如下:
view plaincopy to clipboardprint?
///
public void ReplaceMatchEvaluator()
{
string source = "刘备ABC关羽ABc张飞Abc赵云abc诸葛亮aBC孙权abC周瑜AbC鲁肃aBc曹操DEF许攸郭嘉需晃袁绍";
Regex regex = new Regex("[A-Z]{3}", RegexOptions.IgnoreCase);
string result = regex.Replace(source, new MatchEvaluator(OutPutMatch));
Console.WriteLine("原始字符串:" + source);
Console.WriteLine("替换后的字符串:" + result);
}
///
/// 操作过程中的单个正则表达式匹配
///
private string OutPutMatch(Match match)
{
return "" + match.Value + "";
}
///
public void ReplaceMatchEvaluator()
{
string source = "刘备ABC关羽ABc张飞Abc赵云abc诸葛亮aBC孙权abC周瑜AbC鲁肃aBc曹操DEF许攸郭嘉需晃袁绍";
Regex regex = new Regex("[A-Z]{3}", RegexOptions.IgnoreCase);
string result = regex.Replace(source, new MatchEvaluator(OutPutMatch));
Console.WriteLine("原始字符串:" + source);
Console.WriteLine("替换后的字符串:" + result);
}
///
/// 操作过程中的单个正则表达式匹配
///
private string OutPutMatch(Match match)
{
return "" + match.Value + "";
}
输出结果如下:
原始字符串:刘备ABC关羽ABc张飞Abc赵云abc诸葛亮aBC孙权abC周瑜AbC鲁肃aBc曹操DEF许攸郭嘉需晃袁绍
替换后的字符串:刘备ABC关羽ABc张飞Abc赵云abc诸葛亮aBC孙权abC周瑜AbC鲁肃aBc曹操DEF许攸郭嘉需晃袁绍
在上面的例子中,我们使用了MatchEvaluator委托,并且还涉及到了Match类(Match类将会在下一篇讲述),在MatchEvaluator委托中使用到的Match类表示了单个的正则表达式匹配,通过改变match实例中Value的值来达到完成替换的目的。
在本篇中仅仅是讲述了Regex类的一些简单用法,也没有讲述正则表达式的相关知识,不过即使如此也能减轻我们的一部分工作,学习和灵活运用正则表达式是一个长期积累的过程。
前面两篇讲述了正则表达式的基础和一些简单的例子,这篇将稍微深入一点探讨一下正则表达式分组,在.NET中正则表达式分组是用Math类来代表的。
首先先看一段代码:
这段代码的执行效果如下:
Match=[1A ]
Capture=[1A ]
Groups[0]=[1A ]
Captures[0]=[1A ]
Groups[1]=[1A]
Captures[0]=[1A]
Groups[2]=[1]
Captures[0]=[1]
Groups[3]=[A]
Captures[0]=[A]
Match=[2B ]
Capture=[2B ]
Groups[0]=[2B ]
Captures[0]=[2B ]
Groups[1]=[2B]
Captures[0]=[2B]
Groups[2]=[2]
Captures[0]=[2]
Groups[3]=[B]
Captures[0]=[B]
………………此去省略一些结果
Match=[16N ]
Capture=[16N ]
Groups[0]=[16N ]
Captures[0]=[16N ]
Groups[1]=[16N]
Captures[0]=[16N]
Groups[2]=[16]
Captures[0]=[16]
Groups[3]=[N]
Captures[0]=[N]
通过对上面的代码结合代码的分析,我们得出下面的结论,在((\d+)([a-z]))\s+这个正则表达式里总共包含了四个Group,即分组,按照默认的从左到右的匹配方式,其中Groups[0]代表了整个分组,其它的则是子分组,用示意图表示如下:
在上面的代码中是采用了Regex类的Match()方法,调用这种方法返回的是一个Match,要处理分析全部的字符串,还需要在while循环的中通过Match类的NextMatch()方法返回下一个可能成功的匹配(可通过Match类的Success属性来判断是否成功匹配)。上面的代码还可以写成如下形式:
上面的这段代码和采用While循环遍历所有匹配的结果是一样的,在实际情况中有可能出现不需要全部匹配而是从某一个位置开始匹配的情况,比如从第32个字符处开始匹配,这种要求可以通过Match()或者Matches()方法的重载方法来实现,仅需要将刚才的实例代码中的MatchCollection matchCollection = r.Matches(text);改为MatchCollection matchCollection = r.Matches(text,48);就可以了。
输出结果如下:
Match=[5M ]
Capture=[5M ]
Groups[0]=[5M ]
Captures[0]=[5M ]
Groups[1]=[5M]
Captures[0]=[5M]
Groups[2]=[5]
Captures[0]=[5]
Groups[3]=[M]
Captures[0]=[M]
Match=[16N ]
Capture=[16N ]
Groups[0]=[16N ]
Captures[0]=[16N ]
Groups[1]=[16N]
Captures[0]=[16N]
Groups[2]=[16]
Captures[0]=[16]
Groups[3]=[N]
Captures[0]=[N]
注意上面的MatchCollection matchCollection = r.Matches(text,48)表示从text字符串的位置48处开始匹配,要注意位置0位于整个字符串的之前,位置1位于字符串中第一个字符之后第二个字符之前,示意图如下(注意是字符串“1A”与“2B”之间有空格):
在text的位置48处正好是15M中的5处,因此返回的第一个Match是5M而不是15M。这里还继续拿出第一篇中的图来,如下:
从上图可以看出Capture、Group及Match类之间存在继承关系,处在继承关系顶端的Capture类中就定义了Index、Length和Value属性,其中Index表示原始字符串中发现捕获子字符串的第一个字符的出现位置,Length属性表示子字符串的长度,而Value属性表示从原始字符串中捕获的子字符串,利用这些属性可以实现一些比较复杂的应用。例如在现在还有很多论坛仍没有使用所见即所得的在线编辑器,而是使用了一种UBB编码的编辑器,使用所见即所得的编辑器存在着一定的安全风险,比如可以在源代码中嵌入js代码或者其它恶意代码,这样浏览者访问时就会带来安全问题,而使用UBB代码就不会代码这个问题,因为UBB代码包含了有限的、但不影响常规使用的标记并且支持UBB代码的编辑器不允许直接在字符串中出现HTML代码,也而就避免恶意脚本攻击的问题。在支持UBB代码的编辑器中输入的文本在存入数据库中保存的形式是UBB编码,显示的时候需要将UBB编码转换成HTML代码,例如下面的一段代码就是UBB编码:
http://zhoufoxcn.blog.51cto.com[/url][url=http://blog.csdn.net/zhoufoxcn]周公的专栏
下面通过例子演示如何将上面的UBB编码转换成HTML代码:
程序执行结果如下:
原始UBB代码:[url=http://zhoufoxcn.blog.51cto.com][/url][url=http://blog.csdn.net/zhoufoxcn]周公的专栏[/url]**
替换后的代码:http://zhoufoxcn.blog.51cto.com周公的专栏**
上面的这个例子就稍微复杂点,对于初学正则表达式的朋友来说,可能有点难于理解,不过没有关系,后面我会讲讲正则表达式。在实际情况下,可能通过match.Groups[0].Value这种方式不太方便,就想在访问DataTable时写string name=dataTable.Rows[i][j]这种方式一样,一旦再次调整,这种通过索引的方式极容易出错,实际上我们也可以采用名称而不是索引的放来来访问Group分组,这个也会在以后的篇幅中去讲。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章