reference :http://www.cnblogs.com/thinhunan/archive/2007/08/21/339316.html
.net中的正則表達式使用高級技巧
前言
一、本系列文章不講述基本的正則語法,這些可以在微軟的
JS
幫助文檔中找到,也可以
Google
一下
二、寫系列文章的原因
1
、正則很有用,而且經常要用
2
、正則的一些高級用法有相當一部分人還沒有理解和掌握
3
、剛好又在網上看到了一篇文章錯誤的使用了正則式,使我有了寫本文的沖動
4
、本系列文章的大部分知識可同時適用于
.net
語言,
JavaScript
等
三、本系列文章特點:盡量使用小例子來說明相對難懂而很多正則書籍都沒有說清的正則語法
四、本系列文章內容:替換的高級語法,內聯表達式選項,組,反向引用,正聲明,負聲明,正反聲明,負反聲明,非回溯匹配,判斷式,
.net
正則引擎特點等
?
?
因為
.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
相對應
|
這里我提到內聯標志,是因為相對于用
RegexOptions
在
new Regex
時定義
Regex
表達式的全局選項來說,內聯標志可以更小粒度(以組為單位)的定義匹配選項,從而更方便表達我們的思想
語法是這樣的:
(?i:expression)
為定義一個選項,
(?-i:expression)
為刪除一個選項,
(?i-s:expression)
則定義
i
,刪除
s,
是的,我們可以一次定義很多個選項。這樣,通過內聯選項,你就可以在一個
Regex
中定義一個組為匹分大小寫的,一個組不匹分大小寫的,是不是很方便呢
?
更多精彩內容,請看下回分解,
下一篇:
.net中的正則表達式使用高級技巧 (二)
??
???????????????
.net中的正則表達式使用高級技巧 (三)
?
???????????????
.net中的正則表達式使用高級技巧 (四)
正則表達式中的組是很重要的一個概念,它是我們通向高級正則應用的的橋梁
組的概念
一個正則表達式匹配結果可以分成多個部分,這就是組
(Group)
的目的。能夠靈活的使用組后,你會發現
Regex
真是很方便,也很強大。
先舉個例子
??
????????
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
方法就能很方便的實現。
在這個例子中,我把一次
Match
結果用
(?<name>)
的方式分成三個組
"y","m","d"
分別代表年、月、日。
現在我們已經有了組的概念了,再來看如何分組,很簡單的,除了上在的辦法,我們可以用一對括號就定義出一個組,比如上例可以改成
??
????????
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();
????????}
再看上例,我們用
(?<
數字
>)
的方式手工給每個括號對的組編號,(注意我定義
1
和
2
的位置時不是從左到右定義的)
通過以上三例,我們知道了給
Regex
定義
Group
的三種辦法以及相應的引用組匹配結果的方式。
然后,關于組定義,還有兩點請注意:
1
、因為括號用于定義組了,所以如果要匹配
"("
和
")"
,請使用
"\("
和
"\)"(
關于所有特殊字符的定義,請查看相關
Regex expression
幫助文檔
)
。
2
、如果定義
Regex
時,使用了
ExplicitCapture
選項,則第二個例子不會成功,因為此選項要求顯式定義了編號或名字的組才捕獲并保存結果,如果你沒有定義
ExplicitCapture
選項,而有時又定義了類式于
(A|B)
這樣的部分在表達式,而這個
(A|B)
你又并不想捕獲結果,那么可以使用“不捕獲的組”語法,即定義成
(?:)
的方式,針對于
(A|B),
你可以這樣來定義以達到不捕獲并保存它到
Group
集合中的目的--
(?:A|B)
。
?
上面內容僅討論了一般的組,組還有很多的花樣,很多高級的功能,下一篇將試圖帶您體驗一番其中洞天。
反向引用
反向引用,指把匹配出來的組引用到表達式本身其它地方,比如,在匹配
HTML
的標記時,我們匹配出一個
<a>,
我們要把匹配出來的
a
引用出來,用來找到
</a>
,這個時候就要用到反向引用。
語法
??? a
、反向引用編號的組,語法為
\number
??? b
、反向引用命名的組,語法為
\k<name>
舉例
??? a
、匹配成對的
HTML
標簽
@"
<(?<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
?
輔助匹配組
以下幾種組結構,括號中的
Pattern
都不作為匹配結果的一部分進行保存
??? 1
、正聲明
(?=)
???
涵義:括號中的模式必須出現在聲明右側,但不作為匹配的一部分
????????
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
。
????????
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
、反向負聲明
(?<!)
???
涵義:括號中的模式必須不出現在聲明左側
非回溯匹配
語法:
(?>)
涵義:該組匹配后,其匹配的字符不能通過回溯用于后面的表達式的匹配。呵呵,光看這句話肯定搞不懂,我當初為了搞懂這個也花了不少的時間,還是通過實例來說明吧:
"www.csdn.net"
可以通過
@"\w+\.(.*)\.\w+"
來匹配,卻不能通過
@"\w+\.(?>.*)\.\w+"
來匹配!為什么呢?
原因是正則匹配是貪婪的,匹配時它會盡可能多的匹配最多的結果,所以,上例兩個正則式中的
.*
都會把
csdn.net
匹配完,
這個時候,第一個表達式在開始匹配時發現
\.\w+
沒得字符給它匹配了,所以它會進行回溯,所謂回溯,就是把
.*
匹配的結果往回推,回推留出來的字符再用來匹配
\.\w+,
直到
\.\w+
匹配成功,整個表達式返回成功的匹配結果。而第二個表達式,因使用的是非回溯匹配,所以,
.*
匹配完后,不允許通過回溯來匹配
\.\w+
,所以整個表達式匹配失敗。
請注意,回溯匹配是很浪費資源的一種匹配方式,所以,請盡量避免您的正則式要通過回溯來成功匹配,如上例,
可以換成@"\w+\.([^\.]+\.)+\w+"
+"
。
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
組和塊范圍。例如,
Ll
、
Nd
、
Z
、
IsGreek
、
IsBoxDrawing
。可以使用
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})
。
|
?--完--
?