要讀懂這篇文章的精髓,你最好要有一點正則匹配原理的基礎。比如".*?"匹配文本內容"asp163",稍懂正則表達式的人都知道可以匹配,但是你知道他的匹配過程嗎?如果你不太清楚,那么下面的內容,對你來說可能不太適合,或許,看的太吃力且無法領悟平衡組的用法。因此,我建議你先了解正則表達式NFA引擎的匹配原理。想要整理一份易懂易描述的話,的確要費些時間,但不知道這篇內容會不會達到我預期的效果。慢慢完善吧~(注:這是我2010年寫的,現在拿過來,有時間將自己做為讀者來看本篇文章,修改有問題的地方,并增加些實例,盡量做到通俗易懂。)
一般正則教程中對平衡組的介紹
如果想要匹配可嵌套的層次性結構的話,就得使用平衡組了。舉個例子吧,如何把“xx <aa <bbb> <bbb> aa> yy”這樣的字符串里,最長的尖括號內的內容捕獲出來?
這里需要用到以下的語法構造:
(?<group>) 把捕獲的內容命名為group,并壓入堆棧
(?<-group>) 從堆棧上彈出最后壓入堆棧的名為group的捕獲內容,如果堆棧本來為空,則本分組的匹配失敗
(?(group)yes|no) 如果堆棧上存在以名為group的捕獲內容的話,繼續匹配yes部分的表達式,否則繼續匹配no部分
(?!) 順序否定環視,由于沒有后綴表達式,試圖匹配總是失敗
如果你不是一個程序員(或者你是一個對堆棧的概念不熟的程序員),你就這樣理解上面的三種語法吧:第一個就是在黑板上寫一個(或再寫一個)"group",第二個就是從黑板上擦掉一個"group",第三個就是看黑板上寫的還有沒有"group",如果有就繼續匹配yes部分,否則就匹配no部分。
我們需要做的是每碰到了左括號,就在黑板上寫一個"group",每碰到一個右括號,就擦掉一個,到了最后就看看黑板上還有沒有-如果有那就證明左括號比右括號多,那匹配就應該失敗(為了能看得更清楚一點,我用了(?'group')的語法):
< #最外層的左括號 [^<>]* #最外層的左括號后面的不是括號的內容 ( ( (?'Open'<) #碰到了左括號,在黑板上寫一個"Open" [^<>>]* #匹配左括號后面的不是括號的內容 )+ ( (?'-Open'>) #碰到了右括號,擦掉一個"Open" [^<>]* #匹配右括號后面不是括號的內容 )+ )* (?(Open)(?!)) #在遇到最外層的右括號前面,判斷黑板上還有沒有沒擦掉的"Open";如果有,則匹配失敗 > #最外層的右括號
我為什么寫這篇文章
看了上面的介紹,你明白了嗎?在我未理解正則表達式匹配原理之前,看上面對于平衡組的介紹,似懂非懂,且只能當做模板記住,而不能靈活運用。因此查閱大量有關正則方面的資料,這里尤其感謝lxcnn的技術文檔及《精通正則表達式》這本書,讓我對正則表達式有了更深入、更系統的理解,因此,在它們的基礎之上,我就結合自己的學習經歷做個小結,一來做為學習筆記存檔,另外,如果能解決你的疑惑,也是件讓人高興的事。
我先暫不分析上面的代碼,先講解一下關于平衡組相關的概念及知識。
下面表達式匹配測試工具為:Expresso。
平衡組的概念及作用
平衡組,故名思義,平衡即對稱,主要是結合幾種正則語法規則,提供對配對出現的嵌套結構的匹配。平衡組有狹義與廣義兩種定義,狹義平衡組指(?Expression) 語法,而廣義平衡組并不是固定的語法規則,而是幾種語法規則的綜合運用,我們平時所說的平衡組通常指的是廣義平衡組。本文中如無特殊說明,平衡組這種簡寫指的是廣義平衡組。
平衡組的匹配原理
平衡組的匹配原理可以用堆棧來解釋,先舉個例子,再根據例子進行解釋。
源字符串:a+(b*(c+d))/e+f-(g/(h-i))*j
正則表達式:((?<Open>\()|(?<−Open>)|[^()])*(?(Open)(?!))\)
需求說明:匹配成對出現的()中的內容
輸出:(b*(c+d)) 和 (g/(h-i))
我將上面正則表達式代碼分行寫,并加上注釋,這樣看起來有層次,而且方便
\( #普通字符“(” ( #分組構造,用來限定量詞“*”修飾范圍 (?<Open>\() #命名捕獲組,遇到開括弧“Open”計數加1 | #分支結構 (?<-Open>\)) #狹義平衡組,遇到閉括弧“Open”計數減1 | #分支結構 [^()]+ #非括弧的其它任意字符 )* #以上子串出現0次或任意多次 (?(Open)(?!)) #判斷是否還有“Open”,有則說明不配對,什么都不匹配 \) #普通閉括弧