一、組的分類
正則中的組有捕獲組和非捕獲組,而捕獲組又分為普通的捕獲組和命名捕獲組,分別為
捕獲組:(exp)
命名捕獲組:(?<name>exp)
非捕獲組:(?:exp)
二、組的作用
1、捕獲組的作用
捕獲組的作用是將正則表達(dá)式exp匹配到的內(nèi)容保存到組里,供以后使用
比如這樣一個字符串:
<a title="床上等你">CSDN</a>
我想得到網(wǎng)址,而它符合的規(guī)則是在<a...>標(biāo)簽內(nèi),那就可以這樣做
- C# code
string test = "<a href=\"http://bbs.csdn.net\" title=\"床上等你\">CSDN</a>";Match m = Regex.Match(test, @"<a\s*href=""([^""]*)""[^>]*>", RegexOptions.IgnoreCase);if (m.Success)MessageBox.Show(m.Groups[1].Value);
上面的正則表達(dá)式匹配到了<a ),效果是一樣的
普通捕獲組是用1,2,3...這樣的自然數(shù)對捕獲組進(jìn)行引用的
而命名捕獲組可以不用去數(shù)捕獲組的序號,直接通過捕獲組的命稱對它進(jìn)行引用
- C# code
string test = "<a href=\"http://bbs.csdn.net\" title=\"床上等你\">CSDN</a>";Match m = Regex.Match(test, @"<a\s*href=""(?<url>[^""]*)""[^>]*>", RegexOptions.IgnoreCase);if (m.Success)MessageBox.Show(m.Groups["url"].Value);
至于捕獲組的分組命名及序號排序規(guī)則,在后面說明
2、非捕獲組的作用
非捕獲組的作用有兩個,第一個比較常用,第二個了解一下即可
(1)、節(jié)省系統(tǒng)資源,提高效率
在使用“|”表示“或”的關(guān)系時,稍微復(fù)雜的情況,需要用()來限制“|”的作用范圍,否則即表示“|”的左右兩側(cè)整體為“或”的關(guān)系,這是題外話,這里不詳細(xì)說明了,還有用{num}來表達(dá)式匹配次數(shù)時,有時前面也要用到()限制作用范圍
而使用()來限制作用范圍的同時,默認(rèn)情況下會把匹配到的結(jié)果保存到一個捕獲組里,而大多數(shù)時候,我們是不需要保存這部分內(nèi)容的,這就帶來一定的副作用,浪費(fèi)了系統(tǒng)資源,降低了效率
非捕獲組的一個作用就是用來消除這種副作用的,(?:exp)用來匹配exp所表示的規(guī)則,但不將匹配結(jié)果保存到捕獲組里
比如匹配HH:mm:ss這樣的時間
- C# code
MessageBox.Show(Regex.IsMatch("18:23:55", "^(?:[01][0-9]|2[0-3])(?::[0-5][0-9]){2}$").ToString());
(?:[01][0-9]|2[0-3])驗證小時部分是否符合規(guī)則,但不會將匹配結(jié)果保存到捕獲組里
(?::[0-5][0-9]){2}驗證了分秒部分,但不會將匹配結(jié)果保存到捕獲組里
(2)、在使用Regex.Split方法時,起到與RegexOptions .ExplicitCapture參數(shù)相同的作用,這個用得不多,了解一下就行了
三、捕獲組分組命名及序號排序
普通捕獲組是按“(”從左到右出現(xiàn)的先后順序以自然數(shù)1,2,3...進(jìn)行命名的
命名捕獲組就是以(?<name>exp)中的name進(jìn)行命名的
但是要注意一點,在表達(dá)式匹配成功的前提下,$0在任何情況下都表示整個表達(dá)式所匹配到的內(nèi)容,m.Groups[0].Value表示整個表達(dá)式匹配到的內(nèi)容,可以簡寫為m.Value
另外就是命名捕獲組除了可以用name對它進(jìn)行引用外,還可以通過序號對它引用,它的命名規(guī)則為:先對普通捕獲組從左到右進(jìn)行序號命名,然后再從開頭,從左到右對命名捕獲組進(jìn)行序號命名,舉例如下
<a\s*href="(?<url>[^"]*)"\s*title="([^"]*)"[^>]*>(?<text>[\s\S]*?)</a>
2 url 1 3 text
- C# code
string test = "<a href=\"http://bbs.csdn.net\" title=\"床上等你\">CSDN</a>";Match m = Regex.Match(test, @"<a\s*href=""(?<url>[^""]*)""\s*title=""([^""]*)""[^>]*>(?<text>[\s\S]*?)</a>", RegexOptions.IgnoreCase);if (m.Success){richTextBox1.Text += m.Groups[0].Value + "\n"; //<a href="http://bbs.csdn.net" title="床上等你">CSDN</a> richTextBox1.Text += m.Groups[1].Value + "\n"; //床上等你 richTextBox1.Text += m.Groups[2].Value + "\n"; //http://bbs.csdn.net richTextBox1.Text += m.Groups["url"].Value + "\n"; //http://bbs.csdn.net richTextBox1.Text += m.Groups[3].Value + "\n"; //CSDN richTextBox1.Text += m.Groups["text"].Value + "\n"; //CSDN}
四、組的另一種引用方式
除了上面 m.Groups[1].Value 和 m.Result("$1") 這兩種對結(jié)果集進(jìn)行處理時的引用方式外,還有在替換時的一種引用方式,舉例如下
只保留網(wǎng)址和鏈接文字,去掉<a...>標(biāo)簽中其它無用信息
- C# code
string test = "<a href=\"http://bbs.csdn.net\" title=\"床上等你\">CSDN</a>";string result = Regex.Replace(test, @"<a\s*href=""([^""]*)""\s*title=""([^""]*)""[^>]*>(?<text>[\s\S]*?)</a>", @"<a href=""$1"">${text}</a>", RegexOptions.IgnoreCase);MessageBox.Show(result);
普通捕獲組就是用$number來引用,而命名捕獲是用${name}來引用
預(yù)搜索
(?=exp)
(?!exp)
(?<=exp)
(?<!exp)
下面的說明很容易讓人頭暈,不看也罷,我將以另一種方式對它們的作用和用法進(jìn)行說明
(?=exp) 匹配exp前面的位置
(? <=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(? <!exp) 匹配前面不是exp的位置
有的資料上翻譯為零寬度斷言,我習(xí)慣于預(yù)搜索這種叫法,前兩個為正向預(yù)搜索,后兩個為反向預(yù)搜索,當(dāng)然還有其它翻譯,其實都是一個意思,知道就行,不必在意
這四種表達(dá)式,它們與非捕獲的相同之處在于,并不將匹配到的結(jié)果保存到捕獲組,不同之處在于,非捕獲組匹配到的內(nèi)容,雖然不保存到捕獲組,但卻是在結(jié)果$0實實在在存在的,而以上四種表達(dá)式所匹配到的內(nèi)容,一般來說,是不存在$0內(nèi)的,所以說它們匹配的結(jié)果是零寬度的
更好的理解方式,是把它們作為附加條件,而不是正則表達(dá)式的組成部分
為了更好的說明,先說一下“縫隙”的概念,“縫隙”是零寬度的,它只是字符串中的一個位置,而不是實際的字符,如字符串“ab”,在“a”前面,“a”和“b”中間,還有“b”后面,分別有一個“縫隙”,也就是整個字符串有三個“縫隙”
(?=exp) 在所在“縫隙”的后面附加一個條件,也就是“縫隙”后面必須能夠匹配exp的內(nèi)容
(?!exp) 在所在“縫隙”的后面附加一個條件,也就是“縫隙”后面必須不能夠匹配exp的內(nèi)容
(?<=exp) 在所在“縫隙”的前面附加一個條件,也就是“縫隙”前面必須能夠匹配exp的內(nèi)容
(?<!exp) 在所在“縫隙”的前面附加一個條件,也就是“縫隙”前面必須不能夠匹配exp的內(nèi)容
舉例說明如下:
<[^>]*>表達(dá)式任意html標(biāo)簽
附加一個條件
<(?!img)[^>]*>
這個就表示除<img...>標(biāo)簽外的所有標(biāo)簽,看下實際例子
- C# code
string test = "<p><a href=\"http://bbs.csdn.net\" title=\"床上等你\"><img src=\"http://bbs.csdn.net\\logo.jpg\">CSDN</a></p>";MatchCollection mc = Regex.Matches(test, @"<(?!img)[^>]*>", RegexOptions .IgnoreCase);foreach(Match m in mc){richTextBox1.Text += m.Value + "\n";}
輸出結(jié)果為:
<p>
<a title="床上等你">
</a>
</p>
這時再看一下<(?!img)[^>]*>這個正則表達(dá)式
(?!img)所在的“縫隙”是“<”和它后面的第一個字符之間的“縫隙”,它表示的意思就是,在這個“縫隙”的后面,不能是img,整個表達(dá)式的意思也就是不匹配<img...>標(biāo)簽
同理,<(?=img)[^>]*>表示只匹配<img...>標(biāo)簽
- C# code
string test = "<p><a href=\"http://bbs.csdn.net\" title=\"床上等你\"><img src=\"http://bbs.csdn.net\\logo.jpg\">CSDN</a></p>";MatchCollection mc = Regex.Matches(test, @"<(?=img)[^>]*>", RegexOptions.IgnoreCase);foreach(Match m in mc){MessageBox.Show(m.Value);}
輸出:
<img src="http://bbs.csdn.net\logo.jpg">
(?<=<a[^>]*>)<img[^>]*>表示只匹配前面為<a...>標(biāo)簽的<img...>標(biāo)簽
- C# code
string test = "<p><a href=\"http://bbs.csdn.net\" title=\"床上等你\"><img src=\"http://bbs.csdn.net\\logo.jpg\">CSDN</a></p><img src=\"http://bbs.csdn.net\\home.jpg\">";MatchCollection mc = Regex.Matches(test, @"(?<=<a[^>]*>)<img[^>]*>", RegexOptions.IgnoreCase);foreach(Match m in mc){MessageBox.Show(m.Value);}
輸出:
<img src="http://bbs.csdn.net\logo.jpg">
(?<!<a[^>]*>)<img[^>]*>表示只匹配前面不為<a...>標(biāo)簽的<img...>標(biāo)簽
- C# code
string test = "<p><a href=\"http://bbs.csdn.net\" title=\"床上等你\"><img src=\"http://bbs.csdn.net\\logo.jpg\">CSDN</a></p><img src=\"http://bbs.csdn.net\\home.jpg\">";MatchCollection mc = Regex.Matches(test, @"(?<!<a[^>]*>)<img[^>]*>", RegexOptions.IgnoreCase);foreach(Match m in mc){MessageBox.Show(m.Value);}
輸出:
<img src="http://bbs.csdn.net\home.jpg">
以上面的這個(?<=<a[^>]*>)<img[^>]*>為例,表達(dá)式中雖然有(?<=<a[^>]*>),但是在結(jié)果m.Value中并不存在它匹配到的內(nèi)容<a >,所以說它是零寬度的,只是作為一個附加條件存在