平衡組/遞迴匹配
這裡介紹的平衡組文法是由.Net Framework支援的;其它語言/庫不一定支援這種功能,或者支援此功能但需要使用不同的文法。
有時我們需要匹配像( 100 * ( 50 + 15 ) )這樣的可嵌套的層次性結構,這時簡單地使用\(.+\)則只會匹配到最左邊的左括弧和最右邊的右括弧之間的內容(這裡我們討論的是貪婪模式,懶惰模式也有下面的問題)。假如原來的字串裡的左括弧和右括弧出現的次數不相等,比如( 5 / ( 3 + 2 ) ) ),那我們的匹配結果裡兩者的個數也不會相等。有沒有辦法在這樣的字串裡匹配到最長的,配對的括弧之間的內容呢?
為了避免(和\(把你的大腦徹底搞糊塗,我們還是用角括弧代替圓括弧吧。現在我們的問題變成了如何把xx <aa <bbb> <bbb> aa> yy這樣的字串裡,最長的配對的角括弧內的內容捕獲出來?
這裡需要用到以下的文法構造:
- (?'group') 把捕獲的內容命名為group,並壓入堆棧(Stack)
- (?'-group') 從堆棧上彈出最後壓入堆棧的名為group的捕獲內容,如果堆棧本來為空白,則本分組的匹配失敗
- (?(group)yes|no) 如果堆棧上存在以名為group的捕獲內容的話,繼續匹配yes部分的運算式,否則繼續匹配no部分
- (?!) 零寬負向先行斷言,由於沒有尾碼運算式,試圖匹配總是失敗
如果你不是一個程式員(或者你自稱程式員但是不知道堆棧是什麼東西),你就這樣理解上面的三種文法吧:第一個就 是在黑板上寫一個"group",第二個就是從黑板上擦掉一個"group",第三個就是看黑板上寫的還有沒有"group",如果有就繼續匹配yes部 分,否則就匹配no部分。
我們需要做的是每碰到了左括弧,就在壓入一個"Open",每碰到一個右括弧,就彈出一個,到了最後就看看堆棧是否為空白--如果不為空白那就證明左括弧比右括弧多,那匹配就應該失敗。Regex引擎會進行回溯(放棄最前面或最後面的一些字元),盡量使整個運算式得到匹配。
< #最外層的左括弧
[^<>]* #最外層的左括弧後面的不是括弧的內容
(
(
(?'Open'<) #碰到了左括弧,在黑板上寫一個"Open"
[^<>]* #匹配左括弧後面的不是括弧的內容
)+
(
(?'-Open'>) #碰到了右括弧,擦掉一個"Open"
[^<>]* #匹配右括弧後面不是括弧的內容
)+
)*
(?(Open)(?!)) #在遇到最外層的右括弧前面,判斷黑板上還有沒有沒擦掉的"Open";如果還有,則匹配失敗
> #最外層的右括弧
平衡組的一個最常見的應用就是匹配HTML,下面這個例子可以匹配嵌套的<div>標籤:
<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>