.NET正則基礎(chǔ)之平衡組
1、概述
平衡組是微軟在.NET中提出的一個(gè)概念,主要是結(jié)合幾種正則語(yǔ)法規(guī)則,提供對(duì)配對(duì)出現(xiàn)的嵌套結(jié)構(gòu)的匹配。.NET是目前對(duì)正則支持最完備、功能最強(qiáng)大的語(yǔ)言平臺(tái)之一,而平衡組正是其強(qiáng)大功能的外在表現(xiàn),也是比較實(shí)用的文本處理功能,目前只有.NET支持,相信后續(xù)其它語(yǔ)言會(huì)提供支持。
平衡組可以有狹義和廣義兩種定義,狹義平衡組指.NET中定義的(?<Close-Open>Expression)語(yǔ)法,廣義平衡組并不是固定的語(yǔ)法規(guī)則,而是幾種語(yǔ)法規(guī)則的綜合運(yùn)用,我們平時(shí)所說(shuō)的平衡組通常指的是廣義平衡組。本文中如無(wú)特殊說(shuō)明,平衡組這種簡(jiǎn)寫指的是廣義平衡組。
正是由于平衡組功能的強(qiáng)大,所以帶來(lái)了一些神秘色彩,其實(shí)平衡組并不難掌握。下面就平衡組的匹配原理、應(yīng)用場(chǎng)景以及性能調(diào)優(yōu)展開討論。
2、平衡組匹配原理
2.1 預(yù)備知識(shí)
平衡組通常是由量詞,分支結(jié)構(gòu),命名捕獲組,狹義平衡組,條件判斷結(jié)構(gòu)組成的,量詞和分支結(jié)構(gòu)這里不做介紹,這里只對(duì)命名捕獲組,狹義平衡組和條件判斷結(jié)構(gòu)做下說(shuō)明。
2.1.1命名捕獲組
語(yǔ)法:(?<name>Expression)
(?’name’Expression)
以上兩種寫法在.NET中是等價(jià)的,都是將“Expression”子表達(dá)式匹配到的內(nèi)容,保存到以“name”命名的組里,以供后續(xù)引用。
對(duì)于命名捕獲組的應(yīng)用,這里不做重點(diǎn)介紹,只是需要澄清一點(diǎn),平時(shí)使用捕獲組時(shí),一般反向引用或Group對(duì)象使用得比較多,可能會(huì)有一種誤解,那就是捕獲組只保留一個(gè)匹配結(jié)果,即使一個(gè)捕獲組可以先后匹配多個(gè)子串,也只保留最后一個(gè)匹配到的子串。但事實(shí)是這樣嗎?
舉例來(lái)說(shuō):
源字符串:abcdefghijkl
正則表達(dá)式:(?<chars>[a-z]{2})+
命名捕獲組chars最終捕獲的是什么?
string test = "abcdefghijkl"; Regex reg = new Regex(@"(?<chars>[a-z]{2})+"); Match m = reg.Match(test); if (m.Success) { ? ? ? richTextBox2.Text += "匹配結(jié)果:" + m.Value + "\n"; ? ? ? richTextBox2.Text += "Group:" + m.Groups["chars"].Value + "\n"; } /*--------輸出-------- 匹配結(jié)果:abcdefghijkl Group:kl */
從m.Groups["chars"].Value的輸出上看,似乎確實(shí)是只保留了一個(gè)匹配內(nèi)容,但卻忽略了一個(gè)事實(shí),Group實(shí)際上是Capture的一個(gè)集合
string test = "abcdefghijkl"; Regex reg = new Regex(@"(?<chars>[a-z]{2})+"); Match m = reg.Match(test); if (m.Success) { ? ? ?richTextBox2.Text += "匹配結(jié)果:" + m.Value + "\n"; ? ? ?richTextBox2.Text += "Group:" + m.Groups["chars"].Value + "\n--------------\n"; ? ? ?foreach (Capture c in m.Groups["chars"].Captures) ? ? ?{ ? ? ? ? ? ?richTextBox2.Text += "Capture:" + c + "\n"; ? ? ?} } /*--------輸出-------- 匹配結(jié)果:abcdefghijkl Group:kl -------------- Capture:ab Capture:cd Capture:ef Capture:gh Capture:ij Capture:kl */
平時(shí)應(yīng)用時(shí)可能會(huì)忽略這一點(diǎn),因?yàn)楹苌儆龅揭粋€(gè)捕獲組先后匹配多個(gè)子串的情況,而在一個(gè)捕獲組只匹配一個(gè)子串時(shí),Group集合中就只有一個(gè)Capture元素,所以內(nèi)容是一樣的。
string test = "abcdefghijkl"; Regex reg = new Regex(@"(?<chars>[a-z]{2})"); Match m = reg.Match(test); if (m.Success) { ? ? ?richTextBox2.Text += "匹配結(jié)果:" + m.Value + "\n"; ? ? ?richTextBox2.Text += "Group:" + m.Groups["chars"].Value + "\n--------------\n"; ? ? ?foreach (Capture c in m.Groups["chars"].Captures) ? ? ?{ ? ? ? ? ? richTextBox2.Text += "Capture:" + c + "\n"; ? ? ?} } /*--------輸出-------- 匹配結(jié)果:ab Group:ab -------------- Capture:ab */
捕獲組保存的是一個(gè)集合,而不只是一個(gè)元素,這一知識(shí)點(diǎn)對(duì)于理解平衡組的匹配原理是有幫助的。
2.1.2狹義平衡組
語(yǔ)法:(?<Close-Open>Expression)
其中“Close”是命名捕獲組的組名,也就是“(?<name>Expression)”中的“name”,可以省略,通常應(yīng)用時(shí)并不關(guān)注,所以一般都是省略的,寫作“(?<-Open>Expression)”。作用就是當(dāng)此處的“Expression”子表達(dá)式匹配成功時(shí),則將最近匹配成功到的命名為“Open”組出棧,如果此前不存在匹配成功的“Open”組,那么就報(bào)告“(?<-Open>Expression)”匹配失敗,整個(gè)表達(dá)式在這一位置也是匹配失敗的。
2.1.3條件判斷結(jié)構(gòu)
語(yǔ)法:(?(Expression)yes|no)
(?(name)yes|no)
對(duì)于“(?(Expression)yes|no)”,它是“(?(?=Expression)yes|no)”的簡(jiǎn)寫形式,相當(dāng)于三元運(yùn)算符
(?=Expression) ? yes : no
表示如果子表達(dá)式“(?=Expression)”匹配成功,則匹配“yes”子表達(dá)式,否則匹配“no”子表達(dá)式。如果“Expression”與可能出現(xiàn)的命名捕獲組的組名相同,為避免混淆,可以采用“(?(?=Expression)yes|no)”方式顯示聲明“Expression”為子表達(dá)式,而不是捕獲組名。
“(?=Expression)”驗(yàn)證當(dāng)前位置右側(cè)是否能夠匹配“Expression”,屬于順序環(huán)視結(jié)構(gòu),是零寬度的,所以它只參與判斷,即使匹配成功,也不會(huì)占有字符。
舉例來(lái)說(shuō):
源字符串:abc
正則表達(dá)式:(?(?=a)\w{2}|\w)
當(dāng)前位置右側(cè)如果是字符“a” ,則匹配兩個(gè)“\w”,否則匹配一個(gè)“\w”。
string test = "abc"; Regex reg = new Regex(@"(?(?=a)\w{2}|\w)"); MatchCollection mc = reg.Matches(test); foreach(Match m in mc) { ? ? ?richTextBox2.Text += m.Value + "\n"; } /*--------輸出-------- ab c */
對(duì)于“(?(name)yes|no)”,如果命名捕獲組“name”有捕獲,則匹配“yes”子表達(dá)式,否則匹配“no”子表達(dá)式。這一語(yǔ)法最典型的一種應(yīng)用是平衡組。
當(dāng)然,以上兩種語(yǔ)法中,“yes”和“no都是可以省略的,但同一時(shí)間只能省略一個(gè),不能一起省略。平衡組的應(yīng)用中就是省略了“no”子表達(dá)式。
2.2平衡組的匹配原理
平衡組的匹配原理可以用堆棧來(lái)解釋,先舉個(gè)例子,再根據(jù)例子進(jìn)行解釋。
源字符串:a+(b*(c+d))/e+f-(g/(h-i))*j
正則表達(dá)式:)|[^()])*(?(Open)(?!))\)
需求說(shuō)明:匹配成對(duì)出現(xiàn)的()中的內(nèi)容
string test = "a+(b*(c+d))/e+f-(g/(h-i))*j"; Regex reg = new Regex(@")|[^()])*(?(Open)(?!))\)"); MatchCollection mc = reg.Matches(test); foreach (Match m in mc) { ? ? ?richTextBox2.Text += m.Value + "\n"; } /*--------輸出-------- (b*(c+d)) (g/(h-i)) */
下面來(lái)考察一下這個(gè)正則,為了閱讀方便,寫成寬松模式。
Regex reg = new Regex(@"\( ? ? ? ? ? ? ? ? ? ? ? ? ?#普通字符“(” ? ? ? ? ? ? ? ? ? ? ? ? ? ? ( ? ? ? ? ? ? ? ? ? ? ? #分組構(gòu)造,用來(lái)限定量詞“*”修飾范圍 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?<Open>\() ? ? ? ? #命名捕獲組,遇到開括弧'Open'計(jì)數(shù)加1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?<-Open>\)) ? ? ? ?#狹義平衡組,遇到閉括弧'Open'計(jì)數(shù)減1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [^()]+ ? ? ? ? ? ? ?#非括弧的其它任意字符 ? ? ? ? ? ? ? ? ? ? ? ? ? ? )* ? ? ? ? ? ? ? ? ? ? ?#以上子串出現(xiàn)0次或任意多次 ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?(Open)(?!)) ? ? ? ? ? #判斷是否還有'Open',有則說(shuō)明不配對(duì),什么都不匹配 ? ? ? ? ? ? ? ? ? ? ? ? \) ? ? ? ? ? ? ? ? ? ? ? ? ?#普通閉括弧 ? ? ? ? ? ? ? ? ? ? ?", RegexOptions.IgnorePatternWhitespace);
對(duì)于一個(gè)嵌套結(jié)構(gòu)而言,開始和結(jié)束標(biāo)記都是確定的,對(duì)于本例開始為“(”,結(jié)束為“)”,那么接下來(lái)就是考察中間的結(jié)構(gòu),中間的字符可以劃分為三類,一類是“(”,一類是“)”,其余的就是除這兩個(gè)字符以外的任意字符。
那么平衡組的匹配原理就是這樣的:
1.先找到第一個(gè)“(”,作為匹配的開始
2.在第1步以后,每匹配到一個(gè)“(”,就入棧一個(gè)Open捕獲組,計(jì)數(shù)加1
3.在第1步以后,每匹配到一個(gè)“)”,就出棧最近入棧的Open捕獲組,計(jì)數(shù)減1
4.后面的(?(Open)(?!))用來(lái)保證堆棧中Open捕獲組計(jì)數(shù)是否為0,也就是“(”和“)”是配對(duì)出現(xiàn)的
5.最后的“)”,作為匹配的結(jié)束
匹配過(guò)程(以下匹配過(guò)程,如果覺(jué)得難以理解,可以暫時(shí)跳過(guò),先學(xué)會(huì)如何使用,再研究為什么可以這樣用吧)
首先匹配第一個(gè)“(”,然后一直匹配,直到出現(xiàn)以下兩種情況之一:
a)堆棧中Open計(jì)數(shù)已為0,此時(shí)再遇到“)”
b)匹配到字符串結(jié)束符
這時(shí)控制權(quán)交給(?(Open)(?!)),判斷Open是否有匹配,由于此時(shí)計(jì)數(shù)為0,沒(méi)有匹配,那么就匹配“no”分支,由于這個(gè)條件判斷結(jié)構(gòu)中沒(méi)有“no”分支,所以什么都不做,把控制權(quán)交給接下來(lái)的“\)”
如果上面遇到的是情況a),那么此時(shí)“\)”可以匹配接下來(lái)的“\)”,匹配成功;如果上面遇到的是情況b),那么此時(shí)會(huì)進(jìn)行回溯,直到“\)”匹配成功為止,否則報(bào)告整個(gè)表達(dá)式匹配失敗。
由于.NET中的狹義平衡組“(?<Close-Open>Expression)”結(jié)構(gòu),可以動(dòng)態(tài)的對(duì)堆棧中捕獲組進(jìn)行計(jì)數(shù),匹配到一個(gè)開始標(biāo)記,入棧,計(jì)數(shù)加1,匹配到一個(gè)結(jié)束標(biāo)記,出棧,計(jì)數(shù)減1,最后再判斷堆棧中是否還有Open,有則說(shuō)明開始和結(jié)束標(biāo)記不配對(duì)出現(xiàn),不匹配,進(jìn)行回溯或報(bào)告匹配失??;如果沒(méi)有,則說(shuō)明開始和結(jié)束標(biāo)記配對(duì)出現(xiàn),繼續(xù)進(jìn)行后面子表達(dá)式的匹配。
需要對(duì)“(?!)”進(jìn)行一下說(shuō)明,它屬于順序否定環(huán)視,完整的語(yǔ)法是“(?!Expression)”。由于這里的“Expression”不存在,表示這里不是一個(gè)位置,所以試圖嘗試匹配總是失敗的,作用就是在Open不配對(duì)出現(xiàn)時(shí),報(bào)告匹配失敗。
3、平衡組的應(yīng)用及優(yōu)化
平衡組提供了嵌套結(jié)構(gòu)的匹配功能,這一創(chuàng)新是很讓人興奮的,因?yàn)榇饲罢齽t對(duì)于嵌套結(jié)構(gòu)的匹配是無(wú)能為力的。然而功能的強(qiáng)大,自然也帶來(lái)了實(shí)現(xiàn)的復(fù)雜,正則書寫得不好,可能會(huì)存在效率陷阱,甚至導(dǎo)致程序崩潰,這里介紹一些基本的優(yōu)化方法。
3.1單字符嵌套結(jié)構(gòu)平衡組優(yōu)化
單字符的嵌套結(jié)構(gòu)指的是開始和結(jié)束標(biāo)記都單個(gè)字符的嵌套結(jié)構(gòu),這種嵌套相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,優(yōu)化起來(lái)也比較容易。先從上面提到的例子開始。
3.1.1貪婪與非貪婪模式
上面給的例子是一種做了部分優(yōu)化的常規(guī)寫法,算作是版本1吧,它做了哪些優(yōu)化呢,先來(lái)看下完全沒(méi)有做過(guò)優(yōu)化的版本0吧。
string test = "a+(b*(c+d))/e+f-(g/(h-i))*j"; Regex reg0 = new Regex(@"\( ? ? ? ? ? ? ? ? ? ? ? ? ?#普通字符“(” ? ? ? ? ? ? ? ? ? ? ? ? ? ? ( ? ? ? ? ? ? ? ? ? ? ? #分組構(gòu)造,用來(lái)限定量詞“*”修飾范圍 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?<Open>\() ? ? ? ? #命名捕獲組,遇到開括弧Open計(jì)數(shù)加1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?<-Open>\)) ? ? ? ?#狹義平衡組,遇到閉括弧Open計(jì)數(shù)減1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? . ? ? ? ? ? ? ? ? ? #任意字符 ? ? ? ? ? ? ? ? ? ? ? ? ? ? )*? ? ? ? ? ? ? ? ? ? ? #以上子串出現(xiàn)0次或任意多次,非貪婪模式 ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?(Open)(?!)) ? ? ? ? ? #判斷是否還有'OPEN',有則說(shuō)明不配對(duì),什么都不匹配 ? ? ? ? ? ? ? ? ? ? ? ? \) ? ? ? ? ? ? ? ? ? ? ? ? ?#普通閉括弧 ? ? ? ? ? ? ? ? ? ? ? ?", RegexOptions.IgnorePatternWhitespace); MatchCollection mc = reg0.Matches(test); foreach (Match m in mc) { ? ? ?richTextBox2.Text += m.Value + "\n"; } /*--------輸出-------- (b*(c+d)) (g/(h-i)) */
接下來(lái)對(duì)比一下版本1。
Regex reg1 = new Regex(@"\( ? ? ? ? ? ? ? ? ? ? ? ? ?#普通字符“(” ? ? ? ? ? ? ? ? ? ? ? ? ? ? ( ? ? ? ? ? ? ? ? ? ? ? #分組構(gòu)造,用來(lái)限定量詞“*”修飾范圍 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?<Open>\() ? ? ? ? #命名捕獲組,遇到開括弧'Open'計(jì)數(shù)加1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?<-Open>\)) ? ? ? ?#狹義平衡組,遇到閉括弧'Open'計(jì)數(shù)減1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [^()]+ ? ? ? ? ? ? ?#非括弧的其它任意字符 ? ? ? ? ? ? ? ? ? ? ? ? ? ? )* ? ? ? ? ? ? ? ? ? ? ?#以上子串出現(xiàn)0次或任意多次 ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?(Open)(?!)) ? ? ? ? ? #判斷是否還有'Open',有則說(shuō)明不配對(duì),什么都不匹配 ? ? ? ? ? ? ? ? ? ? ? ? \) ? ? ? ? ? ? ? ? ? ? ? ? ?#普通閉括弧 ? ? ? ? ? ? ? ? ? ? ?", RegexOptions.IgnorePatternWhitespace);
看到區(qū)別了嗎?版本1對(duì)版本0的改進(jìn)主要有兩個(gè)地方,一個(gè)是用“[^()]+”來(lái)代替“.”,另一個(gè)是用“*”來(lái)代替“*?”,也就是用貪婪模式來(lái)代替非貪婪模式。
如果使用了小數(shù)點(diǎn)“.”,那么為什么不能在分組內(nèi)使用“.+”,后面又為什么不能用“*”呢?只要在上面的正則中使用并運(yùn)行一下代碼就可以知道了,匹配的結(jié)果是
(b*(c+d))/e+f-(g/(h-i))
而不是
(b*(c+d))
(g/(h-i))
因?yàn)闊o(wú)論是分組內(nèi)使用“.+”還是后面使用“*”,都是貪婪模式,所以小數(shù)點(diǎn)會(huì)一直匹配下去,直到匹配到字符串的結(jié)束符才會(huì)停止,然后進(jìn)行回溯匹配。為了取得正確結(jié)果,必須使用非貪婪模式“*?”。
這就類似于用“”去匹配“(abc)def(ghi)”一樣,得到的結(jié)果是“(abc)def(ghi)”,而不是通常我們希望的“(abc)”和“(ghi)”。這時(shí)要用非貪婪模式“”來(lái)得到正確的結(jié)果。
貪婪模式和非貪婪模式在匹配失敗時(shí),回溯的次數(shù)基本上是一樣的,效率上沒(méi)有多大區(qū)別,但是在匹配成功時(shí),貪婪模式比非貪婪模式回溯的次數(shù)要少得多,效率要高得多。
對(duì)于“”如果既要得到正確的匹配結(jié)果,又要提高匹配效率,可以使用排除型捕獲組+貪婪模式的方式,即“”。
版本0的平衡組也是一樣,可以使用排除字符組“[^()]+”和貪婪模式“*”結(jié)合的方式,提高匹配效率,得到的就是版本1的平衡組。
相對(duì)于版本0,或許你會(huì)認(rèn)為版本1的寫法是很自然的,但是如果不了解這樣一個(gè)演進(jìn)過(guò)程,那么在字符序列嵌套結(jié)構(gòu)平衡組優(yōu)化時(shí),就不會(huì)是那么自然的一件事了。
3.1.2 分支結(jié)構(gòu)
接下來(lái)就是分支結(jié)構(gòu)的優(yōu)化。
語(yǔ)法:(Exp1|Exp2|Exp3)
因?yàn)榉种ЫY(jié)構(gòu)的匹配規(guī)則是,從左向右嘗試匹配,當(dāng)左側(cè)分支匹配成功時(shí),就不再向右嘗試。所以使用分支結(jié)構(gòu)時(shí),可以根據(jù)以下兩條規(guī)則進(jìn)行優(yōu)化:
1.盡量抽象出每個(gè)分支中的公共的部分,使最后的表達(dá)式中,每個(gè)分支共公部分盡可能的少,比如(this|that)的匹配效率是沒(méi)有th(is|at)高的。
2. 在不影響匹配結(jié)果的情況下,把出現(xiàn)概率高的分支放在左側(cè),出現(xiàn)概率低的分支放右側(cè)。
對(duì)于本例中的分支結(jié)構(gòu),已經(jīng)沒(méi)有公共部分,符合第一條規(guī)則,再看下第二條規(guī)則,開始標(biāo)記“(”和結(jié)束標(biāo)記“)”出現(xiàn)的概率基本上是一樣的,而除“(”和“)”之外的字符出現(xiàn)的概率是比“(”和“)”出現(xiàn)的概率高的,所以應(yīng)該把“[^()]+”分支放在左側(cè)。
版本1由于采用了排除型捕獲組,所以這三個(gè)分支沒(méi)有包含關(guān)系,左右順序?qū)Y(jié)果不會(huì)造成影響,可以調(diào)整順序。因?yàn)檫@是已經(jīng)經(jīng)過(guò)優(yōu)化的了,而如果是版本0,由“.”對(duì)“(”和“)”有包含關(guān)系,就不能調(diào)整順序了。
在版本1基礎(chǔ)上對(duì)分支結(jié)構(gòu)進(jìn)行優(yōu)化后,就得到版本2。
string test = "a+(b*(c+d))/e+f-(g/(h-i))*j"; Regex reg2 = new Regex(@"\( ? ? ? ? ? ? ? ? ? ? ? ? ?#普通字符“(” ? ? ? ? ? ? ? ? ? ? ? ? ? ? ( ? ? ? ? ? ? ? ? ? ? ? #分組構(gòu)造,用來(lái)限定量詞“*”修飾范圍 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [^()]+ ? ? ? ? ? ? ?#非括弧的其它任意字符 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?<Open>\() ? ? ? ? #命名捕獲組,遇到開括弧Open計(jì)數(shù)加1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?<-Open>\)) ? ? ? ?#狹義平衡組,遇到閉括弧Open計(jì)數(shù)減1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? )* ? ? ? ? ? ? ? ? ? ? ?#以上子串出現(xiàn)0次或任意多次 ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?(Open)(?!)) ? ? ? ? ? #判斷是否還有'OPEN',有則說(shuō)明不配對(duì),什么都不匹配 ? ? ? ? ? ? ? ? ? ? ? ? \) ? ? ? ? ? ? ? ? ? ? ? ? ?#普通閉括弧 ? ? ? ? ? ? ? ? ? ? ? ?", RegexOptions.IgnorePatternWhitespace); MatchCollection mc = reg2.Matches(test); foreach (Match m in mc) { ? ? ?richTextBox2.Text += m.Value + "\n"; } /*--------輸出-------- (b*(c+d)) (g/(h-i)) */
3.1.3 捕獲組
這里面主要涉及到了兩個(gè)捕獲組“(?<Open>\()”和“(?<-Open>\))”,而在平衡組的應(yīng)用中,我是只關(guān)心它是否匹配了,而對(duì)于匹配到的內(nèi)容是不關(guān)心的。對(duì)于這樣一種需求,可以用以下方式實(shí)現(xiàn)
\( (?<Open>)
\)(?<-Open>)
“(?<Open>)”和“(?<-Open>)”這兩種方式只是使用了命名捕獲組,捕獲的是一個(gè)位置,它總是能夠匹配成功的,而匹配的內(nèi)容是空的,分配的內(nèi)存空間是固定的,可以有效的節(jié)省資源,這在單字符嵌套結(jié)構(gòu)中并不明顯,但是在字符序列嵌套結(jié)構(gòu)中就比較明顯了。
由于捕獲組是直接跟在開始或結(jié)束標(biāo)記之后的,所以只要開始或結(jié)束標(biāo)記匹配成功,命名捕獲組自然就會(huì)匹配成功,對(duì)于功能是沒(méi)有任何影響的。
那么把標(biāo)記和捕獲組調(diào)整一下順序是否可以呢?從功能上來(lái)講,是可以的,但是匹配的流程上會(huì)有所不同,先是捕獲組匹配成功,入棧,然后再匹配標(biāo)記,成功則繼續(xù)匹配,不成功則該分支匹配失敗,進(jìn)行回溯,出棧,繼續(xù)嘗試下一分支。這樣將增加許多入棧和出棧的操作,對(duì)匹配效率是有影響的,所以這種方式并不可取。
在版本2基礎(chǔ)上對(duì)捕獲組進(jìn)行優(yōu)化后,就得到版本3。
string test = "a+(b*(c+d))/e+f-(g/(h-i))*j"; Regex reg3 = new Regex(@"\( ? ? ? ? ? ? ? ? ? ? ? ? ?#普通字符“(” ? ? ? ? ? ? ? ? ? ? ? ? ? ? ( ? ? ? ? ? ? ? ? ? ? ? #分組構(gòu)造,用來(lái)限定量詞“*”修飾范圍 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [^()]+ ? ? ? ? ? ? ?#非括弧的其它任意字符 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? \( ?(?<Open>) ? ? ? #命名捕獲組,遇到開括弧Open計(jì)數(shù)加1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? \) ?(?<-Open>) ? ? ?#狹義平衡組,遇到閉括弧Open計(jì)數(shù)減1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? )* ? ? ? ? ? ? ? ? ? ? ?#以上子串出現(xiàn)0次或任意多次 ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?(Open)(?!)) ? ? ? ? ? #判斷是否還有'OPEN',有則說(shuō)明不配對(duì),什么都不匹配 ? ? ? ? ? ? ? ? ? ? ? ? \) ? ? ? ? ? ? ? ? ? ? ? ? ?#普通閉括弧 ? ? ? ? ? ? ? ? ? ? ? ?", RegexOptions.IgnorePatternWhitespace); MatchCollection mc = reg3.Matches(test); foreach (Match m in mc) { ? ? ?richTextBox2.Text += m.Value + "\n"; } /*--------輸出-------- (b*(c+d)) (g/(h-i)) */
3.1.4 固化分組
看到有些人使用平衡組時(shí)用到了固化分組,但并不是所有人都明白固化分組的作用。
語(yǔ)法:(?>Expression)
用“”去匹配“(abc)”是可以匹配成功的,因?yàn)椴挥没厮?,相?duì)于“”這種非貪婪模式,效率上有所提升,但是對(duì)于匹配失敗的情況又如何呢?
源字符串:(abc
正則表達(dá)式:
匹配中間過(guò)程這里不再詳述,可以參考NFA引擎匹配原理。
當(dāng)“[^()]+”匹配到結(jié)束位置時(shí),控制權(quán)交給“\)”,匹配失敗,進(jìn)行回溯,而由于前面使用了“[^()]+”這種排除型字符組,所以可供回溯的位置,不會(huì)存在可以匹配“\)”的情況,這時(shí)候的回溯是完全沒(méi)有意義的,只會(huì)浪費(fèi)時(shí)間,但是由于傳統(tǒng)NFA引擎的特點(diǎn),必須回溯所有可能之后才會(huì)報(bào)告匹配失敗。
這時(shí)可以用固化分組來(lái)進(jìn)行優(yōu)化,一旦占有字符,就不再釋放。也就是一旦占有,就不再記錄可供回溯的可能。通常是與排除型字符組或順序否定環(huán)視一起使用的。
優(yōu)化后的正則表達(dá)式:
需要說(shuō)明的一點(diǎn),固化分組要作用于量詞修飾的子表達(dá)式才有意義,對(duì)于“(?>abc)”由于內(nèi)容是固定的,根本就不會(huì)產(chǎn)生回溯,所以使用固化分組是沒(méi)有意義的。
對(duì)于平衡組的應(yīng)用也是一樣,如果分組構(gòu)造中沒(méi)有量詞,那么使用固化分組就是沒(méi)有意義的,比如版本0
Regex reg = new Regex(@")|.)*?(?(Open)(?!))\)");
這種場(chǎng)景下使用固化分組就是沒(méi)有意義的。
在版本3基礎(chǔ)上對(duì)捕獲組進(jìn)行優(yōu)化后,就得到版本4。
string test = "a+(b*(c+d))/e+f-(g/(h-i))*j"; Regex reg4 = new Regex(@"\( ? ? ? ? ? ? ? ? ? ? ? ? ?#普通字符“(” ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?> ? ? ? ? ? ? ? ? ? ? #分組構(gòu)造,用來(lái)限定量詞“*”修飾范圍 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [^()]+ ? ? ? ? ? ? ?#非括弧的其它任意字符 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? \( ?(?<Open>) ? ? ? #命名捕獲組,遇到開括弧Open計(jì)數(shù)加1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? \) ?(?<-Open>) ? ? ?#狹義平衡組,遇到閉括弧Open計(jì)數(shù)減1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? )* ? ? ? ? ? ? ? ? ? ? ?#以上子串出現(xiàn)0次或任意多次 ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?(Open)(?!)) ? ? ? ? ? #判斷是否還有'OPEN',有則說(shuō)明不配對(duì),什么都不匹配 ? ? ? ? ? ? ? ? ? ? ? ? \) ? ? ? ? ? ? ? ? ? ? ? ? ?#普通閉括弧 ? ? ? ? ? ? ? ? ? ? ? ?", RegexOptions.IgnorePatternWhitespace); MatchCollection mc = reg4.Matches(test); foreach (Match m in mc) { ? ? ?richTextBox2.Text += m.Value + "\n"; } /*--------輸出-------- (b*(c+d)) (g/(h-i)) */
那么對(duì)于分組構(gòu)造外層的“*”修飾的子表達(dá)式是否可以使用固化分組呢?答案是否定的,因?yàn)槠胶饨M通常是要進(jìn)行回溯才能最終匹配成功的,所以如果使用固化分組,不記錄回溯可能的話,將無(wú)法得到正確結(jié)果。
3.1.5 進(jìn)一步優(yōu)化討論
那么現(xiàn)在是不是已經(jīng)完成優(yōu)化了呢?是的,通??梢赃@么認(rèn)為。在一般應(yīng)用當(dāng)中,這已經(jīng)是從正則層面上來(lái)說(shuō),最優(yōu)方案了。
但是在有些場(chǎng)景下,由于Compiled模式可以有效提高分支結(jié)構(gòu)的匹配效率,所以對(duì)于源字符串比較復(fù)雜的情況,犧牲一些編譯時(shí)間和內(nèi)存,還是可以有效提高匹配效率的。
Regex reg5 = new Regex(@"\( ? ? ? ? ? ? ? ? ? ? ? ? #普通字符“(” ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?> ? ? ? ? ? ? ? ? ? ? #分組構(gòu)造,用來(lái)限定量詞“*”修飾范圍 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [^()]+ ? ? ? ? ? ? ?#非括弧的其它任意字符 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? \( ?(?<Open>) ? ? ? #命名捕獲組,遇到開括弧Open計(jì)數(shù)加1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? \) ?(?<-Open>) ? ? ?#狹義平衡組,遇到閉括弧Open計(jì)數(shù)減1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? )* ? ? ? ? ? ? ? ? ? ? ?#以上子串出現(xiàn)0次或任意多次 ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?(Open)(?!)) ? ? ? ? ? #判斷是否還有'OPEN',有則說(shuō)明不配對(duì),什么都不匹配 ? ? ? ? ? ? ? ? ? ? ? ? \) ? ? ? ? ? ? ? ? ? ? ? ? ?#普通閉括弧 ? ? ? ? ? ? ? ? ? ? ? ?", RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); MatchCollection mc = reg5.Matches(test); foreach (Match m in mc) { ? ? ?richTextBox2.Text += m.Value + "\n"; } /*--------輸出-------- (b*(c+d)) (g/(h-i)) */
并不是所有應(yīng)用場(chǎng)景都適合使用Compiled模式,比如上面這個(gè)例子里的源字符串如果是“a+(b*(c+d))/e+f-(g/(h-i))*j”,本身是非常簡(jiǎn)單的,使用Compiled模式將是得不償失的。什么時(shí)候使用,要根據(jù)具體問(wèn)題具體分析。
3.2 字符序列嵌套結(jié)構(gòu)平衡組應(yīng)用
字符序列嵌套結(jié)構(gòu)的匹配,典型的應(yīng)用就是html標(biāo)簽的提取。由于上面詳細(xì)說(shuō)明了單字符嵌套結(jié)構(gòu)的優(yōu)化過(guò)程,這里主要講應(yīng)用場(chǎng)景,個(gè)別涉及到優(yōu)化的地方再討論。
字符序列嵌套結(jié)構(gòu)的匹配,舉例來(lái)說(shuō),取div標(biāo)簽。源字符串如下:
<div id="0"> ? ? 0 </div> <div id="1"> ? ? 1 ? ? <div id="2"> ? ? ? ? 2 </div> </div>
3.2.1提取最外層嵌套結(jié)構(gòu)
提取最外層div標(biāo)簽,分析過(guò)程及構(gòu)造方式與單字符嵌套結(jié)構(gòu)差不多,只是捕獲組等內(nèi)容稍稍復(fù)雜點(diǎn),先給出實(shí)現(xiàn),再進(jìn)行解釋。
string test = @"<div id=""0""> ? ? 0 </div> <div id=""1""> ? ? 1 ? ? <div id=""2""> ? ? ? ? 2 ? ? </div> </div>"; Regex reg = new Regex(@"(?isx) ? ? ? ? ? ? ? ? ? ? ?#匹配模式,忽略大小寫,“.”匹配任意字符 ? ? ? ? ? ? ? ? ? ? ? <div[^>]*> ? ? ? ? ? ? ? ? ? ? ?#開始標(biāo)記“<div...>” ? ? ? ? ? ? ? ? ? ? ? ? ? (?> ? ? ? ? ? ? ? ? ? ? ? ? #分組構(gòu)造,用來(lái)限定量詞“*”修飾范圍 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <div[^>]*> ?(?<Open>) ? #命名捕獲組,遇到開始標(biāo)記,入棧,Open計(jì)數(shù)加1 ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? </div> ?(?<-Open>) ? ? ?#狹義平衡組,遇到結(jié)束標(biāo)記,出棧,Open計(jì)數(shù)減1 ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?:(?!</?div\b).)* ? ? ?#右側(cè)不為開始或結(jié)束標(biāo)記的任意字符 ? ? ? ? ? ? ? ? ? ? ? ? ? )* ? ? ? ? ? ? ? ? ? ? ? ? ?#以上子串出現(xiàn)0次或任意多次 ? ? ? ? ? ? ? ? ? ? ? ? ? (?(Open)(?!)) ? ? ? ? ? ? ? #判斷是否還有'OPEN',有則說(shuō)明不配對(duì),什么都不匹配 ? ? ? ? ? ? ? ? ? ? ? </div> ? ? ? ? ? ? ? ? ? ? ? ? ?#結(jié)束標(biāo)記“</div>” ? ? ? ? ? ? ? ? ? ? ? "); MatchCollection mc = reg.Matches(test); foreach (Match m in mc) { richTextBox2.Text += m.Value + "\n--------------------\n"; } /*--------輸出-------- <div id="0"> ? ? 0 </div> -------------------- <div id="1"> ? ? 1 ? ? <div id="2"> ? ? ? ? 2 ? ? </div> </div> -------------------- */
在單字符嵌套結(jié)構(gòu)中,使用排除型字符組“[^()]+”,與分組構(gòu)造外的匹配優(yōu)先量詞“*” 達(dá)到貪婪模式匹配效果。在字符序列嵌套結(jié)構(gòu)中,要排除的是一個(gè)子串,而不是簡(jiǎn)單的幾個(gè)無(wú)序字符,所以不能使用排除型字符組,此時(shí)需要用到順序否定環(huán)視來(lái)達(dá)到這一目的。“(?:(?!</?div\b).)*”表示的是所在位置右側(cè)不是“<div…>”或“</div>”的字符,這樣的字符重復(fù)0次或任意多次。關(guān)于環(huán)視的細(xì)節(jié),可以參考 正則基礎(chǔ)之——環(huán)視。
而由于這種否定環(huán)視包含兩種狀態(tài),所以在與固化分組結(jié)合使用時(shí),會(huì)與后面的開始或結(jié)束標(biāo)記形成包含關(guān)系,所以與固化分組一起使用時(shí),不能放在左側(cè),只能放在右側(cè)。
3.2.2 根據(jù)id提取div嵌套標(biāo)簽
根據(jù)id提取div時(shí),改變的只是最外層div的結(jié)構(gòu),對(duì)內(nèi)分組構(gòu)造內(nèi)部結(jié)構(gòu)沒(méi)有影響。但是因?yàn)閕d是變化的,所以正則需要?jiǎng)討B(tài)生成。下面給出實(shí)現(xiàn),源字符串和輸出結(jié)果由于比較影響篇幅,就不再給出了。
string id = Regex.Escape(textBox1.Text); ? ? ? ? ? ? ? ? ? ?//動(dòng)態(tài)獲取id Regex reg = new Regex(@"(?isx) ? ? ? ? ? ? ? ? ? ? ? <div(?:(?!(?:id=|</?div\b)).)*id=(['""]?)" + id ?+ @"\1[^>]*> ? ? ? ?#開始標(biāo)記“<div...>” ? ? ? ? ? ? ? ? ? ? ? ? ? (?> ? ? ? ? ? ? ? ? ? ? ? ? #分組構(gòu)造,用來(lái)限定量詞“*”修飾范圍 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <div[^>]*> ?(?<Open>) ? #命名捕獲組,遇到開始標(biāo)記,入棧,Open計(jì)數(shù)加1 ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? </div> ?(?<-Open>) ? ? ?#狹義平衡組,遇到結(jié)束標(biāo)記,出棧,Open計(jì)數(shù)減1 ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?:(?!</?div\b).)* ? ? ?#右側(cè)不為開始或結(jié)束標(biāo)記的任意字符 ? ? ? ? ? ? ? ? ? ? ? ? ? )* ? ? ? ? ? ? ? ? ? ? ? ? ?#以上子串出現(xiàn)0次或任意多次 ? ? ? ? ? ? ? ? ? ? ? ? ? (?(Open)(?!)) ? ? ? ? ? ? ? #判斷是否還有'OPEN',有則說(shuō)明不配對(duì),什么都不匹配 ? ? ? ? ? ? ? ? ? ? ? </div> ? ? ? ? ? ? ? ? ? ? ? ? ?#結(jié)束標(biāo)記“</div>” ? ? ? ? ? ? ? ? ? ? ?"); MatchCollection mc = reg.Matches(test); foreach (Match m in mc) { ? ? ?richTextBox2.Text += m.Value + "\n--------------------\n"; }
在動(dòng)態(tài)生成正則表達(dá)式時(shí),由于輸入的字符串中可能存在正則中有特殊意義的元字符,如果不進(jìn)行轉(zhuǎn)義的話,正則解析時(shí)會(huì)拋出異常。所以用Regex.Escape(string str)來(lái)對(duì)動(dòng)態(tài)輸入的字符串進(jìn)行轉(zhuǎn)義處理,確保不會(huì)因動(dòng)態(tài)輸入的內(nèi)容而拋異常。比如上面的例子,如果id不進(jìn)行轉(zhuǎn)義處理時(shí),輸入“abc(def”就會(huì)拋“) 不足”這樣的異常。
3.2.3 根據(jù)id提取任意嵌套標(biāo)簽
再擴(kuò)展一下,根據(jù)id屬性取任意嵌套標(biāo)簽。實(shí)現(xiàn)如下,具體實(shí)現(xiàn)細(xì)節(jié)和討論參考 就是通過(guò)id獲得一個(gè)html標(biāo)簽塊。以下正則相對(duì)于帖子對(duì)個(gè)別細(xì)節(jié)做了調(diào)整。
string html = @" <html> <body> <div id=""div1""> ? ? <div id=""div2"" style=""background:Red;""> ? ? ? ? <div id=""div3""> ? ? ? ? ? ? <table id=""table1""> ? ? ? ? ? ? ? ? <tr> ? ? ? ? ? ? ? ? ? ? <td> ? ? ? ? ? ? ? ? ? ? ? ? <div id=""div4"" style=""width:100px""></div> ? ? ? ? ? ? ? ? ? ? </td> ? ? ? ? ? ? ? ? </tr> ? ? ? ? ? ? </table> ? ? ? ? </div> ? ? </div> ? ? <div id=div5> ? ? ? ? <a href=""http://www.csdn.net"">csdn</a> ? ? </div> </div> <img src=""http://www.csdn.net/Images/logo_csdn.gif""/> </body> </html>"; Console.WriteLine(html); string[] idList = { "div1", "div2", "div3", "div4", "table1", "div5", "abc(def" }; string pattern = @"<([a-z]+)(?:(?!\bid\b)[^<>])*id=([""']?){0}\2[^>]*>(?><\1[^>]*>(?<o>)|</\1>(?<-o>)|(?:(?!</?\1).)*)*(?(o)(?!))</\1>"; foreach (string id in idList) { ? ? ?Match match = Regex.Match(html, string.Format(pattern, Regex.Escape(id)), ? ? ? ? ? ? ? ? ? ? RegexOptions.Singleline | RegexOptions.IgnoreCase); ? ? ?Console.WriteLine("--------begin {0}--------", id); ? ? ?if (match.Success) ? ? ? ? ? Console.WriteLine(match.Value); ? ? ?else ? ? ? ? ? Console.WriteLine("o(╯□╰)o"); ? ? ?Console.WriteLine("--------end {0}--------", id); } Console.ReadLine();
3.2.4 根據(jù)標(biāo)簽取外層嵌套結(jié)構(gòu)
根據(jù)動(dòng)態(tài)輸入的tag,取相應(yīng)的最外層的嵌套標(biāo)簽,實(shí)現(xiàn)如下。
string html = @" <html> <body> <div id=""div1""> ? ? <div id=""div2"" style=""background:Red;""> ? ? ? ? <div id=""div3""> ? ? ? ? ? ? <table id=""table1""> ? ? ? ? ? ? ? ? <tr> ? ? ? ? ? ? ? ? ? ? <td> ? ? ? ? ? ? ? ? ? ? ? ? <div id=""div4"" style=""width:100px""></div> ? ? ? ? ? ? ? ? ? ? </td> ? ? ? ? ? ? ? ? </tr> ? ? ? ? ? ? </table> ? ? ? ? </div> ? ? </div> ? ? <div id=div5> ? ? ? ? <a href=""http://www.csdn.net"">csdn</a> ? ? </div> </div> <img src=""http://www.csdn.net/Images/logo_csdn.gif""/> </body> </html>"; Console.WriteLine(html); string[] tagList = { "html", "body", "div", "table", "abc(def" }; string pattern = @"(?isx) ? ? ? ? ? ? ? ? ? ? ? <({0})\b[^>]*> ? ? ? ? ? ? ? ? ?#開始標(biāo)記“<tag...>” ? ? ? ? ? ? ? ? ? ? ? ? ? (?> ? ? ? ? ? ? ? ? ? ? ? ? #分組構(gòu)造,用來(lái)限定量詞“*”修飾范圍 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <\1[^>]*> ?(?<Open>) ? ?#命名捕獲組,遇到開始標(biāo)記,入棧,Open計(jì)數(shù)加1 ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? </\1> ?(?<-Open>) ? ? ? #狹義平衡組,遇到結(jié)束標(biāo)記,出棧,Open計(jì)數(shù)減1 ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?:(?!</?\1\b).)* ? ? ? #右側(cè)不為開始或結(jié)束標(biāo)記的任意字符 ? ? ? ? ? ? ? ? ? ? ? ? ? )* ? ? ? ? ? ? ? ? ? ? ? ? ?#以上子串出現(xiàn)0次或任意多次 ? ? ? ? ? ? ? ? ? ? ? ? ? (?(Open)(?!)) ? ? ? ? ? ? ? #判斷是否還有'OPEN',有則說(shuō)明不配對(duì),什么都不匹配 ? ? ? ? ? ? ? ? ? ? ? </\1> ? ? ? ? ? ? ? ? ? ? ? ? ? #結(jié)束標(biāo)記“</tag>” ? ? ? ? ? ? ? ? ? ? ?"; foreach (string tag in tagList) { ? ? ?Match match = Regex.Match(html, string.Format(pattern, Regex.Escape(tag))); ? ? ?Console.WriteLine("--------begin {0}--------", tag); ? ? ?if (match.Success) ? ? ? ? ?Console.WriteLine(match.Value); ? ? ?else ? ? ? ? ?Console.WriteLine("o(╯□╰)o"); ? ? Console.WriteLine("--------end {0}--------", tag); } Console.ReadLine();
3.2.5 條件判斷結(jié)構(gòu)擴(kuò)展應(yīng)用
條件判斷結(jié)構(gòu)的作用不只限于驗(yàn)證開始和結(jié)束標(biāo)記是否配對(duì),根據(jù)需求的不同,還可以有其它一些應(yīng)用。比如在匹配div標(biāo)簽時(shí),只取內(nèi)部“存在”嵌套的外層標(biāo)簽。
string test = @"<div id=""0""> ? ? 0 </div> <div id=""1""> ? ? 1 ? ? <div id=""2""> ? ? ? ? 2 ? ? </div> </div>"; Regex reg = new Regex(@"(?isx) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?#匹配模式,忽略大小寫,“.”匹配任意字符 ? ? ? ? ? ? ? ? ? ? ? <div[^>]*> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?#開始標(biāo)記“<div...>” ? ? ? ? ? ? ? ? ? ? ? ? ? (?> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #分組構(gòu)造,用來(lái)限定量詞“*”修飾范圍 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <div[^>]*> ?(?<Open>)(?<Mask>) ?#遇到開始標(biāo)記,入棧,Open和Mask計(jì)數(shù)各加1 ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? </div> ?(?<-Open>) ? ? ? ? ? ? ?#遇到結(jié)束標(biāo)記,出棧,Open計(jì)數(shù)減1 ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #分支結(jié)構(gòu) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (?:(?!</?div\b).)* ? ? ? ? ? ? ?#右側(cè)不為開始或結(jié)束標(biāo)記的任意字符 ? ? ? ? ? ? ? ? ? ? ? ? ? )* ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?#以上子串出現(xiàn)0次或任意多次 ? ? ? ? ? ? ? ? ? ? ? ? ? (?(Open)(?!))(?(Mask)|(?!)) ? ? ? ? #'OPEN'保證標(biāo)記配對(duì),'Mask'保證內(nèi)部有嵌套 ? ? ? ? ? ? ? ? ? ? ? </div> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?#結(jié)束標(biāo)記“</div>” ? ? ? ? ? ? ? ? ? ? ? "); MatchCollection mc = reg.Matches(test); foreach (Match m in mc) { ? ? ?richTextBox2.Text += m.Value + "\n--------------------\n"; } /*--------輸出-------- <div id="1"> ? ? 1 ? ? <div id="2"> ? ? ? ? 2 ? ? </div> </div> -------------------- */
命名捕獲組“(?<Mask>)”只入棧不出棧,如果內(nèi)部有嵌套,則“(?<Mask>)”一定有匹配,此時(shí)匹配“(?(Mask)yes|no)”中的“yes”子表達(dá)式,也就是什么都不做;如果內(nèi)部沒(méi)有嵌套,則“(?<Mask>)”沒(méi)有匹配,此時(shí)匹配“(?(Mask)yes|no)”中的“no”子表達(dá)式,也就是報(bào)告匹配失敗。這里省略的是“(?(Mask)yes|no)”中的“yes”子表達(dá)式。
對(duì)于匹配內(nèi)部沒(méi)有嵌套的標(biāo)簽,也就是最內(nèi)層標(biāo)簽,可以使用上面的正則表達(dá)式,將“(?(Mask)yes|no)”中的“yes”子表達(dá)式設(shè)為“(?!)”,將“yes”子表達(dá)式省略。不過(guò)這樣做有些浪費(fèi),完全可以用順序否定環(huán)視來(lái)實(shí)現(xiàn)這一需求。
string test = @"<div id=""0""> ? ? 0 </div> <div id=""1""> ? ? 1 ? ? <div id=""2""> ? ? ? ? 2 ? ? </div> </div>"; Regex reg = new Regex(@"(?is)<div[^>]*>(?:(?!</?div\b).)*</div>"); MatchCollection mc = reg.Matches(test); foreach (Match m in mc) { ? ? ? richTextBox2.Text += m.Value + "\n--------------------\n"; } /*--------輸出-------- <div id="0"> ? ? 0 </div> -------------------- <div id="2"> ? ? ? ? 2 ? ? </div> -------------------- */
4、平衡組應(yīng)用范圍探討
平衡組可以用來(lái)匹配嵌套結(jié)構(gòu),這是一個(gè)很大的創(chuàng)新,但是否就認(rèn)為平衡組適合用來(lái)解決任何嵌套問(wèn)題呢?事實(shí)當(dāng)然不會(huì)是這樣。
比如下面這個(gè)需求,(參考 請(qǐng)問(wèn)一個(gè)正則表達(dá)式) :
源字符串:1+Sum(1,Sum(2, Sum(3), 4), 5)*4+5+Sum(9,Sum(8, Sum(7), 6), 5)*6+7
要求輸出:
Sum(1,Sum(2, Sum(3), 4), 5)
Sum(2, Sum(3), 4)
Sum(3)
Sum(9,Sum(8, Sum(7), 6), 5)
Sum(8, Sum(7), 6)
Sum(7)
這種需求使用平衡組+遞歸的方式可以實(shí)現(xiàn),實(shí)現(xiàn)代碼如下:
//遞歸方法 private void getNesting(string src, Regex reg, List<string> list) { ? ? MatchCollection mc = reg.Matches(src); ? ? foreach(Match m in mc) ? ? { ? ? ? ? list.Add(m.Value); ? ? ? ? src = m.Value.Remove(m.Value.Length-1, 1); ? ? ? ? if (reg.IsMatch(src)) ? ? ? ? { ? ? ? ? ? ? ?getNesting(src, reg, list); ? ? ? ? } ? ? } } //調(diào)用 string test = "1+Sum(1,Sum(2, Sum(3), 4), 5)*4+5+Sum(9,Sum(8, Sum(7), 6), 5)*6+7"; List<string> list = new List<string>(); Regex reg = new Regex(@"(?i)Sum(?<-o>))*(?(o)(?!))\)", RegexOptions.Compiled); getNesting(test, reg, list); foreach (string s in list) { ? ? ?richTextBox2.Text += s + "\n"; }
平衡組雖然可以實(shí)現(xiàn)要求,但除非你對(duì)效率沒(méi)有要求,否則這一類需求通常是不適合用正則來(lái)實(shí)現(xiàn)的。因?yàn)槠胶饨M并不是為這一功能而設(shè)計(jì)的,在實(shí)現(xiàn)過(guò)程中做了很多額外的嘗試。效率上自然要大打折扣。
類似這樣的需求,可以自己寫有窮自動(dòng)機(jī)來(lái)實(shí)現(xiàn),畢竟正則也只不過(guò)是一種有窮自動(dòng)機(jī)的實(shí)現(xiàn)而已。
? ? ? ? ? ? string test = @"1+Sum(1,Sum(2, Sum(3), 4), 5)*4+5+Sum(9,Sum(8, Sum(7), 6), 5)*6+7 "; ? ? ? ? ? ? StringBuilder nesting = new StringBuilder(64); ? ? ? ? ? ? List<StringBuilder> list = new List<StringBuilder>(); ? ? ? ? ? ? List<string> groups = new List<string>(); ? ? ? ? ? ? int level = 0; ? ? ? ? ? ? int state = 0; ? ? ? ? ? ? foreach (char c in test) ? ? ? ? ? ? { ? ? ? ? ? ? ? ? if ((c == 'S' || c == 's') && state == 0) ? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? state = 1; ? ? ? ? ? ? ? ? ? ? nesting.Append(c); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? else if ((c == 'U' || c == 'u') && state == 1) ? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? state = 2; ? ? ? ? ? ? ? ? ? ? nesting.Append(c); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? else if ((c == 'M' || c == 'm') && state == 2) ? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? state = 3; ? ? ? ? ? ? ? ? ? ? nesting.Append(c); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? else if (c == '(' && state == 3) ? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? state = 0; ? ? ? ? ? ? ? ? ? ? level++; ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? else ? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? state = 0; ? ? ? ? ? ? ? ? ? ? nesting = new StringBuilder(64); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? if (c == ')') ? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? if (level > 0) ? ? ? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? ? ? level--; ? ? ? ? ? ? ? ? ? ? ? ? groups.Add(list[level].ToString() + c); ? ? ? ? ? ? ? ? ? ? ? ? list.Remove(list[level]); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? if (level > 0) ? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? while(list.Count < level) ? ? ? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? ? ? list.Add(nesting); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? for (int i = 0; i < level; i++) ? ? ? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? ? ? list[i].Append(c); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? ? ? foreach (string s in groups) ? ? ? ? ? ? { ? ? ? ? ? ? ? ? Console.WriteLine(s); ? ? ? ? ? ? } ? ? ? ? ? ? Console.ReadLine();
5、其它聲明
到此為止,平衡組的基本應(yīng)用場(chǎng)景和性能調(diào)優(yōu)都已討論完了,本文對(duì)于平衡組匹配原理講得相對(duì)比較少,以應(yīng)用場(chǎng)景分析為主。主要是因?yàn)槟軌蚴褂闷胶饨M來(lái)解決問(wèn)題的人,通常已經(jīng)對(duì)正則的基本語(yǔ)法有了一定程度的理解。而如果事實(shí)確實(shí)如此,那么對(duì)于平衡組的理解,也是水到渠成的了。
以上正則實(shí)現(xiàn)中,采用的多是寬松排列模式,主要是為了加注釋,使得閱讀清晰。而寬松排列模式通常用于教學(xué)目的,實(shí)際使用過(guò)程中,如果不是為了可讀性的考慮,可以去掉這些注釋和寬松排列模式參數(shù)。
上面給出了很多平衡組的應(yīng)用,這里需要說(shuō)明的是,我提供的只是一些方法和思路,從來(lái)不推薦把正則當(dāng)作模板來(lái)用,雖然有些時(shí)候,它確實(shí)可以當(dāng)作模板來(lái)用,但我還是希望你能真正的掌握這些語(yǔ)法規(guī)則之后,再去應(yīng)用平衡組。當(dāng)然,如果你認(rèn)為能用就行,不需要知道為什么可以這樣用,只是把它當(dāng)作模板來(lái)套,我也無(wú)話可說(shuō)。
到此這篇關(guān)于.NET正則基礎(chǔ)之平衡組的文章就介紹到這了,更多相關(guān)正則平衡組內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
正則表達(dá)式中對(duì)各字符集編碼范圍的總結(jié)
正則表達(dá)式中對(duì)各字符集編碼范圍的總結(jié)...2007-03-03用正則表達(dá)式匹配字符串中漢字及中文標(biāo)點(diǎn)符號(hào)
正則表達(dá)式通常用于判斷某一個(gè)字符串是否符合或滿足某一種格式,下面這篇文章主要給大家介紹了關(guān)于如何使用正則表達(dá)式匹配字符串中漢字及中文標(biāo)點(diǎn)符號(hào)的相關(guān)資料,需要的朋友可以參考下2022-07-07只能是字母或數(shù)字或者是字母和數(shù)字的組合的正則previousSibling
只能是字母或數(shù)字或者是字母和數(shù)字的組合的正則previousSibling...2007-03-03JS正則表達(dá)式驗(yàn)證數(shù)字(非常全)
正則表達(dá)式,又稱規(guī)則表達(dá)式,在項(xiàng)目中經(jīng)常會(huì)用到正則表達(dá)式,今天小編抽空給大家分享js正則表達(dá)式驗(yàn)證數(shù)字的方法,感興趣的朋友參考下吧2016-12-12php 正則表達(dá)式提取網(wǎng)頁(yè)超級(jí)鏈接url的函數(shù)
php 正則表達(dá)式提取網(wǎng)頁(yè)超級(jí)鏈接url的函數(shù)2010-01-01