天天看點

《正規表達式經典執行個體(第2版)》——2.10 再次比對先前比對的文本

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

問題描述

建立一個正規表達式來比對按照yyyy-mm-dd格式的“神奇”日期。神奇日期指的是年份後2位與月份和該月的日期都是相同的數字。例如,2008-08-08就是一個神奇日期。你可以假設目标文本中的所有日期都是有效的。這個正規表達式并不需要考慮去掉像9999-99-99這樣的日期,因為它們不會出現在目标文本中。你隻需要找到神奇的日期即可。

解決方案

讨論

為了在正規表達式中比對先前比對到的文本,我們首先必須記錄上次比對的文本。這可以使用捕獲分組來實作,執行個體2.9中已經講解過。在此之後,我們可以使用反向引用(backreference)來在該正規表達式中的任何地方比對相同的文本。你可以使用一個反斜杠之後跟一個單個數字(1~9)來引用前9個捕獲分組。而第10~99組,則要使用‹10›~‹99›。

《正規表達式經典執行個體(第2版)》——2.10 再次比對先前比對的文本

不要使用01。它或者是一個八進制的轉義,或者會産生一個錯誤。在本書中我們不會用到八進制轉義,因為xff這樣的十六進制轉義更加容易了解。

當正規表達式‹bdd(dd)-1-1b›遇到2008-08-08的時候,開始的‹dd›會比對20。接着正則引擎會進入捕獲分組,并記錄目标文本中所在的位置。

在捕獲分組中的‹dd›會比對08,然後引擎會到達分組的右括号。在這個點上,比對的部分08會被儲存到1号捕獲分組中。

下一個記号是連字元,它會按照字面進行比對。接着就遇到了反向引用。正則引擎會檢查第一個捕獲分組的内容:08。然後引擎會試圖按照字面來比對這個文本。如果該正規表達式是不區分大小寫的,那麼捕獲分組也會按照這種方式進行比對。在這裡,反向引用比對成功。下一個連字元和反向引用也會比對成功。最終,單詞分界符會比對目标文本的結尾,這樣就找到了一個完整比對:2008-08-08。現在捕獲分組中依然儲存的是08。

如果一個捕獲分組被重複,這可以通過量詞(執行個體2.12)或者是回溯(執行個體2.13)來實作,每次捕獲分組比對成功,都會覆寫之前儲存的捕獲分組比對的内容。對該分組的反向引用隻會比對該分組最後一次捕獲到的文本。

如果同一個正規表達式遇到2008-05-24 2007-07-07的時候,當‹bdd(dd)›比對到2008的時候,該分組第一次捕獲到的内容08,會被儲存到第一個(也是唯一一個)捕獲分組中。接下來,連字元會比對它自身。反向引用在試圖用05來比對‹08›的時候,比對失敗。

由于在該正規表達式中不存在其他的選擇分支,引擎會放棄比對嘗試。這包括清除所有的捕獲分組。當引擎再次嘗試的時候,從目标文本中的第一個0開始,‹1›不再存有任何文本内容。

接下來繼續處理2008-05-24 2007-07-07,該分組下一次會捕獲到内容是當‹bdd(dd)›比對到2007的時候,它會把07儲存起來。接下來,連字元比對自身。現在反向引用會試圖比對‹07›。這次比對是成功的,接着下一個連字元、反向引用以及單詞邊界都比對成功。結果是找到了2007-07-07。

因為正則引擎是從前向後處理的,是以應當把捕獲括号放到反向引用的前面。正規表達式‹bdd1-(dd)-1›和‹bdd1-1-(dd)b›永遠不可能比對到任何東西。因為這裡的反向引用是在捕獲分組之前出現的,而它還沒有捕獲到任何東西。除非你使用的是javascript,否則反向引用指向還沒有進行比對嘗試的分組時,它總是會比對失敗。

還沒有參與比對的分組,并不等同于捕獲到長度為0的分組。對一個長度為0的捕獲分組的反向引用總是會比對成功。當‹(^)1›比對字元串的開始的時候,第一個捕獲分組會捕獲到^号的長度為0的比對,進而‹1›會比對成功。在實踐中,這會發生在當所有捕獲分組的内容都是可選的情況下。

《正規表達式經典執行個體(第2版)》——2.10 再次比對先前比對的文本

javascript是我們所知道的唯一與正規表達式中幾十年反向引用的傳統相違背的流派。在javascript中,或者至少在遵循javascript标準的實作中,對一個還沒有參與比對的分組的反向引用總是會比對成功,這同捕獲了長度為0的比對的分組的反向引用是一樣的。是以,在javascript中,‹bdd1-1-(dd)b›會成功比對12--34。