捕獲組就是把正則表達式中子表達式匹配的內容,保存到內存中以數字編號或顯式命名的組裏,方便後面引用。固然,這種引用既能夠是在正則表達式內部,也能夠是在正則表達式外部。javascript
捕獲組有兩種形式,一種是普通捕獲組,另外一種是命名捕獲組,一般所說的捕獲組指的是普通捕獲組。語法以下:html
普通捕獲組:(Expression)java
命名捕獲組:(?<name>Expression)正則表達式
普通捕獲組在大多數支持正則表達式的語言或工具中都是支持的,而命名捕獲組目前只有.NET、PHP、Python等部分語言支持,聽說Java會在7.0中提供對這一特性的支持。上面給出的命名捕獲組的語法是.NET中的語法,另外在.NET中使用(?’name’Expression)與使用(?<name>Expression)是等價的。在PHP和Python中命名捕獲組語法爲:(?P<name>Expression)。工具
另外須要說明的一點是,除(Expression)和(?<name>Expression)語法外,其它的(?...)語法都不是捕獲組。url
編號規則指的是以數字爲捕獲組進行編號的規則,在普通捕獲組或命名捕獲組單獨出現的正則表達式中,編號規則比較清晰,在普通捕獲組與命名捕獲組混合出現的正則表達式中,捕獲組的編號規則稍顯複雜。spa
在展開討論以前,須要說明的是,編號爲0的捕獲組,指的是正則表達式總體,這一規則在支持捕獲組的語言中,基本上都是適用的。下面對其它編號規則逐一展開討論。.net
若是沒有顯式爲捕獲組命名,即沒有使用命名捕獲組,那麼須要按數字順序來訪問全部捕獲組。在只有普通捕獲組的狀況下,捕獲組的編號是按照「(」出現的順序,從左到右,從1開始進行編號的。code
正則表達式:(\d{4})-(\d{2}-(\d\d))htm
上面的正則表達式能夠用來匹配格式爲yyyy-MM-dd的日期,爲了在下表中得以區分,月和日分別採用了\d{2}和\d\d這兩種寫法。
用以上正則表達式匹配字符串:2008-12-31,匹配結果爲:
編號 |
命名 |
捕獲組 |
匹配內容 |
0 |
(\d{4})-(\d{2}-(\d\d)) |
2008-12-31 |
|
1 |
(\d{4}) |
2008 |
|
2 |
|
(\d{2}-(\d\d)) |
12-31 |
3 |
|
(\d\d) |
31 |
命名捕獲組經過顯式命名,能夠經過組名方便的訪問到指定的組,而不須要去一個個的數編號,同時避免了在正則表達式擴展過程當中,捕獲組的增長或減小對引用結果致使的不可控。
不過容易忽略的是,命名捕獲組也參與了編號的,在只有命名捕獲組的狀況下,捕獲組的編號也是按照「(」出現的順序,從左到右,從1開始進行編號的。
正則表達式:(?<year>\d{4})-(?<date>\d{2}-(?<day>\d\d))
用以上正則表達式匹配字符串:2008-12-31
匹配結果爲:
編號 |
命名 |
捕獲組 |
匹配內容 |
0 |
(?<year>\d{4})-(?<date>\d{2}-(?<day>\d\d)) |
2008-12-31 |
|
1 |
year |
(?<year>\d{4}) |
2008 |
2 |
date |
(?<date>\d{2}-(?<day>\d\d)) |
12-31 |
3 |
day |
(?<day>\d\d) |
31 |
當一個正則表達式中,普通捕獲組與命名捕獲組混合出現時,捕獲組的編號規則稍顯複雜。對於其中的命名捕獲組,隨時均可以經過組名進行訪問,而對於普通捕獲組,則只能經過肯定其編號後進行訪問。
混合方式的捕獲組編號,首先按照普通捕獲組中「(」出現的前後順序,從左到右,從1開始進行編號,當普通捕獲組編號完成後,再按命名捕獲組中「(」出現的前後順序,從左到右,接着普通捕獲組的編號值繼續進行編號。
也就是先忽略命名捕獲組,對普通捕獲組進行編號,當普通捕獲組完成編號後,再對命名捕獲組進行編號。
正則表達式:(\d{4})-(?<date>\d{2}-(\d\d))
用以上正則表達式匹配字符串:2008-12-31,匹配結果爲:
編號 |
命名 |
捕獲組 |
匹配內容 |
0 |
(\d{4})-(?<date>\d{2}-(\d\d)) |
2008-12-31 |
|
1 |
(\d{4}) |
2008 |
|
3 |
date |
(?<date>\d{2}-(\d\d)) |
12-31 |
2 |
|
(\d\d) |
31 |
對捕獲組的引用通常有如下幾種:
1) 正則表達式中,對前面捕獲組捕獲的內容進行引用,稱爲反向引用;
2) 正則表達式中,(?(name)yes|no)的條件判斷結構;
3) 在程序中,對捕獲組捕獲內容的引用。
捕獲組捕獲到的內容,不只能夠在正則表達式外部經過程序進行引用,也能夠在正則表達式內部進行引用,這種引用方式就是反向引用。
反向引用的做用一般是用來查找或限定重複,限定指定標識配對出現等等。
對於普通捕獲組和命名捕獲組的引用,語法以下:
普通捕獲組反向引用:\k<number>,一般簡寫爲\number
命名捕獲組反向引用:\k<name>或者\k'name'
普通捕獲組反向引用中number是十進制的數字,即捕獲組的編號;命名捕獲組反向引用中的name爲命名捕獲組的組名。
反向引用涉及到的內容比較多,後續單獨說明。
條件判斷結構在平衡組中談到過,基本應用和擴展應用均可以在其中找到例子,這裏再也不贅述,請參考.NET正則基礎之——平衡組。
根據語言的不一樣,程序中對捕獲組引用的方式也有所不一樣,下面就JavaScript和.NET進行舉例說明。
因爲JavaScript中不支持命名捕獲組,因此對於捕獲組的引用就只支持普通捕獲組的反向引用和$number方式的引用。程序中的引用通常在替換和匹配時使用。
注:如下應用舉例僅考慮簡單應用場景,對於<a href="javascript:document.write('<b>hello</b>')"/>這種複雜場景暫不考慮。
1) 在Replace中引用,一般是經過$number方式引用。
舉例:替換掉html標籤中的屬性。
<textareaid="result"rows="10"cols="100"></textarea> <scripttype="text/javascript"> var data = "<table id=\"test\"><tr class=\"light\"><td> test </td></tr></table>"; var reg = /<([a-z]+)[^>]*>/ig; document.getElementById("result").value = data.replace(reg, "<$1>"); </script> //輸出 <table><tr><td> test </td></tr></table>
2) 在匹配時的引用,一般經過RegExp.$number方式引用。
舉例:同時獲取<img…>中的src和name屬性值,屬性的順序不固定。參考一條正則能不能同時取出一個img標記的src和name?
<textarea id="result" rows="10" cols="100"></textarea> <script type="text/javascript"> var data = [' <img alt="" border="0" name="g6-o44-1" onload="DrawImage" src="/bmp/foo1.jpg" />', ' <img src="/bmp/foo2.jpg" alt="" border="0" name="g6-o44-2" onload="DrawImage" />'] ; var reg = /<img\b(?=(?:(?!name=).)*name=(['"]?)([^'"\s>]+)\1)(?:(?!src=).)*src=(['"]?)([^'"\s>]+)\3[^>]*>/i; for(var i=0;i<data.length;i++) { var s = data[i]; document.getElementById("result").value += "源字符串:" + s + "\n"; document.write("<br />"); if(reg.test(s)) { document.getElementById("result").value += "name: " + RegExp.$2 + "\n"; document.getElementById("result").value += "src: " + RegExp.$4 + "\n"; } } </script>
因爲.NET支持命名捕獲組,因此在.NET中的引用方式會多一些。一般也是在兩種場景下應用,一是替換,一是匹配。
1) 替換中的引用
普通捕獲組:$number
命名捕獲組:${name}
替換中應用,還是上面的例子。
舉例:替換掉html標籤中的屬性。使用普通捕獲組。
string data ="<table id=\"test\"><tr class=\"light\"><td> test </td></tr></table>"; richTextBox2.Text = Regex.Replace(data, @"(?i)<([a-z]+)[^>]*>", "<$1>"); //輸出 <table><tr><td> test </td></tr></table>
使用命名捕獲組。
string data ="<table id=\"test\"><tr class=\"light\"><td> test </td></tr></table>"; richTextBox2.Text = Regex.Replace(data, @"(?i)<(?<tag>[a-z]+)[^>]*>", "<${tag}>"); //輸出 <table><tr><td> test </td></tr></table>
2) 匹配後的引用
對於匹配結果中捕獲組捕獲內容的引用,能夠經過Groups和Result對象進行引用。
string test = "<a href=\"http://www.csdn.net\">CSDN</a>"; Regex reg = new Regex(@"(?is)<a(?:(?!href=).)*href=(['""]?)(?<url>[^""'\s>]*)\1[^>]*>(?<text>(?:(?!</a>).)*)</a>"); MatchCollection mc = reg.Matches(test); foreach (Match m in mc) { richTextBox2.Text += "m.Value".PadRight(25) + m.Value + "\n"; richTextBox2.Text += "m.Result(\"$0\") : ".PadRight(25) + m.Result("$0") + "\n"; richTextBox2.Text += "m.Groups[0].Value : ".PadRight(25) +m.Groups[0].Value + "\n"; richTextBox2.Text += "m.Result(\"$2\") : ".PadRight(25) + m.Result("$2") + "\n"; richTextBox2.Text += "m.Groups[2].Value : ".PadRight(25) + m.Groups[2].Value + "\n"; richTextBox2.Text += "m.Result(\"${url}\") : ".PadRight(25) + m.Result("${url}") + "\n"; richTextBox2.Text += "m.Groups[\"url\"].Value : ".PadRight(25) + m.Groups["url"].Value + "\n"; richTextBox2.Text += "m.Result(\"$3\") : ".PadRight(25) + m.Result("$3") + "\n"; richTextBox2.Text += "m.Groups[3].Value : ".PadRight(25) + m.Groups[3].Value + "\n"; richTextBox2.Text += "m.Result(\"${text}\") : ".PadRight(25) + m.Result("${text}") + "\n"; richTextBox2.Text += "m.Groups[\"text\"].Value : ".PadRight(25) + m.Groups["text"].Value + "\n"; } //輸出 m.Value : <a href="http://www.csdn.net">CSDN</a> m.Result("$0") : <a href="http://www.csdn.net">CSDN</a> m.Groups[0].Value : <a href="http://www.csdn.net">CSDN</a> m.Result("$2") : http://www.csdn.net m.Groups[2].Value : http://www.csdn.net m.Result("${url}") : http://www.csdn.net m.Groups["url"].Value : http://www.csdn.net m.Result("$3") : CSDN m.Groups[3].Value : CSDN m.Result("${text}") : CSDN m.Groups["text"].Value : CSDN
對於捕獲組0的引用,能夠簡寫做m.Value。
下面舉一個項目中遇到的正則匹配:
Pattern p = Pattern.compile("\\(((((\\d*(\\.\\d+)?)\\×\\(\\d\\+\\d*(\\.\\d+)*%*\\)\\×(\\d*(\\.\\d+)?))?)\\+(((\\d*(\\.\\d+)?)\\×(\\d*(\\.\\d+)?))?)(\\+(\\d*(\\.\\d+)?))?)\\)(\\×(\\d*(\\.\\d+)?)\\×(\\d*(\\.\\d+)?))"); String s = "(3.52×(1+1.5%)×65+2.2×1+0)×1.01×1"; Matcher m = p.matcher(s); while (m.find()) { int count = m.groupCount(); for(int i = 0;i<=count;i++){ System.out.print(i+" "); System.out.println(m.group(i)); } } //輸出爲: 0 (3.52×(1+1.5%)×65+2.2×1+0)×1.01×1 1 3.52×(1+1.5%)×65+2.2×1+0 2 3.52×(1+1.5%)×65 3 3.52×(1+1.5%)×65 4 3.52 5 .52 6 .5 7 65 8 null 9 2.2×1 10 2.2×1 11 2.2 12 .2 13 1 14 null 15 +0 16 0 17 null 18 ×1.01×1 19 1.01 20 .01 21 1 22 null
解釋:
(0)、group(0)表示匹配正則表達式的整個字符串,由於字符串s恰好只匹配一次正則表達式,因此顯示爲(3.52×(1+1.5%)×65+2.2×1+0)×1.01×1
(1)、正則表達式中的\\(不計算在組裏面的,因此group(1)表示從左邊起第二個(裏面的內容(由於第一個是\\(,因此不在組計數的裏面),因此爲3.52×(1+1.5%)×65+2.2×1+0,
\\(表示非正則表達式中的組,而是匹配字符串中自己就攜帶了(號,在計算分組下標的時候,只計算匹配規則中的組括號(,而不計算\\(。
(2)、2,3,4,同理
(5)、匹配爲(\\.\\d+),對應匹配字符串中的.52
完整的組下標爲: