天天看點

《正規表達式經典執行個體(第2版)》——2.17 根據條件比對兩者之一

本節書摘來自異步社群《正規表達式經典執行個體(第2版)》一書中的第2章,第2.17節,作者: 【美】jan goyvaerts , steven levithan著,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

問題描述

建立一個正規表達式,比對一個由逗号分隔的單詞清單one、two和three。每個單詞可以在該清單中出現任意多次,但是每個單詞必須至少出現一次。

解決方案

java、javascript和ruby并不支援條件判斷。在這些(或者其他任何語言)中進行程式設計的時候,你可以使用不帶有條件判斷的正規表達式,再另外編寫一些其他的代碼來檢查其中的三個捕獲分組是否都比對了相應的内容。

讨論

.net、pcre、perl和python支援使用編号捕獲分組的條件判斷(conditional)。‹(?(1)then|else)›是用來檢查第一個捕獲分組是否成功比對的一個條件判斷。如果它比對成功,正則引擎會嘗試去比對‹then›。如果該捕獲分組到目前為止還沒有參與比對嘗試,就會去嘗試比對‹else›。

這裡的括号、問号和豎線都是屬于條件判斷文法的一部分。它們在這裡并不具有平時的含義。你可以在‹then›和‹else›部分中使用任意種類的正規表達式。唯一的限制是如果想要在其中一個部分之内使用選擇分支,就必須使用分組來把它們包到一起。因為在條件判斷中隻允許直接出現一條豎線。

如果願意,也可以省略掉‹then›或‹else›的部分。空的正規表達式總是會找到一個長度為0的比對。這個執行個體所給的解決方案中使用了3個條件判斷,它們都包含空的‹then›部分。如果捕獲分組參與了比對,那麼這個條件判斷隻會簡單地成功比對。

一個空否定型順序環視‹(?!)›用在了‹else›部分。因為空的正規表達式總是會成功比對,是以包含空正規表達式的否定型順序環視則總是會比對失敗。是以,如果第一個捕獲分組沒有比對到任何東西,條件判斷‹(?(1)|(?!))›必定會失敗。

通過把這三個必需的選擇分支放到自己的捕獲分組中,我們可以在正規表達式的結尾使用3個條件判斷來檢查是否所有的捕獲分組都捕獲到了内容。如果其中一個單詞沒有比對,引用其捕獲分組的條件判斷就會執行“else”部分,該部分的空否定型順序環視就會導緻條件判斷比對失敗。是以隻要有一個單詞沒有比對,整個正則式就沒有比對成功。

要允許單詞以任意順序出現,并且出現任意次數,我們将所有單詞放在一個使用選擇分支的分組内,并使用量詞重複這個分組。因為我們有三個單詞,并且要求每個單詞至少比對一次,是以我們知道這個分組至少要重複三次。.net、python和pcre 6.7還支援命名捕獲分組的條件判斷。‹(?(name)then|else)›會檢查命名捕獲分組name是否參與了比對嘗試。perl 5.10及以後版本同樣支援命名條件判斷。但是perl要求名字兩邊使用尖括号或單引号,如‹(?(< name >)then|else)›或‹(?('name')then|else)›。pcre 7.0及以後版本也支援perl的命名條件判斷文法,以及.net和python所采用的文法。

為了更好地了解條件判斷是如何工作的,我們來看一個正規表達式‹(a)?b(?(1)c|d)›。它本質上是與‹abc|bd›等價的一種更為複雜的形式。

如果目标字元串是由一個a開頭的,那麼它就會被捕獲到第一個捕獲分組中。如果不是,那麼第一個捕獲分組就沒有參與到比對嘗試中。在該捕獲分組之後的問号是很重要的,因為這使得整個分組成為可選的。如果不存在a的話,該分組會重複0次,是以不會有機會捕獲任何内容。它不能捕獲一個長度為0的字元串。

如果你使用的是‹(a?)›,那該分組總是會參與到比對嘗試中。而在該分組之後并不存在量詞,是以它會正好重複一次。該分組或者捕獲a,或者捕獲空串。

不管‹a›是否成功比對,下一個記号是‹b›。然後是條件判斷。如果捕獲分組參與了比對嘗試,即使它捕獲的是長度為0的字元串(在這裡是不可能的),都會嘗試比對‹c›。如果沒有的話,那麼會嘗試比對‹d›。

用一句話來描述,‹(a)?b(?(1)c|d)›或者比對ab後跟着c,或者比對b後跟着d。

在.net、pcre和perl中(但是不包括python),條件判斷中還可以使用環視。‹(?(?=if__)then|else)›首先會把‹(?=if )›當作一個正常的順序環視來進行測試。執行個體2.16中講解了它是如何執行的。如果環視比對成功的話,那麼會接着嘗試比對‹then›部分。如果沒有成功的話,那麼會嘗試比對‹else›中的正規表達式。因為環視的長度為0,是以‹then›和‹else›中的正規表達式都會在目标文本中‹if›比對成功或者失敗的同一位置處接着進行嘗試。

在條件判斷中,除了使用順序環視,也可以使用逆序環視。你還可以使用否定型的環視,雖然我們并不推薦這樣做,因為它會把“then”和“else”的含義反轉,進而造成不必要的混淆。

圖像說明文字使用環視的條件判斷也可以寫成不使用條件判斷的形式:‹(?=if)then| (?!if)else›。如果肯定型順序環視比對成功,就會嘗試比對‹then›部分。如果肯定型順序環視比對失敗,那麼會嘗試比對第二個選擇。接下來的否定型順序環視會做同樣的檢查。否定型順序環視在‹if›比對失敗的時候會比對成功,因為‹(?=if)›已經比對失敗了,是以否定型順序環視肯定成功。是以,繼續嘗試比對‹else›。把順序環視放到條件判斷中更節省時間,因為條件判斷隻需嘗試比對‹if›一次。