Levy.Net

它山之石,可以为错; 它山之石,可以攻玉
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

点滴正则表达式

Posted on 2008-03-25 17:25  LevyLiu  阅读(277)  评论(2)    收藏  举报
 .Net的正则表达式,很早就看了,工作中用得不多,快淡忘了。这段时间公司没什么事,又复习了一遍。
在这里,我不是通遍去介绍正则表达式,这样的文章太多了,MSDN也有一些。我只是记录一下心得,点滴一下正则表达式中不太容易理解的地方,主要是给自己做一下笔记,如果能给你一个参考,也是一种收获。
说实话,不知道是不是中文版的MSDN的翻译有问题还是什么,在MSDN上看正则表达式的资料不太容易看懂,有的名字太拗口,给人的感觉是太专业了。比如把(?! )这个分组叫零宽度正预测先行断言,让我愣在那里读了半天。

一.正则表式类:

1.  Regex
这个类是正则表达式的核心。其中常用的就几个方法:MatchMatchesReplaceSplitIsMatch。这几个方法都有实例方法和静态方法的重载。

2.  MatchGroupCapture以及它们相关的集合类
对这几个类的名词解释就不在这里说了,参考MSDN就行了。在这里主要说一下它们的关系。这几个类的构造函数都被声明为internal,所以Match只能从Regex获得,GroupCapture只能从对应的集合类中获得。
Capture本身是一个基类,是正则表达式的子表达式的捕获结果。
Group是单个捕获组的结果,是从Capture类继承,Group包含了CaptureCollectionCaptureCollection是指当前的捕获组能捕获到的所有结果。简单的来说Group本身也是一个捕获(Capture),CaptureCollection的最后一个元素(为什么不是第一个元素?)就指向了Group本身。
Match是单个正则表达式匹配的结果。应该是说单个正则表达式尽可能多的匹配的结果,它包含了GroupCollectionGroupCollection获取由正则表达式匹配的组的集合。它是从Group继承的,也就是说Match本身也是一个捕获(Capture)GroupCollection的第一个元素就指向了Match本身。所以Match.Captures就指的是Match.Groups[0].Captures
MatchCollection类的理解就很简单,就是指成功的非重叠匹配。
下面就用一个例子来说明它们的关系。

            Regex r = new Regex("(Abc)+");
            Match m 
= r.Match("XYZAbcAbcAbcXYZAbcAb");
            GroupCollection gc 
= m.Groups;
            Console.WriteLine(
"Captured groups = " + gc.Count.ToString());
            
for (int i = 0; i < gc.Count; i++)
            
{
                cc 
= gc[i].Captures;
                counter 
= cc.Count;
                
// Print number of captures in this group.
                Console.WriteLine("Captures count = " + counter.ToString());
                
// Loop through each capture in group.
                for (int ii = 0; ii < counter; ii++)
                
{
                    
// Print capture and position.
                    Console.WriteLine(cc[ii] + "   Starts at character " +
                        cc[ii].Index);
                }

            }

                     

         输出的结果:

         Captured groups = 2

    Captures count = 1

    AbcAbcAbc   Starts at character 3

    Captures count = 3

    Abc   Starts at character 3

    Abc   Starts at character 6

    Abc   Starts at character 9
说明:
正则表达式: "(Abc)+",这是个贪婪捕获,它会尽可能多的捕获至少一组Abc,所以m.ValueAbcAbcAbc”而不是“Abc”,而m捕获组(GroupCollection)则有两个Group,一个是“AbcAbcAbc(就是Match本身),另一个则是“Abc”。gc[0]Captures只有一个元素,因为对于“AbcAbcAbc”这个捕获组,它只捕获到了一个。在这里m=gc[0]=gc[0].Captures[0].gc[1],因为其捕获组的值是“Abc”,所以其在“AbcAbcAbc”字符串上有三个Captures:Abc”,只是索引不一样。在这里,gc[1]=gc[1].Captures[2]

二.正则表达式符号
在这里,不一一列出正则表达式符号的解释,只把一些特殊应用说一下。

