组的概念
当你获得这样的一个字符串”最后比分是: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(); } |
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(); } |
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(); } |
- (?<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}来获取两个分组构造的匹配内容