正则表达式-理解组资料收集

组的概念

 

       当你获得这样的一个字符串最后比分是:19/24”,你肯定希望有一个正则表达式,他不单能够找到形如 data1/data2的字符串,还应该能够直接把data1,data2作为单独的结果传送出来。否则你需要再对形如”19/24”的字符串进行分析,才能够顺利得到双方的比分。显然,正则表达式不会忽略这个问题,所以他增加了组的概念。你可以把一次搜索的结果分别放入不同的组中,并通过组名或者组的所以分别取得这些组的结果。比如上例,我们就可以用@”(/d+)/(/d+)”作为表达式。来看看结果吧:

                            Regex regex = new Regex(@"(/d+)/(/d+)");                                             

                            MatchCollection matches = regex.Matches(@"最后比分是:19/24");        

                            //show matches                                                                         

                            Console.WriteLine("----------------------------------");            

                            foreach(Match m in matches)                                                 

                            {                                                                                  

                                     //Console.WriteLine("match string is: /"{0}/", length: {1}",                                                 // m.Value.ToString(), m.Value.Length);                                 

                                     foreach(string name in regex.GetGroupNames())                      

                                     {                                                                        

                                               Console.WriteLine("/r capture group /"{0}/" value is:/"{1}/""        

                                              , name, m.Groups[name].Value);                                 

                                     }                                                                        

                            }                                                                                  

                            Console.WriteLine("matched count: {0}", matches.Count);                         

 

输出:

----------------------------------

 capture group "0" value is:"19/24"

 capture group "1" value is:"19"

 capture group "2" value is:"24"

matched count: 1

 

现在清楚了吧,Regex对象把匹配的结果放入组0中。同时,匹配的组信息也放入了对应的组中。组的名称在默认的情况下,是从1开始依次增加的整数。0作为保留名称,专门用于指示匹配的整个字符串。既然有默认情况这样的概念,自然也就意味着用户可以自定义组的名称。方法很简单,在组的前面加上:?<name>就可以了。好了,现在把前面的正则表达式修改一下,换成@”(?<score1>/d+)/(?<score1>/d+)”,现在再看看结果:

----------------------------------

 capture group "0" value is:"19/24"

 capture group "score1" value is:"19"

 capture group "score2" value is:"24"

matched count: 1

 

换成自己定义的名字了吧,哈哈!为了在今后的测试中,能够更加方便的看到所有结果,我们对前面介绍过的showMatches()做一点小小的调整。这样,如果在表达式中包含了组的定义,我们也可以在不需要修改任何代码的情况下,直接看到所有的组信息了,调整后的方法showMatchesPro()如下:

         public static void showMatchesPro(string expression, RegexOptions option, string ms)          

         {                                                                                                    

                   Regex regex = new Regex(expression, option);                                             

                   MatchCollection matches = regex.Matches(ms);                                           

                   //show matches                                                                                  

                   Console.WriteLine("----------------------------------");                     

                   Console.WriteLine(" string: /"{0}/"/r/n expression: /"{1}/"/r/n match result is:",             

                  ms, expression);                                                                                

                   foreach(Match m in matches)                                                          

                   {                                                                                                                       foreach(string name in regex.GetGroupNames())                                

                            {                                                                                  

                                     Console.WriteLine("/r capture group /"{0}/" value is:/"{1}/"",                

                                     name, m.Groups[name].Value);                                            

                            }                                                                                  

                   }                                                                                           

                   Console.WriteLine("matched count: {0}", matches.Count);                                   

                   // show group name                                                                 

                  Console.WriteLine("group name count {0}", regex.GetGroupNames().Length);                 

                   foreach(string name in regex.GetGroupNames())                                         

                   {                                                                                           

                            Console.WriteLine("group name :/"{0}/"", name);                                 

                   }                                                                                           

         }                                                                                                    

 

正则表达式中的组是很重要的一个概念,它是我们通向高级正则应用的的桥梁。   组的概念   一个正则表达式匹配结果可以分成多个部分,这就是组(Group)的目的。能够灵活的使用组后,你会发现Regex真是很方便,也很强大。   先举个例子

