.NET 正則之委託

 

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”,以決定是否替換。這種方式因爲匹配過程中不需要進行判斷,所以匹配的速度是很快的,然後在委託方法中只執行一次判斷即可。兩種處理方式的效率,在字符較少時區別不大,在字符較多,調用較頻繁的情況下,還是委託方法的效率比較高。

類似於這種需求,在效率、可讀性、可擴展性等方面綜合考慮,還是使用委託方法要好一些。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章