1.       ? 符号:
A). ?最简单的用法是作为限定符使用,就是匹配前面的子表达式零次或一次,等价于 {0,1}
B). 限定其他限定符,就是紧跟 (*+?{n} {n,} {n,m}) 后面,这样使用的匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。
下面就用一个例子简单说明一下:
string url = "http://www.contoso.com:8080/letters";
Regex r = new Regex(@"^(?<proto>\w+)://[^/]+?(?<port>:\d+)?/");
Match m = r.Match(url); //m.Value=http://www.contoso.com:8080/
 string result = m.Result("${proto}${port}");
注意,在这里的非贪婪模式:“+?”,它使“[^/]+”尽可能少的去捕获,这样当它遇到“:8080”字符串时就停止捕获,因为“(?<port>:\d+)?捕获组会处理这个字符串。如果把正则表达式改为"^(?<proto>\w+)://[^/]+ (?<port>:\d+)?/"(去掉“?”),m.Value不会有变化,但“?<port>”这个捕获组将捕获不到任何东西。
C). (?:pattern)。匹配 pattern 但不捕获组,不进行存储供以后使用。就是说Match.Groups中不含此捕获组。
D). (?=pattern)MSDN上的名词解释太别扭了,有的资料称这个为正向预查。就是说仅当子表达式在此位置的右侧匹配时才算匹配成功。它不仅不捕获组也不捕获pattern,它仅仅用于匹配。
下面的例子可以看出(?:pattern)(?=pattern)的区别:
Match m1 = Regex.Match("C#.net,PHP,Java", @"([\w#])+(.net)");
m1.Value值为C#.net,有三个捕获组”C#.net”, ”#”, “.net”

Match m2 = Regex.Match("C#.net,PHP,Java", @"([\w#])+(?:.net)");
m2.Value值为C#.net,只有两个捕获组”C#.net”, ”#”

Match
m3 = Regex.Match("C#.net,PHP,Java", @"([\w#])+(?=.net)");
m3.Value值为”C#”, 只有两个捕获组”C#.net”, ”#”

E). (?!pattern)。这是负向预查,和(?=pattern)相似,只是匹配条件是相反的。
Match m4 = Regex.Match("Windows 2000,Windows 98,Windows XP", @"Windows (?!NT|2000|2003)");
m4.Value Windows 98 Windows,不是 Windows 2000Windows
F). (?(expression)yes|no) .表达测试,替换构造之一, 相当于 (?(?=expression)yes|no) 。就是说如果匹配了expression,则继续用yes选项来匹配,否则用no来匹配.no部分可以省略,这时如果expression匹配了并且还要与yes匹配,否则匹配不成功.注意:如果expression是整个正则表达式的捕获组的名字或编号,则将被解释成为下面的捕获测试.
还是看一下例子:
string input = "qhatta 2hbxxb,3habs";
string p = @"h(?(?=a)att|bxx)";
这里匹配 hatt hbxx,虽然habsa匹配表达式,但它不匹配yes|no选项
如果 p = @"h(?(?=qq)att|bxx)";则只匹配 hbxx.
如果p = @"h(?(?=qq)att)"(省略no部分);则只匹配 h.
如果p = @"h(?(?=a)att)"(省略no部分);则只匹配 hatt h.
G). (?(name)yes|no) . 捕获测试,替换构造之一,和上面的构造相似,区别是name是一个捕获组的名字,如果相应的捕获组有匹配,则匹配”yes”部分,否则匹配”no”部分. 注意:如果name不与此表达式中使用的捕获组的名称或编号对应,则替换构造将解释为表达式测试。
例子:
 string p = @"(?<n>\d+)?h(?(n)att|bxx)";
 string input = "3hatta qhbxxb,3hbs ";这里匹配3hatt,hbxx

2.      $符号
A). $最简单的用法就是:匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 '\n' '\r' 之前的位置.
B). 用在替换模式中。”$数字”${name}”代表捕获编号或捕获名称。
string result = Regex.Replace("0011ggx33a", @"([\w])\1", "$1-$1");
该例是在相同字符间插入一个“-”。结果是:"0-01-1g-gx3-3a".
 

突然发觉把自己的思路和心得用文字表达出来,又想让别人也能看懂,是一件不容易的事。