讀古今文學網 > 精通正則表達式(第3版) > .NET高級話題 >

.NET高級話題

Advanced.NET

下面的內容涉及某些尚未介紹過的特性:通過正則裝配件(regex assemblies)構建正則表達式庫,使用.NET專屬的特性匹配嵌套結構,以及對Capture對象的講解。

正則表達式裝配件

Regex Assemblies

.NET能夠把Regex對像封裝到裝配件(assembly)中,在構建正則表達式庫時這很有用。下一頁的補充內容提供了示例。

運行補充內容中的例子,能夠在當前工程的bin代碼目錄下創建JfriedlsRegexLibrary.DLL。然後我們可以通過Visual Studio.NET的Project>Add Reference將其加入,在其他工程中使用這個裝配件。

要使用裝配件中的類,首先必須導入:

Imports jfriedl

然後就可以像其他任何類一樣引用它們,例如:

在這個例子中,我僅僅從 jfriedl namespace 導入,但也可以很簡單地從 jfiedl.CSV namespace導入,然後這樣創建Regex對像:

Dim FieldRegex as GetField=New GetField\'生成新的regex對像

這是兩種風格。

也可以不進行任何導入,而是直接使用:

Dim FieldRegex as jfriedl.CSV.GetField=New jfriedl.CSV.GetField

這有點麻煩,但是清楚地說明了對象的出處。同樣,這只是風格的問題。

匹配嵌套結構

Matching Nested Constructs

微軟提供了一種創新的功能,專門用於匹配對稱的結構(長期以來,正則表達式對此無能為力)。它理解起來並不容易——本節篇幅不長,但請注意,其中的內容份量不少。

最簡單的辦法就是用一個例子來說明:

它匹配第一組正確配對的嵌套括號,例如中用下畫線標注的部分。第一個開括號不會匹配,因為它沒有對應的閉括號。

這裡簡要說明了程序的工作原理:

1.每匹配一個(超過正則表達式開頭「(」的)『(』,「(?<DEPTH>)」會把正則表達式保存的當

前括號嵌套深度值加1。

2.每匹配一個『)』,「(?<-DEPTH>)」會把深度減1。

3.「(?(DEPTH)(?!))」確保最後的「)」匹配時,深度應該為0。

因為引擎的回溯堆棧保存了當前匹配成功分組的信息,這個辦法沒有問題。「(?<DEPTH>)」只是使用了命名捕獲的「()」,它總是能成功匹配。因為它緊跟在「(」之後,它的成功匹配(此信息會保存在堆棧中,直到出棧為止)用於標記開括號的數目。

因此,當前已經成功匹配的『DEPTH』分組總數就保存在回溯堆棧中。我們希望在找到閉括號之後減去它們。.NET獨有的「(?<-DEPTH>)」結構,會從堆棧中去掉最近的「successful DEPTH」標記。如果不存在這樣的標記,「(?<-DEPTH>)」就會報告失敗,整個正則表達式的匹配宣告失敗。

最後的「(?(DEPTH)(?!))」是一個普通的條件判斷,如果『DEPTH』分組匹配成功它會應用「(?!)」。如果在程序運行到此處時選擇應用此分支,就表示還存在未匹配的開括號。果真如此的話,我們就需要退出匹配(我們不希望匹配不對稱的序列)所以我們用否定型順序環視「(?!)」來做檢查,確保匹配失敗。

看到了嗎?這就是.NET正則表達式匹配嵌套結構的原理。

Capture對像

Capture Objects

.NET的對象模型中還包括Capture對象,之前一直沒有介紹過。依視角的不同,它可能為匹配結果增加了新的觀察角度,也可能是增加把結果弄得更糟。

Capture對像幾乎等價於 Group對象,因為它表示一組捕獲型括號匹配的文本。與 Group對像一樣,它提供了 Value(匹配的文本)、Length(匹配文本的長度),以及 Index(匹配文本在目標字符串中的偏移值,編號從0開始)。

Group對像和Capture對象的主要差別是,每個Group對象都包含了一組Captures,分別對應到匹配過程中各分組的未確定匹配(intermediary match),以及該分組最終匹配的文本。

看下面這個例子:

Dim M as Match=Regex.Match(〞abcdefghijk〞,〞^(..)+〞)

正則表達式匹配了5 組「(..)」,包括了字符串中的絕大多數字符』:因為加號在括號外面,加號控制的每次迭代都會重新捕獲,這個捕獲型括號最後保存的是『ij』(也就是說,M.Groups(1).Value等於『ij』)。相反,M.Groups(1)同樣包含一組Capture,它們對應到「(..)」的匹配過程:

你也許會注意到,最後匹配的『ij』等同於最終全局匹配中的M.Groups(1).Value。看起來,Group的Value就是本分組最終匹配文本的簡記法。M.Groups(1).Value是:

M.Groups (1).Captures(M.Groups(1).Captures.Count-1).Value

關於Capture,還要講幾點:

●M.Groups(1).Capture 是一個 CaptureCollection,與普通的集合類(collection)一樣,它包含了Items和Count屬性。不過,通常大家都不會使用這兩個屬性,而是通過索引值直接訪問,例如 M.Groups(1).Captures(3)(在 C#中是 M.Groups[1].Captures[3])。

●Capture對像沒有Success方法,如果需要,請測試Group的Success。

●到現在,我們已經看到,Capture 對像在 Group 對像內部可用。Match 對象也有Captures 屬性,儘管湧出並不大。M.Captures 可以直接訪問編號為 0 的分組的Captures屬性(也就是說 M.Captures等價於 M.Groups(0).Captures)。因為編號為0的分組表示整個匹配,所以不會有「遍歷」匹配的迭代,所以編號為0的捕獲集合類只有一個Capture。因為它們包含與編號為0的匹配同樣的信息,M.Capters和M.Groups(0).Captures並不是很有用。

.NET的Capture對象是一種創新,但是因為與對像模型「集成過度(overly integrated)」,使用起來反而更複雜,而且令人迷惑。在仔細參閱了.NET的文檔,並真正理解了這些對像之後,我感覺這種做法有利也有弊。一方面,我樂於看到這種創新。雖然它的用法並不會馬上顯現出來,但這或許是因為一直以來我都習慣於用傳統的正則表達式特性來思考問題。

另一方面,在匹配過程中的額外的分組,匹配完成之後把它們封裝到一個對像中,似乎降低了效率,我並不希望降低效率,除非要得到額外的信息。增加的 Capture分組在大多數匹配中不會用到,但是照目前的情況來看,生成 Match 對像時會構建所有的 Group 和Capture對像(以及它們相關的GroupCollection和CaptureCollection對像)。所以無論是否需要,它們都在那裡,如果你能夠發現Capture對象的使用價值,就不要放過。