public static void Main() {  string s = "2005-2-21";  Regex reg = new Regex(@"(?<y>/d{4})-(?<m>/d{1,2})-(?<d>/d{1,2})",RegexOptions.Compiled);  Match match = reg.Match(s);  int year = int.Parse(match.Groups["y"].Value);  int month = int.Parse(match.Groups["m"].Value);  int day = int .Parse(match.Groups["d"].Value);  DateTime time = new DateTime(year,month,day);  Console.WriteLine(time);  Console.ReadLine(); }
以上的例子通过组来实现分析一个字符串,并把其转化为一个DateTime实例,当然,这个功能用DateTime.Parse方法就能很方便的实现。   在这个例子中,我把一次Match结果用(?<name>)的方式分成三个组"y","m","d"分别代表年、月、日。   现在我们已经有了组的概念了,再来看如何分组,很简单的,除了上在的办法,我们可以用一对括号就定义出一个组,比如上例可以改成:
public static void Main() {  string s = "2005-2-21";  Regex reg = new Regex(@"(/d{4})-(/d{1,2})-(/d{1,2})",RegexOptions.Compiled);  Match match = reg.Match(s);  int year = int.Parse(match.Groups[1].Value);  int month = int.Parse(match.Groups[2].Value);  int day = int .Parse(match.Groups[3].Value);  DateTime time = new DateTime(year,month,day);  Console.WriteLine(time);  Console.ReadLine(); }
从上例可以看出,第一个括号对包涵的组被自动编号为1,后面的括号依次编号为2、3……
public static void Main() {  string s = "2005-2-21";  Regex reg = new Regex(@"(?<2>/d{4})-(?<1>/d{1,2})-(?<3>/d{1,2})",RegexOptions.Compiled);  Match match = reg.Match(s);  int year = int.Parse(match.Groups[2].Value);  int month = int.Parse(match.Groups[1].Value);  int day = int .Parse(match.Groups[3].Value);  DateTime time = new DateTime(year,month,day);  Console.WriteLine(time);  Console.ReadLine(); }
再看上例,我们用(?<数字>)的方式手工给每个括号对的组编号,(注意我定义1和2的位置时不是从左到右定义的)   通过以上三例,我们知道了给Regex定义Group的三种办法以及相应的引用组匹配结果的方式。   然后,关于组定义,还有两点请注意:   1、因为括号用于定义组了,所以如果要匹配"("和")",请使用"/("和"/)"(关于所有特殊字符的定义,请查看相关Regex expression帮助文档)。    2、如果定义Regex时,使用了ExplicitCapture选项,则第二个例子不会成功,因为此选项要求显式定义了编号或名字的组才捕获并保存结 果,如果你没有定义ExplicitCapture选项,而有时又定义了类式于(A|B)这样的部分在表达式,而这个(A|B)你又并不想捕获结果,那么 可以使用“不捕获的组”语法,即定义成(?:)的方式,针对于(A|B),你可以这样来定义以达到不捕获并保存它到Group集合中的目的--(?: A|B)。
问题的提出:
< table >   < tr >   < td    id ="td1" >   </ td >   < td    id ="td2" >   < table > < tr > < td > snhame </ td > < td > f </ td > </ tr > </ td >   < td ></ td > </ tr >   </ table >  
以上为部分的HTML代码.现在我们的问题是要提取出其<td id="td2">的<td>标签并将其删除掉 以往我们惯用的方法都是直接去取,像 <td/s*id="td2">[/s/S]+?/</td> 不过问题出来了,我们提取到的不是我们想要的内容,而是
<td   id="td2">  <table> <tr> <td>snhame</td>
原因也很简单,它和离他最近的</td>标签匹配上了,不过它不知道这个标签不是它的-_- 是不是就是?符号的原因呢,我们去掉让他无限制贪婪,可这下问题更大了,什么乱七八糟的东东它都匹配到了
<td   id="td2">  <table> <tr> <td>snhame</td> <td>f</td> </tr> </td>  <td></td>
它还真是贪婪,多吃多喝,也不什么给什么了.看来问题可不想我们想像的那样简单^_^ 在网络上搜索了半天,资料倒是找到了不少,不过都是E文 -_-,谁叫我没有写好,敢打 硬着头皮看呗http://weblogs.asp.net/whaggard/archive/2005/02/20/377025.aspx 哎,能用的翻译工具都用上吧.总算看出点眉目来. .NET正则表达式引擎 注意:这里介绍的平衡组语法是由.Net Framework支持的;其它语言/库不一定支持这种功能,或者支持此功能但需要使用不同的语法。  有时我们需要匹配像( 100 * ( 50 + 15 ) )这样的可嵌套的层次性结构,这时简单地使用/(.+/)则只会匹配到最左边的左括号和最右 边的右括号之间的内容(这里我们讨论的是贪婪模式,懒惰模式也有下面的问题)。假如原来的字符串里的左括号和右括号出现的次数不相等,比如( 5 /  ( 3 + 2 ) ) ),那我们的匹配结果里两者的个数也不会相等。有没有办法在这样的字符串里匹配到最长的,配对的括号之间的内容呢?  为了避免(和/(把你的大脑彻底搞糊涂,我们还是用尖括号代替圆括号吧。现在我们的问题变成了如何把xx <aa <bbb> <bbb> aa> yy这样的字符串里,最长的配对的尖括号内的内容捕获出来?  这里需要用到以下的语法构造:
  • (?<group>)或者(?’group’) - 把捕获的结果内容命名为group,并压入堆栈;
  • (?<-group>)或者(?’-group’)- 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败;
  • (?(group)yes|no)或者(?(group)yes|no)- 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分;
  • (?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败 ;

    上面的表达可以用更简单的表达式让大家理解一下堆栈,堆栈就像往箱子里面装东西,最先装的放在最下面,而最后装的放在最上面,在对堆栈有一定认识以后,这就非常好理解了,(?<group>)就为往箱子里面装一个东东,(?<-group>)就是在箱子里面取一个东东(当然第一次只能取到最上面的),(?(group)yes|no)就是最后再来检查箱子里是否还有东西,如果有则继续匹配yes部分,否则就匹配no的部分. 很简单的道理: 我们需要做的是每碰到了左括号,就在黑板上写一个"group",每碰到一个右括号,就擦掉一个,到了最后就看看黑板上还有没有--如果有那就证明左括号比右括号多,那匹配就应该失败.

下在这个是Jeffrey Friedl在精通正则表达式第二版当中所提供的例子 Jeffrey Friedl provides the following example in his excellent book Mastering Regular Expressions, 2nd Edition.

Balanced Parentheses. (匹配括号)

Dim R As Regex = New Regex(" /(                   " & _
"   (?>                " & _
"       [^()]+         " & _
"     |                " & _
"       /( (?<DEPTH>)  " & _
"     |                " & _
"       /) (?<-DEPTH>) " & _
"   )*                 " & _
"   (?(DEPTH)(?!))     " & _
" /)                   ", _
RegexOptions.IgnorePatternWhitespace);
现在这个正则表达式可以匹配正确的嵌套组但是这种结构不能匹配到像XML标签其嵌套字符多于一个简单字符的嵌套组
Regex re = new Regex(@"^
(?>
/( (?<LEVEL>)   # On opening paren push level
|
/) (?<-LEVEL>)  # On closing paren pop level
|
(?! /( | /) ) . # Match any char except ( or )
)+
(?(LEVEL)(?!))      # If level exists then fail
___FCKpd___2quot;, RegexOptions.IgnorePatternWhitespace);
这个表达式也能匹配正确的嵌套组,但这里最大的不同就是这里用否定查找来确定这个字符不是一个匹配来代替[^()]+

这一类的的匹配.它也只能匹配到一个字符的限制符.

Balanced XML tags.

 
Regex re = new Regex(@"^
(?>
<tag>  (?<LEVEL>)      # On opening <tag> push level
|
</tag> (?<-LEVEL>)     # On closing </tag> pop level
|
(?! <tag> | </tag> ) . # Match any char unless the strings
)+                         # <tag> or </tag> in the lookahead string
(?(LEVEL)(?!))             # If level exists then fail
___FCKpd___3quot;, RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);
 
这个正则表达式可以匹配到正确的xml嵌套的标签<tag>和</tag>.
它只是把匹配括号的表达式中的"("修改为"<tag>"和")"修改为了"</tag>".

General version of Balanced constructs (HTML Anchor tags used in example).

现在这个正则表达式用一个简单的字符串格式符替换掉表达式中弹出和加入的限定符.

这种方式就更加的常用,它可以很简单的就把开始和结束限制符表示在其表达式当中. 这一切在.NET正则表达式当中都显得非常的简单.

Regex re = new Regex(string.Format(@"^
(?>
{0} (?<LEVEL>)      # On opening delimiter push level
|
{1} (?<-LEVEL>)     # On closing delimiter pop level
|
(?! {0} | {1} ) .   # Match any char unless the opening
)+                      # or closing delimiters are in the lookahead string
(?(LEVEL)(?!))          # If level exists then fail
___FCKpd___8quot;, "<a[^>]*>", "</a>"),
RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);






 

Retrieving data between delimiters where there are possible nested delimiters

有一个比较通常的应用就是在两限制符之间的字符中包含里限制符标签(这也就是我们上面所提到的问题) If there were no nested tags then this regular expression would be rather simple but since there are one essentially needs to wrap the expression from above with the set of outer tags and then capture the inner text. 

Regex re = new Regex(string.Format(@"^
{0}                       # Match first opeing delimiter
(?<inner>
(?>
{0} (?<LEVEL>)      # On opening delimiter push level
|
{1} (?<-LEVEL>)     # On closing delimiter pop level
|
(?! {0} | {1} ) .   # Match any char unless the opening
)+                      # or closing delimiters are in the lookahead string
(?(LEVEL)(?!))          # If level exists then fail
)
{1}                       # Match last closing delimiter
___FCKpd___10quot;, "<quote>", "</quote>"),
RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);

re.Match("<quote>inner text</quote>").Groups["inner"].Value == "inner text"
re.Match("<quote>a<quote>b</quote>c</quote>").Groups["inner"].Value == "a<quote>b</quote>c"

 

Matching multiple balanced constructs(匹配多平衡组结构)

这个例子主要是为了显示如何匹配多种适当的平衡标签与一个单一平衡标签的表达式. 不过,经过表达式的创造和测试,它出现了一个有趣的问题,举例来说,以确保( )和[ ]得到正确嵌套单独实现容易如上所示, 而是要确保他们获得适当嵌套连同是不可能的. net的正则表达式为了更好地了解该问题考虑下列不正当嵌套实例( [ ) ]或[(()]). 他们是单独嵌套表达式,但不当的嵌套时,考虑到它们放在一起.下面一个表达式认识到这一点:

Regex re = new Regex(@"^
(?>
(?<LEVEL> /()                 # On opening paren capture ( on stack
|
(?(/k<LEVEL>=/()              # Make sure the top of stack is (
(?<-LEVEL> /) ))              # On closing paren pop ( off stack
|
(?<LEVEL> /[ )                # On opening bracket capture [ on stack
|
(?(/k<LEVEL>=/])              # Make sure the top of stack is [
(?<-LEVEL> /] ))              # On closing bracket pop [ off stack
|
(?! /( | /) | /[ | /] ) .     # Match any char except (, ), [ or ]
)+
(?(LEVEL)(?!))                    # If level exists then fail
___FCKpd___11quot;, RegexOptions.IgnorePatternWhitespace);

这个正则表达式不能正常运行它只是作为一个示例来说明.

上面说了这么多,想必大家出都看出这个匹配多平衡组的原理了,其实质上也只是一个堆栈的原理.首先把左操作符压入栈. 在遇上右操作符的时候并不是马上把左操作符弹出,而是多了一个判定,判断其栈的最顶元素是否就是当前匹配到的右操作符 相对应的左操作符,如果是,则弹出,相反失败. 说了这么多,现在来解决问题了,很明显我们应该采用最后一种结构,匹配多平衡组.(由于时间的原因,这组答案没能完成) 采用前面匹配括号的方法来实现

< td. *? id = " td2 " [ ^> ] *> (( ?>< td[ ^> ] *> ( ?< o > ) |</ td > ( ?<- o > ) | [/s/S]) * )( ? (o)( ?! )) </ td >

做的不怎么满意,还需继续学习. 分组构造 (                  捕获匹配的子字符串(或非捕获组;有关详细信息,请参见正则表达式选项中的 ExplicitCapture 选项)。使用 () 的捕获根据左括号的顺 序从 1 开始自动编号。捕获元素编号为零的第一个捕获是由整个正则表达式模式匹配的文本。  (?<name>          将匹配的子字符串捕获到一个组名称或编号名称中。用于 name 的字符串不能包含任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号,例如 (?'name')。  (?<name1-name2>    平衡组定义。删除先前定义的 name2 组的定义并在 name1 组中存储先前定义的 name2 组和当前组之间的间隔。如果未定义 name2  组,则匹配将回溯。由于删除 name2 的最后一个定义会显示 name2 的先前定义,因此该构造允许将 name2 组的捕获堆栈用作计数器以跟踪 嵌套构造(如括号)。在此构造中,name1 是可选的。可以使用单引号替代尖括号,例如 (?'name1-name2')。  (?:               非捕获组。  (?imnsx-imnsx:    应用或禁用子表达式中指定的选项。例如,(?i-s:  将打开不区分大小写并禁用单行模式。有关详细信息,请参见正则表达式选项。  (?=               零宽度正预测先行断言。仅当子表达式在此位置的右侧匹配时才继续匹配。例如,/w+(?=/d)             与后跟数字的单词匹配,而不与该数字匹配。此构造不会回溯。  (?!               零宽度负预测先行断言。仅当子表达式不在此位置的右侧匹配时才继续匹配。例如,/b(?!un)/w+/b 与不以 un 开头的单词匹配。  (?<=              零宽度正回顾后发断言。仅当子表达式在此位置的左侧匹配时才继续匹配。例如,(?<=19)99 与跟在 19 后面的 99 的实例匹配。此构造不会回溯。  (?<!              零宽度负回顾后发断言。仅当子表达式不在此位置的左侧匹配时才继续匹配。  (?>               非回溯子表达式(也称为“贪婪”子表达式)。该子表达式仅完全匹配一次,然后就不会逐段参与回溯了。(也就是说,该子表达式仅与可由该子表达式单独匹配的字符串匹配。)  构造: 最基本的构造方式就是(),在左右括号中括起来的部分,就是一个分; 更进一步的分就是形如:(?<name> )的分方式,这种方式与第一种方式的不同点,就是对分的部分进行了命名,这样就可以通过该的命名来获取信息; (还有形如(?= )等等的分构造,我们这篇的例子中也没有使用到,下次我们在来介绍) 替换: 上面提到了两种基本的构造分方式()以及(?<name> ),通过这两种分方式,我们可以得到形如$1,${name}的匹配结果。 这样说,可能概念上还是有些模糊,我们还是结合上面的例子来说: 第三个例子的正则表达式为://b(?<month>//d{1,2})/(?<day>//d{1,2})/(?<year>//d{2,4})//b (解释一下,为什么这里都是//一起用:这里是C#的例子,在C#语言中/是转译字符,要想字符串中的/不转译,就需要使用//或者在整个字符串的开始加上@标记,即上面等价与 @”/b(?<month>/d{1,2})/(?<day>/d{1,2})/(?<year>/d{2,4}/b”) /b -- 是一种特殊情况。在正则表达式中,除了在 [] 字符类中表示退格符以外,/b 表示字边界(在 /w 和 /W 字符之间)。在替换模式中,/b 始终表示退格符 (?<month>/d{1,2}) – 构造一个名为month的分,这个分匹配一个长度为1-2的数字 / -- 匹配普通的/字符 (?<day>/d{1,2}) --构造一个名为day的分,这个分匹配一个长度为1-2的数字 / -- 匹配普通的/字符 (?<year>/d{2,4}/b”) --构造一个名为year的分,这个分匹配一个长度为2-4的数字 这里还不能够看出这些分的作用,我们接着看这一句 ${day}-${month}-${year} ${day} – 获得上面构造的名为day的分匹配后的信息 - -- 普通的-字符 ${month} --获得上面构造的名为month的分匹配后的信息 - -- 普通的-字符 ${year} --获得上面构造的名为year的分匹配后的信息 举例来说: 将形如04/02/2003的日期使用例3种的方法替换 (?<month>/d{1,2}) 分将匹配到04由${month}得到这个匹配值 (?<day>/d{1,2}) 分将匹配到02由${day}得到这个匹配值 (?<year>/d{1,2}) 分将匹配到2003由${year}得到这个匹配值 了解了这个例子后,我们在来看第4个例子就很简单了。 第4个例子的正则 ^(?<proto>/w+)://[^/]+?(?<port>:/d+)?/ ^ -- 表示限定匹配开始于字符串的开始 (?<proto>/w+) – 构造一个名为proto的分,匹配一个或多个字母 : -- 普通的:字符 // -- 匹配两个/字符 [^/] – 表示这里不允许是/字符 +? – 表示指定尽可能少地使用重复但至少使用一次匹配 (?<port>:/d+) – 构造一个名为port的分,匹配形如:2134(冒号+一个或多个数字) ? – 表示匹配字符出现0次或1次 / -- 匹配/字符 最后通过${proto}${port}来获取两个分构造的匹配内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值