1 概述
一般的正則替換,只能對匹配的子串做簡單的處理,且只能是做string類型的處理之後,作爲替換文本替換匹配子串,可以實現的功能比較有限。.NET的Replace()方法中的replacement參數,不僅可以是字符串,還可以是委託(delegate),在每次匹配成功時,都會調用委託方法,對匹配的子串進行處理之後,再作爲替換文本返回,匹配子串使用委託方法,可以做任意複雜的處理,因此這種替換功能非常強大。
委託的類型可以是MatchEvaluator,也可以是匿名方法,在每次匹配成功時調用。委託方法傳入參數是Match對象,返回類型是string,即正則表達式在每次匹配成功時,會得到一個Match對象,作爲參數傳給委託方法,做一定處理後,返回替換文本,替換匹配到的子串。
2 委託和匿名方法
在正則替換中使用的委託,一般有兩種方式,顯式聲明的委託和匿名方法。下面以實例說明兩種方式的使用方法。委託和匿名方法的區別和各自的特點不在這裏介紹,請參考相關文獻或文章。
2.1 委託
舉例:
源字符串: a=10, b=20, c=30
需求:將字符串中的數字加100。
//委託方法
private string regReplace(Match m)
{
return (Convert.ToInt32(m.Value) + 100).ToString();
}
//聲明一個MatchEvaluator類型委託
MatchEvaluator me = new MatchEvaluator(regReplace);
//正則替換應用
string test = "a=10, b=20, c=30";
Regex reg = new Regex(@"(?i)(?<=[a-z]=)\d+");
string result = reg.Replace(test, me);
richTextBox2.Text = result;
/*--------輸出--------
a=110, b=120, c=130
*/
2.2 匿名方法
事實上,對於以上這種簡單的需求,不需要顯式的聲明委託,直接使用匿名方法即可,
string test = "a=10, b=20, c=30";
Regex reg = new Regex(@"(?i)(?<=[a-z]=)\d+");
string result = reg.Replace(test, delegate(Match m) { return (Convert.ToInt32(m.Value) + 100).ToString(); });
richTextBox2.Text = result;
/*--------輸出--------
a=110, b=120, c=130
*/
3 正則中委託的典型應用場景
一個涉及到替換的需求,首先要進行分析,是否能夠通過一個正則表達式進行直接替換,如果不可以,那就要藉助委託了。接下來就要找出可在委託方法中進行處理的子串的規律,剩下的就是委託方法中最基本的字符串處理了。
正則中委託的典型應用場景一般可歸納爲以下幾種:
1、替換子串需進行非string類型的處理,如計算等;
2、替換子串需經過條件或邏輯判斷來決定處理方式;
3、多種條件組合的替換。
以上分類方式或許有重疊的地方,但是都比較有代表性,所以單獨進行舉例說明。
3.1 非string類型處理
替換子串非string類型處理,最典型的就是以上舉例中的計算。還有比較典型的就是涉及計數的問題。
舉例:
源字符串:<a href="http://www.sina.com.cn/">新浪 </a> <a href="http://www.sohu.com/">搜狐 </a> <a href="http://www.qq.com/">騰訊QQ </a> <a href="http://www.163.com/">網易163 </a>
需求:在每個鏈接後面加編號,結果
<a href="http://www.sina.com.cn/">新浪 </a>01 <a href="http://www.sohu.com/">搜狐 </a>02 <a href="http://www.qq.com/">騰訊QQ </a>03 <a href="http://www.163.com/">網易163 </a>04
代碼實現:
string test = "<a href=\"http://www.sina.com.cn/\">新浪</a><a href=\"http://www.sohu.com/\">搜狐</a><a href=\"http://www.qq.com/\">騰訊QQ</a><a href=\"http://www.163.com/\">網易163</a>";
Regex reg = new Regex(@"(?is)<a[^>]*>(?:(?!</?a\b).)*</a>");
int i = 1;
string result = reg.Replace(test, delegate(Match m) { return m.Value + (i++).ToString("00"); });
richTextBox2.Text = result;
/*--------輸出--------
<a href="http://www.sina.com.cn/">新浪</a>01<a href="http://www.sohu.com/">搜狐</a>02<a href="http://www.qq.com/">騰訊QQ</a>03<a href="http://www.163.com/">網易163</a>04
*/
這個需求是在鏈接後加編號,只要匹配到<a…>…</a>標籤,在後面加上編號即可,但是由於編號是要根據a標籤的個數來計數的,所以是動態變化的,這樣直接替換就做不到了。而正則中的委託,是每次匹配成功後都會調用委託方法,而匹配是從左向右按順序匹配的,所以調用委託方法也是按匹配的先後順序進行調用的,這樣就可以先用正則匹配出a標籤,然後在委託方法中動態進行計數了。
3.2 邏輯判斷
如果待替換的子串,需要根據當前匹配子串的內容,經過判斷後決定如何替換,一般無法直接通過replace()實現,需求在委託方法裏進行判斷。
舉例1:
源字符串:源字符串規律爲“字母=數字”,用“&”相連
a=12&b=34&c=56&d=78
a=98&b=76&d=54
需求:如果源字符串有“c=數字”,就替換爲“c=12”,否則在字符串結尾添加“&c=98”。
代碼實現:
string[] test = new string[]{"a=12&b=34&c=56&d=78", "a=98&b=76&d=54"};
Regex reg = new Regex(@"(?is)(?<=^(?:(?!c=).)*)(?(c=[^&]+)c=[^&]+|$)");
foreach(string s in test)
{
richTextBox2.Text += "字符串: " + s + "\n";
richTextBox2.Text += "替換後: " + reg.Replace(s, delegate(Match m) { return m.Value == "" ? "&c=98" : "c=12"; }) + "\n\n";
}
/*--------輸出--------
字符串: a=12&b=34&c=56&d=78
替換後: a=12&b=34&c=12&d=78
字符串: a=98&b=76&d=54
替換後: a=98&b=76&d=54&c=98
*/
還有一個類似的需求實例。
舉例2(一個可能很簡單的正式表達式求助):
源字符串:要處理的字符有可能是
""(空)
"p=1"
"ID=e2798a59&xx=79d5&bb=4833-9c57&cc=87d46a8&bb=b907a"
"ID=e2798a59&xx=79d5&bb=4833-9c57&cc=87d46a8&bb=b907a&p=2"
"ID=e2798a59&xx=79d5&p=4&bb=4833-9c57&cc=87d46a8&bb=b907a"
需求:對上述任何一種字符串的可能,查找是否有p=x,如果找不到,爲字符串加上"p=0" ,如果找到,還要得到x的值,讓y=x+1之後,再把"p=y"替換之前的p=x。
代碼實現:
string[] test = new string[] { "", "p=1", "ID=e2798a59&xx=79d5&bb=4833-9c57&cc=87d46a8&bb=b907a", "ID=e2798a59&xx=79d5&bb=4833-9c57&cc=87d46a8&bb=b907a&p=2", "ID=e2798a59&xx=79d5&p=4&bb=4833-9c57&cc=87d46a8&bb=b907a" };
foreach (string s in test)
{
richTextBox2.Text += "原始字符串: \t" + s + "\n";
richTextBox2.Text += "替換後字符串: \t" + Regex.Replace(s, @"(?is)p=(?<v>\d+)|(?<!p=\d+.*)$", delegate(Match m) { if (m.Groups["v"].Value != "") return "p=" + (Convert.ToInt32(m.Groups["v"].Value) + 1); return "p=0"; }) + "\n\n";
}
/*--------輸出--------
原始字符串:
替換後字符串: p=0
原始字符串: p=1
替換後字符串: p=2
原始字符串: ID=e2798a59&xx=79d5&bb=4833-9c57&cc=87d46a8&bb=b907a
替換後字符串: ID=e2798a59&xx=79d5&bb=4833-9c57&cc=87d46a8&bb=b907ap=0
原始字符串: ID=e2798a59&xx=79d5&bb=4833-9c57&cc=87d46a8&bb=b907a&p=2
替換後字符串: ID=e2798a59&xx=79d5&bb=4833-9c57&cc=87d46a8&bb=b907a&p=3
原始字符串: ID=e2798a59&xx=79d5&p=4&bb=4833-9c57&cc=87d46a8&bb=b907a
替換後字符串: ID=e2798a59&xx=79d5&p=5&bb=4833-9c57&cc=87d46a8&bb=b907a
*/
這個需求中,既涉及到了對替換子串的邏輯判斷,又涉及到了數字運算,直接替換做不到,所以要考慮使用委託。先通過正則匹本出p=x,再在委託方法中進行邏輯判斷和運算。
3.3 多條件組合替換
當需求中的條件多於一個時,可能無法在一個正則表達式中進行判斷,或者即使能夠在一個正則表達式中判斷,由於正則表達式非常複雜,會降低匹配效率,所以還是要在委託方法中進行替換。
舉例1:
源字符串:第一個測試...<a href=\"www.test.com\">又一個測試</a>...第三個測試...<a href=\"www.test.com\" title=\"測試\" >第幾個測試了?</a>...這是最後一個測試了...
需求:爲字符串中的“測試”加鏈接,已有鏈接的不加。
這個需求,首先是要進行替換,但又加了一個附加條件,已有鏈接的不替換,這樣如果在一個正則表達式中實現,正則太複雜,不但降低匹配效率,擴展起來也很困難,可讀性也差,所以還是用正則委託來實現比較好。
先分析一下需求,在<a…>…</a>標籤內的關鍵字不進行替換,那換個角度,只要先找出a標籤外的字符串,對關鍵字進行替換就可以滿足需求了。所以就是寫正則,匹配出a標籤外的子串,在委託方法中對關鍵字加鏈接,再替換回原字符串就可以了。
代碼實現:
string test = "第一個測試...<a href=\"www.test.com\">又一個測試</a>...第三個測試...<a href=\"www.test.com\" title=\"測試\" >第幾個測試了?</a>...這是最後一個測試了...";
Regex reg = new Regex(@"(?is)^((?!</?a).)+|</a>((?!</?a).)+");
string result = reg.Replace(test, delegate(Match m) { return m.Value.Replace("測試", "<a href=\"www.test.com\">測試</a>"); });
richTextBox2.Text = result;
/*--------輸出--------
第一個<a href="www.test.com">測試</a>...<a href="www.test.com">又一個測試</a>...第三個<a href="www.test.com">測試</a>...<a href="www.test.com" title="測試" >第幾個測試了?</a>...這是最後一個<a href="www.test.com">測試</a>了...
*/
當然,這個例子並不嚴謹,因爲其它標籤中也可能出現關鍵字,而這些關鍵通常也是不應該被替換的,這時也可以在委託方法中進行判斷,以確定是否應該被替換。
舉例2(正則去除不包含特定字符串的A標籤~):
源字符串:<a href=www.abc.com>abc </a>啊啊啊 <a href=bcd.com>abc </a>啊啊啊 <a href="www.abc.com" class="t1">abc </a>啊啊啊 <a href=def.com>abc </a>啊啊啊 <a href=efg.com>abc </a>
需求:把鏈接中不包含“abc”的超鏈接過濾掉。
這個需求,實際上也是兩個條件,首先是要做替換,然後附加了一個條件,鏈接中不包含“abc”的替換。類似於這種符合某一規律的子串,部分替換,部分保留的情況,通常比較適合用正則委託來解決。
當然,這個需求還是可以直接通過一個正則表達式來處理的,先看一下這種處理方式的代碼。
string test = "<a href=www.abc.com>abc </a>啊啊啊 <a href=bcd.com>abc </a>啊啊啊 <a href=\"www.abc.com\" class=\"t1\">abc </a>啊啊啊 <a href=def.com>abc </a>啊啊啊 <a href=efg.com>abc </a> ";
Regex reg = new Regex(@"(?is)<a(?:(?!href=).)*href=(['""]?)(?:(?!abc|['""\s>]).)+\1(?:\s[^>]*)?>((?:(?!</?a\b).)*)</a>");
string result = reg.Replace(test, "$2");
richTextBox2.Text = result;
/*--------輸出--------
<a href=www.abc.com>abc </a>啊啊啊 abc 啊啊啊 <a href="www.abc.com" class="t1">abc </a>啊啊啊 abc 啊啊啊 abc
*/
可以看到,這種處理方式,是先進行判斷,再進行匹配。在正則表達式中,對鏈接子串的每一個字符用“(?!abc|['""\s>]).”進行判斷,所以有多少個字符,就要判斷多少次,在這種情況下,通常需要使用“|”來對不同的條件取“或”,而“|”的效率一般是比較低的。
另一種處理方式,是先把鏈接匹配出來,然後在委託方法中進行判斷,以決定是否替換。
代碼實現:
string test = "<a href=www.abc.com>abc </a>啊啊啊 <a href=bcd.com>abc </a>啊啊啊 <a href=\"www.abc.com\" class=\"t1\">abc </a>啊啊啊 <a href=def.com>abc </a>啊啊啊 <a href=efg.com>abc </a> ";
Regex reg = new Regex(@"(?is)<a(?:(?!href=).)*href=(['""]?)([^'""\s>]+)\1[^>]*>((?:(?!</?a\b).)*)</a>");
string result = reg.Replace(test, delegate(Match m) { if (m.Groups[2].Value.IndexOf("abc") > -1) return m.Value; return m.Groups[3].Value; });
richTextBox2.Text = result;
/*--------輸出--------
<a href=www.abc.com>abc </a>啊啊啊 abc 啊啊啊 <a href="www.abc.com" class="t1">abc </a>啊啊啊 abc 啊啊啊 abc
*/
這種處理方式,是先進行匹配,再進行判斷。先通過正則把每一個鏈接都匹配出來,作爲參數傳給委託方法,在委託方法中判斷是否包含“abc”,以決定是否替換。這種方式因爲匹配過程中不需要進行判斷,所以匹配的速度是很快的,然後在委託方法中只執行一次判斷即可。兩種處理方式的效率,在字符較少時區別不大,在字符較多,調用較頻繁的情況下,還是委託方法的效率比較高。
類似於這種需求,在效率、可讀性、可擴展性等方面綜合考慮,還是使用委託方法要好一些。