.NET中正則使用

原文:http://www.cnblogs.com/thinhunan/archive/2006/02/28/RegexExpressionSyntax1.html

前言  


因爲
.net的基本正則語法和Perl5基本相同,所以基本語法你可以去下載一下M$JS幫助文檔,上面有詳細的說明/d表示什麼,{,5}表示什麼,/[表示什麼……,這裏我只想提醒大家一點,爲了避免和反向引用相沖突,在你用/nn表示八進制的ASCII碼時,請在/後加0就是說,/40在表示ASCII碼時,請這樣寫/040

替換

Regex類有一個靜態的Replace方法,其實例也有一個Replace方法,這個方法很強大,因爲它可以傳入一個delegate,這樣,你可以自定義每次捕獲匹配時,如何處理捕獲的內容。 

        public static void Main()
        
{    
            
string s = "1 12 3 5";
            s 
= Regex.Replace(s,@"/d+",new MatchEvaluator(CorrectString),RegexOptions.Compiled|RegexOptions.IgnoreCase);
            Console.WriteLine(s);
            Console.ReadLine();
        }

        
private static string CorrectString(Match match)
        
{
            
string matchValue = match.Value;
            
if(matchValue.Length == 1)
                matchValue 
= "0" + matchValue;
            
return matchValue;
        }

以上這段代碼說明了如果使用delegate MatchEvaluator 來處理正則的Match結果,該代碼返回"01 12 03 05"Replace方法除了使用delegate來處理捕獲的Match,還可以用字符串來替換Match的結果,而用字符串來替換Match結果除了把Match結果靜態的替換成一個固定的文本外,還可以使用以下語法來更方便的實現你需要的功能:

$number

把匹配的第number組替換成替換表達式,還有這句話怎麼寫也表達不清楚意思,還是來個例子吧:

 

 

 

        public static void Main()
        
{    
            
string s = "1 12 3 5";
            s 
= Regex.Replace(s,@"(/d+)(?#這個是註釋)","0$1",RegexOptions.Compiled|RegexOptions.IgnoreCase);
            Console.WriteLine(s);
            Console.ReadLine();
        }

這段代碼返回的是 01 012 03 05

 

 

 

就是說,對組一的每個匹配結果都用"0$1"這個表達式來替換,"0$1""$1"由組1匹配的結果代入

${name}

把匹配的組名爲"name"的組替換成表達式,

上例的Regex expression改成@"(?<name>/d+)(?#這個是註釋)"後面的替換式改爲"0${name}"結果是一樣的

$$  

$的轉義符,如上例表達式改成@"(?<name>/d+)(?#這個是註釋)""$$${name}",則結果爲"$1 $12 $3 $5"

$&

替換整個匹配

$`

替換匹配前的字符

$'

替換匹配後的字符

$+

替換最後匹配的組

$_

替換整個字符串

後面的選項,大家自己寫個例子體味一下。

*,上例中的(?#這個是註釋)說明了正則的內聯註釋語法爲(?#)


表達項選項

正則表達式選項RegexOptions有如下一下選項,詳細說明請參考聯機幫助

RegexOptions枚舉值

內聯標誌

簡單說明

ExplicitCapture

n

只有定義了命名或編號的組才捕獲

IgnoreCase

i

不區分大小寫

IgnorePatternWhitespace

x

消除模式中的非轉義空白並啓用由 # 標記的註釋。

MultiLine

m

多行模式,其原理是修改了^$的含義

SingleLine

s

單行模式,和MultiLine相對應

這裏我提到內聯標誌,是因爲相對於用RegexOptionsnew Regex時定義Regex表達式的全局選項來說,內聯標誌可以更小粒度(以組爲單位)的定義匹配選項,從而更方便表達我們的思想

語法是這樣的:(?i:expression)爲定義一個選項,(?-i:expression)爲刪除一個選項,(?i-s:expression)則定義i,刪除s,是的,我們可以一次定義很多個選項。這樣,通過內聯選項,你就可以在一個Regex中定義一個組爲匹分大小寫的,一個組不匹分大小寫的,是不是很方便呢?

正則表達式中的組是很重要的一個概念,它是我們通向高級正則應用的的橋樑

組的概念

一個正則表達式匹配結果可以分成多個部分,這就是組(Group)的目的。能夠靈活的使用組後,你會發現Regex真是很方便,也很強大。

先舉個例子  

 

 

在這個例子中,我把一次Match結果用(?<name>)的方式分成三個組"y","m","d"分別代表年、月、日。

現在我們已經有了組的概念了,再來看如何分組,很簡單的,除了上在的辦法,我們可以用一對括號就定義出一個組,比如上例可以改成  

 

 

通過以上三例,我們知道了給Regex定義Group的三種辦法以及相應的引用組匹配結果的方式。

然後,關於組定義,還有兩點請注意:
1、因爲括號用於定義組了,所以如果要匹配"("")",請使用"/(""/)"(關於所有特殊字符的定義,請查看相關Regex expression幫助文檔)
2、如果定義Regex時,使用了ExplicitCapture選項,則第二個例子不會成功,因爲此選項要求顯式定義了編號或名字的組才捕獲並保存結果,如果你沒有定義ExplicitCapture選項,而有時又定義了類式於(A|B)這樣的部分在表達式,而這個(A|B)你又並不想捕獲結果,那麼可以使用“不捕獲的組”語法,即定義成(?:)的方式,針對於(A|B),你可以這樣來定義以達到不捕獲並保存它到Group集合中的目的--(?:A|B) 

上面內容僅討論了一般的組,組還有很多的花樣,很多高級的功能,下一篇將試圖帶您體驗一番其中洞天。

        public static void Main()
        
{    
            
string s = "2005-2-21";
            Regex reg 
= new Regex(@"(/d{4})-(/d{1,2})-(/d{1,2})",RegexOptions.Compiled);
            Match match 
= reg.Match(s);
            
int year =  int.Parse(match.Groups[1].Value);
            
int month = int.Parse(match.Groups[2].Value);
            
int day = int .Parse(match.Groups[3].Value);
            DateTime time 
= new DateTime(year,month,day);
            Console.WriteLine(time);
            Console.ReadLine();
        }


從上例可以看出,第一個括號對包涵的組被自動編號爲1,後面的括號依次編號爲2、3…… 

 

 

        public static void Main()
        
{    
            
string s = "2005-2-21";
            Regex reg 
= new Regex(@"(?<2>/d{4})-(?<1>/d{1,2})-(?<3>/d{1,2})",RegexOptions.Compiled);
            Match match 
= reg.Match(s);
            
int year =  int.Parse(match.Groups[2].Value);
            
int month = int.Parse(match.Groups[1].Value);
            
int day = int .Parse(match.Groups[3].Value);
            DateTime time 
= new DateTime(year,month,day);
            Console.WriteLine(time);
            Console.ReadLine();
        }

再看上例,我們用(?<數字>)的方式手工給每個括號對的組編號,(注意我定義12的位置時不是從左到右定義的)

        public static void Main()
        
{    
            
string s = "2005-2-21";
            Regex reg 
= new Regex(@"(?<y>/d{4})-(?<m>/d{1,2})-(?<d>/d{1,2})",RegexOptions.Compiled);
            Match match 
= reg.Match(s);
            
int year =  int.Parse(match.Groups["y"].Value);
            
int month = int.Parse(match.Groups["m"].Value);
            
int day = int .Parse(match.Groups["d"].Value);
            DateTime time 
= new DateTime(year,month,day);
            Console.WriteLine(time);
            Console.ReadLine();
        }

以上的例子通過組來實現分析一個字符串,並把其轉化爲一個DateTime實例,當然,這個功能用DateTime.Parse方法就能很方便的實現。

反向引用

反向引用,指把匹配出來的組引用到表達式本身其它地方,比如,在匹配HTML的標記時,我們匹配出一個<a>,我們要把匹配出來的a引用出來,用來找到</a>,這個時候就要用到反向引用。
語法
    a、反向引用編號的組,語法爲/number
    b、反向引用命名的組,語法爲/k<name>
舉例
    a、匹配成對的HTML標籤

 

輔助匹配組

以下幾種組結構,括號中的Pattern都不作爲匹配結果的一部分進行保存
    1、正聲明(?=)
    涵義:括號中的模式必須出現在聲明右側,但不作爲匹配的一部分

 

 

非回溯匹配
語法:(?>)
涵義:該組匹配後,其匹配的字符不能通過回溯用於後面的表達式的匹配。呵呵,光看這句話肯定搞不懂,我當初爲了搞懂這個也花了不少的時間,還是通過實例來說明吧:
"www.csdn.net" 可以通過@"/w+/.(.*)/./w+"來匹配,卻不能通過@"/w+/.(?>.*)/./w+"來匹配!爲什麼呢?

原因是正則匹配是貪婪的,匹配時它會儘可能多的匹配最多的結果,所以,上例兩個正則式中的.*都會把csdn.net匹配完, 這個時候,第一個表達式在開始匹配時發現/./w+沒得字符給它匹配了,所以它會進行回溯,所謂回溯,就是把.*匹配的結果往回推,回推留出來的字符再用來匹配/./w+,直到/./w+匹配成功,整個表達式返回成功的匹配結果。而第二個表達式,因使用的是非回溯匹配,所以,.*匹配完後,不允許通過回溯來匹配/./w+,所以整個表達式匹配失敗。

請注意,回溯匹配是很浪費資源的一種匹配方式,所以,請儘量避免您的正則式要通過回溯來成功匹配,如上例,可以換成@"/w+/.([^/.]+/.)+/w+"+"

        public static void Main()
        

            
string newsContent = @"url:<a href=""1.html""><img src=""1.gif"">test<span style=""color:red;"">Regex</span></a>.";
            Regex regEnd 
= new Regex(@"</s*a[^>]*>([^<]|<(?!/a))*</s*/a/s*>",RegexOptions.Multiline);
            
            Console.WriteLine(regEnd.Match(newsContent).Value);
//Result: <a href="1.html"><img src="1.gif">test<span style="color:red;">Regex</span></a>
            Console.ReadLine();
        }

    3、反向正聲明(?<=) 
    涵義:括號中的模式必須出現在聲明左側,但不作爲匹配的一部分
    4、反向負聲明(?<!)
    涵義:括號中的模式必須不出現在聲明左側

        public static void Main()
        
{    
            
string s = "C#.net,VB.net,PHP,Java,JScript.net";
            Regex reg 
= new Regex(@"[/w/#]+(?=/.net)",RegexOptions.Compiled);
            MatchCollection mc 
= reg.Matches(s);
            
foreach(Match m in mc)
                Console.WriteLine(m.Value); 
            Console.ReadLine();
            
//輸出 C# VB JScript
        }

    可以看到匹配引擎要求匹配.net,但卻不把.net放到匹配結果中
    2、負聲明(?!)
     涵義:括號中的模式必須不出現在聲明右側
        下例演示如何取得一個<a>標籤對中的全部內容,即使其中包含別的HTML tag

Lazy匹配
語法:??,*?,+?,{n}?,{n,m}?
涵義:簡單說,後面的這個?(lazy)告訴正則引擎,它前面的表達式匹配到最短的匹配項就不用匹配下去了,如??,?本身匹配0-1個匹配項,那麼??就取最短的,匹配0個項就不匹配下去了,同理,*?匹配0個,+?匹配1個,{n}?匹配n個,{n,m}?匹配n個。當用@”/w*?”匹配”abcd”時,會有五次成功匹配,每次都匹配的結果都是空字符串爲什麼會是5次呢,這是因爲正則引擎在匹配一個表達式時是一個字符一個字符對比下去的,每成功匹配一次,就前進一下。
判斷表達式
語法:
    1、A|B,這個是最基本的,A或者B,其實這個不能算判斷
    2、(?(expression)yes-expression|no-expression),其中no-expression爲可選項,意爲,如果expression成立,則要求匹配yes-expression,否則要求匹配no-expression
    3、(?(group-name)yes-expressioin|no-expression),其中no-expression爲可選項,意爲,如果名爲group-name的組匹配成功,則要求匹配yes-expression,否則要求匹配no-expression
    判斷表達式還是很好理解的,唯有一點要注意:@"(?(A)A|B)"不能匹配"AA",爲什麼呢?要怎麼樣寫才能匹配呢,大家先想想……
我們應該這樣寫Regex: @”(?(A)AA|B)”,請注意,判斷式中的內容並不會做爲yes-expression或no-expression表達式的一部分。 
.net 的正則引擎工作特點
    .net的正則引擎工作方式大多數和我們“想當然”的方式一樣,只是有幾點要注意:
    1、.NET Framework 正則表達式引擎儘可能的匹配多的字符(貪婪)。正是由於這一點,所以,不要用@"<.*>(.*)</.*>"這樣的正則式來試圖找出一個HTML文檔中的所有innerText。(我也正是在網上看到有人這樣寫正則式才決定要寫《正則表達式 高級技巧》的,呵呵)
2、.NET Framework 正則表達式引擎是回溯的正則表達式匹配器,它併入了傳統的非確定性有限自動機 (NFA) 引擎(例如 Perl、Python使用的引擎)。這使其有別於更快的、但功能更有限的純正則表達式確定性有限自動機 (DFA) 引擎。.NET Framework 正則表達式引擎儘量匹配成功,所以,當@"/w+/.(.*)/./w+"中的.*把www. .csdn.net中的.csdn.net都匹配完了,讓後面的/./w+沒得字符去匹配時,引擎會進行回溯,以得到成功的匹配。
NET Framework 正則表達式引擎還包括了一組完整的語法,讓程序員能夠操縱回溯引擎。包括:
“惰性”限定符:??、*?、+?、{n,m}?。這些惰性限定符指示回溯引擎首先搜索最少數目的重複。與之相反,普通的“貪婪的”限定符首先嚐試匹配最大數目的重複。
從右到左匹配。這在從右到左而非從左到右搜索的情況下十分有用,或者在從模式的右側部分開始搜索比從模式的左側部分開始搜索更爲有效的情況下十分有用。
3、.NET Framework 正則表達式引擎在(expression1|expression2|expression3)這樣情況下,expression1總是最先得到嘗試,再依次是expression2和expression3           

 

        public static void Main()
        
{    
            
string s = "THIN is a asp.net developer.";
            Regex reg 
= new Regex(@"(/w{2}|/w{3}|/w{4})",RegexOptions.Compiled|RegexOptions.IgnoreCase);
            MatchCollection mc 
= reg.Matches(s);
            
foreach(Match m in mc)
                Console.WriteLine(m.Value); 
            Console.ReadLine();
    }

 

輸出結果是: ‘TH’ ‘IN’ ‘is’ ‘as’ ‘ne’ ‘de’ ‘ve’ ‘lo’ ‘pe’

附表

轉義符
說明
一般字符
.$ ^ { [ ( | ) * + ? / 外,其他字符與自身匹配。
/a
與響鈴(警報)/u0007 匹配。
/b
在正則表達式中,/b表示單詞邊界(在 /w /W 之間),不過,在 [] 字符類中,/b表示退格符。在替換模式中,/b始終表示退格符。
/t
Tab /u0009 匹配。
/r
與回車符 /u000D 匹配。
/v
與垂直 Tab /u000B 匹配。
/f
與換頁符 /u000C 匹配。
/n
與換行符 /u000A 匹配。
/e
Esc /u001B 匹配。
/040
ASCII 字符匹配爲八進制數(最多三位);如果沒有前導零的數字只有一位數或者與捕獲組號相對應,則該數字爲後向引用。例如,字符 /040表示空格。
/x20
使用十六進制表示形式(恰好兩位)與 ASCII 字符匹配。
/cC
ASCII 控制字符匹配;例如,/cC Ctrl-C
/u0020
使用十六進制表示形式(恰好四位)與 Unicode 字符匹配。
/
在後面帶有不識別爲轉義符的字符時,與該字符匹配。例如,/* /x2A 相同。
字符類
說明
.
匹配除 /n 以外的任何字符。如果已用 Singleline 選項做過修改,則句點字符可與任何字符匹配。
[ aeiou ]
與指定字符集中包含的任何單個字符匹配。
[^ aeiou ]
與不在指定字符集中的任何單個字符匹配。
[0-9a-fA-F]
使用連字號 (–) 允許指定連續字符範圍。
/p{ name }
{name} 指定的命名字符類中的任何字符都匹配。支持的名稱爲 Unicode 組和塊範圍。例如,LlNdZIsGreekIsBoxDrawing。可以使用GetUnicodeCategory方法找到某個字符所屬的 Unicode 類別。
/P{ name }
與在 {name} 中指定的組和塊範圍不包括的文本匹配。
/w
與任何單詞字符匹配。等效於 Unicode 字符類別 [/p{Ll}/p{Lu}/p{Lt}/p{Lo}/p{Nd}/p{Pc}/p{Lm}]。如果用 ECMAScript 選項指定了符合 ECMAScript 的行爲,則 /w 等效於 [a-zA-Z_0-9]
/W
與任何非單詞字符匹配。等效於 Unicode 字符類別 [^/p{Ll}/p{Lu}/p{Lt}/p{Lo}/p{Nd}/p{Pc}/p{Lm}]。如果用 ECMAScript 選項指定了符合 ECMAScript 的行爲,則 /W 等效於 [^a-zA-Z_0-9]
/s
與任何空白字符匹配。等效於 Unicode 字符類別 [/f/n/r/t/v/x85/p{Z}]。如果用 ECMAScript 選項指定了符合 ECMAScript 的行爲,則 /s 等效於 [ /f/n/r/t/v]
/S
與任何非空白字符匹配。等效於 Unicode 字符類別 [^/f/n/r/t/v/x85/p{Z}]。如果用 ECMAScript 選項指定了符合 ECMAScript 的行爲,則 /S 等效於 [^ /f/n/r/t/v]
/d
與任何十進制數字匹配。對於 Unicode 類別的 ECMAScript 行爲,等效於 /p{Nd},對於非 Unicode 類別的 ECMAScript 行爲,等效於 [0-9]
/D
與任何非數字匹配。對於 Unicode 類別的 ECMAScript 行爲,等效於 /P{Nd},對於非 Unicode 類別的 ECMAScript 行爲,等效於 [^0-9]
斷言
說明
^
指定匹配必須出現在字符串的開頭或行的開頭。。
$
指定匹配必須出現在以下位置:字符串結尾、字符串結尾處的 /n 之前或行的結尾。
/A
指定匹配必須出現在字符串的開頭(忽略 Multiline 選項)。
/Z
指定匹配必須出現在字符串的結尾或字符串結尾處的 /n 之前(忽略 Multiline 選項)。
/z
指定匹配必須出現在字符串的結尾(忽略 Multiline 選項)。
/G
指定匹配必須出現在上一個匹配結束的地方。與 Match.NextMatch() 一起使用時,此斷言確保所有匹配都是連續的。
/b
指定匹配必須出現在 /w(字母數字)和 /W(非字母數字)字符之間的邊界上。匹配必須出現在單詞邊界上,即出現在由任何非字母數字字符分隔的單詞中第一個或最後一個字符上。
/B
指定匹配不得出現在 /b 邊界上。
限定符
說明
*
指定零個或更多個匹配;例如 /w* (abc)*。等效於 {0,}
+
指定一個或多個匹配;例如 /w+ (abc)+。等效於 {1,}
?
指定零個或一個匹配;例如 /w? (abc)?。等效於 {0,1}
{ n }
指定恰好 n 個匹配;例如 (pizza){2}
{ n ,}
指定至少 n 個匹配;例如 (abc){2,}
{ n , m }
指定至少 n 個但不多於 m 個匹配。
*?
指定儘可能少地使用重複的第一個匹配(等效於 lazy *)。
+?
指定儘可能少地使用重複但至少使用一次(等效於 lazy +)。
??
指定使用零次重複(如有可能)或一次重複 (lazy ?)
{ n }?
等效於 {n} (lazy {n})
{ n ,}?
指定儘可能少地使用重複但至少使用 n (lazy {n,})
{ n , m }?
指定介於 n 次和 m 次之間、儘可能少地使用重複 (lazy {n,m})


@"<(?<tag>[^/s>]+)[^>]*>.*<//k<tag>>"        

    b、匹配兩個兩個重疊出現的字符  

 

        public static void Main()
        
{    
            
string s = "aabbc11asd";
            Regex reg 
= new Regex(@"(/w)/1");
            MatchCollection matches 
= reg.Matches(s);
            
foreach(Match m in matches)
                Console.WriteLine(m.Value);
            Console.ReadLine();
        }
      

返回結果爲aa bb 11 

一、本系列文章不講述基本的正則語法,這些可以在微軟的JS幫助文檔中找到,也可以Google一下
二、寫系列文章的原因
1、正則很有用,而且經常要用
2、正則的一些高級用法有相當一部分人還沒有理解和掌握
3、剛好又在網上看到了一篇文章錯誤的使用了正則式,使我有了寫本文的衝動
4、本系列文章的大部分知識可同時適用於.net語言,JavaScript
三、本系列文章特點:儘量使用小例子來說明相對難懂而很多正則書籍都沒有說清的正則語法
四、本系列文章內容:替換的高級語法,內聯表達式選項,組,反向引用,正聲明,負聲明,正反聲明,負反聲明,非回溯匹配,判斷式,.net正則引擎特點等 

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