選擇與編組
選擇允許使用 '|' 字元來在兩個或多個候選項中進行選擇。通過擴充章區段標頭的Regex,可以將其擴充為不僅僅適用於章區段標頭的運算式。不過,這可沒有想象的那麼直接。在使用選擇時,將匹配'|' 字元每邊最可能的運算式。你可能認為下面的 JScript 和 VBScript 運算式將匹配位於一行的開始和結束位置且後跟一個或兩個數位 'Chapter' 或 'Section':
/^Chapter|Section [1-9][0-9]{0,1}$/ "^Chapter|Section [1-9][0-9]{0,1}$"
不幸的是,真正的情況是上面所示的Regex要麼匹配位於一行開始處的單詞 'Chapter',要麼匹配一行結束處的後跟任何數位 'Section'。如果輸入字串為 'Chapter 22',上面的運算式將只匹配單詞 'Chapter'。如果輸入字串為 'Section 22',則該運算式將匹配 'Section 22'。但這種結果不是我們此處的目的,因此必須有一種辦法來使Regex對於所要做的更易於響應,而且確實也有這種方法。
可以使用圓括弧來限制選擇的範圍,也就是說明確該選擇只適用於這兩個單詞 'Chapter' 和 'Section'。不過,圓括弧同樣也是難處理的,因為它們也用來建立子運算式,有些內容將在後面關於子運算式的部分介紹。通過採用上面所示的Regex並在適當位置添加圓括弧,就可以使該Regex既可以匹配 'Chapter 1',也可以匹配 'Section 3'。
下面的Regex使用圓括弧將 'Chapter' 和 'Section' 組成一組,所以該運算式才能正確工作。對 JScript 為:
/^(Chapter|Section) [1-9][0-9]{0,1}$/
對 VBScript 為:
"^(Chapter|Section) [1-9][0-9]{0,1}$"
這些運算式工作正確,只是產生了一個有趣的副產品。在 'Chapter|Section' 兩邊放置圓括弧建立了適當的編組,但也導致兩個待匹配單詞之一都被捕獲供今後使用。由於在上面所示的運算式中只有一組圓括弧,因此只能有一個捕獲的 submatch。可以使用 VBScript 的Submatches 集合或者JScript 中RegExp 對象的 $1-$9 屬性來引用這個子匹配。
有時捕獲一個子匹配是所希望的,有時則是不希望的。在說明所示的樣本中,真正想做的就是使用圓括弧對單詞 'Chapter' 或 'Section' 之間的選擇編組。並不希望在後面再引用該匹配。實際上,除非真的是需要捕獲子匹配,否則請不要使用。由於不需要花時間和記憶體來儲存那些子匹配,這種Regex的效率將更高。
可以在Regex模式圓括弧內部的前面使用 '?:'來防止儲存該匹配供今後使用。對上面所示Regex的下述修改提供了免除子匹配儲存的相同功能。對 JScript:
/^(?:Chapter|Section) [1-9][0-9]{0,1}$/
對 VBScript:
"^(?:Chapter|Section) [1-9][0-9]{0,1}$"
除了 '?:' 元字元,還有兩個非捕獲元字元用於稱之為預查的匹配。一個為正向預查,用 ?= 表示, 在任何開始匹配圓括弧內的Regex模式的位置來匹配搜尋字串。一個為負向預查,用 '?!' 表示,在任何開始不匹配該Regex模式的位置來匹配搜尋字串。
例如,假定有一個包含引用有 Windows 3.1、Windows 95、Windows 98 以及 Windows NT 的文檔。進一步假設需要更新該文檔,方法是尋找所有對 Windows 95、Windows 98 以及 Windows NT 的引用,並將這些引用更改為 Windows 2000。可以使用下面的 JScript Regex,這是一個正向預查,來匹配 Windows 95、Windows 98 以及 Windows NT:
/Windows(?=95 |98 |NT )/
在 VBScript 要進行同樣的匹配可以使用下述運算式:
"Windows(?=95 |98 |NT )"
找到一個匹配後,緊接匹配到的文字(而不包括預查中使用的字元)就開始對下一次匹配的搜尋。例如,如果上面所示的運算式匹配到 'Windows 98',則將從 'Windows' 而不是 '98' 之後繼續尋找。
後向引用
Regex一個最重要的特性就是將匹配成功的模式的某部分進行儲存供以後使用這一能力。請回想一下,對一個Regex模式或部分模式兩邊添加圓括弧將導致這部分運算式儲存到一個臨時緩衝區中。可以使用非捕獲元字元 '?:', '?=', or '?!' 來忽略對這部分Regex的儲存。
所捕獲的每個子匹配都按照在Regex模式中從左至右所遇到的內容儲存。儲存子匹配的緩衝區編號從 1 開始,連續編號直至最大 99 個子運算式。每個緩衝區都可以使用 '/n' 訪問,其中 n 為一個標識特定緩衝區的一位或兩位十進位數。
後向引用一個最簡單,最有用的應用是提供了確定文字中連續出現兩個相同單詞的位置的能力。請看下面的句子:
Is is the cost of of gasoline going up up?
根據所寫內容,上面的句子明顯存在單詞多次重複的問題。如果能有一種方法無需尋找每個單詞的重複現象就能修改該句子就好了。下面的 JScript Regex使用一個子運算式就可以實現這一功能。
//b([a-z]+) /1/b/gi
等價的 VBScript 運算式為:
"/b([a-z]+) /1/b"
在這個樣本中,子運算式就是圓括弧之間的每一項。所捕獲的運算式包括一個或多個字母字元,即由'[a-z]+' 所指定的。該Regex的第二部分是對前面所捕獲的子匹配的引用,也就是由附加運算式所匹配的第二次出現的單詞。'/1'用來指定第一個子匹配。單詞邊界元字元確保只檢測單獨的單詞。如果不這樣,則諸如 "is issued" 或 "this is" 這樣的短語都會被該運算式不正確地識別。
在 JScript 運算式中,Regex後面的全域標誌 ('g') 表示該運算式將用來在輸入字串中尋找儘可能多的匹配。大小寫敏感性由運算式結束處的大小寫敏感性標記 ('i') 指定。多行標記指定可能出現在分行符號的兩端的潛在匹配。對 VBScript 而言,在運算式中不能設定各種標記,但必須使用 RegExp 對象的屬性來顯式設定。
使用上面所示的Regex,下面的 JScript 代碼可以使用子匹配資訊,在一個文字字串中將連續出現兩次的相同單詞替換為一個相同的單詞:
var ss = "Is is the cost of of gasoline going up up?./n"; var re = //b([a-z]+) /1/b/gim; //建立Regex樣式. var rv = ss.replace(re,"$1"); //用一個單詞替代兩個單詞.
最接近的等價 VBScript 代碼如下:
Dim ss, re, rv ss = "Is is the cost of of gasoline going up up?." & vbNewLine Set re = New RegExp re.Pattern = "/b([a-z]+) /1/b" re.Global = True re.IgnoreCase = True re.MultiLine = True rv = re.Replace(ss,"$1")
請注意在 VBScript 代碼中,全域、大小寫敏感性以及多行標記都是使用 RegExp 對象的適當屬性來設定的。
在replace 方法中使用 $1 來引用所儲存的第一個子匹配。如果有多個子匹配,則可以用 $2, $3 等繼續引用。
後向引用的另一個用途是將一個通用資源指示符 (URI) 分解為組件部分。假定希望將下述的URI 分解為協議 (ftp, http, etc),網域名稱地址以及頁面/路徑:
http://msdn.microsoft.com:80/scripting/default.htm
下面的Regex可以提供這個功能。對 JScript,為:
/(/w+):////([^/:]+)(:/d*)?([^# ]*)/
對 VBScript 為:
"(/w+):////([^/:]+)(:/d*)?([^# ]*)"
第一個附加子運算式是用來捕獲該 網址的協議部分。該子運算式匹配位於一個冒號和兩個正斜杠之前的任何單詞。第二個附加子運算式捕獲該地址的網域名稱地址。該子運算式匹配不包括 '^'、 '/' 或 ':' 字元的任何字元序列。第三個附加子運算式捕獲網站連接埠號碼碼,如果指定了該連接埠號碼。該子運算式匹配後跟一個冒號的零或多個數字。最後,第四個附加子運算式捕獲由該 網址指定的路徑以及/或者頁面資訊。該子運算式匹配一個和多個除'#' 或空格之外的字元。
將該Regex應用於上面所示的 URI 後,子匹配包含下述內容:
RegExp.$1 包含 "http"
RegExp.$2 包含 "msdn.microsoft.com"
RegExp.$3 包含 ":80"
RegExp.$4 包含 "/scripting/default.htm"