您好,登錄后才能下訂單哦!
本篇文章為大家展示了.NET正則類及其方法應用是怎樣的,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
1 概述
初學正則時,對于Regex類不熟悉,遇到問題不知道該用哪種方法解決,本文結合一些正則應用的典型應用場景,介紹一下Regex類的基本應用。這里重點進行.NET類的介紹,對于正則的運用,不做深入探討。
正則的應用最終都是進行模式的匹配,而根據目的的不同,基本上可以分為以下幾種應用:驗證、提取、替換、分割。結合.NET提供的控件、類以及類的方法,可以很方便的實現這些應用。
以下將結合一些典型的應用場景,對.NET中常見的類、方法及屬性進行介紹。本文旨在.NET類基礎用法的引導,對于其中涉及到的正則表達式不做深入探討。本文適合于在.NET平臺下使用正則的初學者。
2 基礎應用
2.1 驗證
驗證的目的是為了判斷輸入的源字符串是否符合某一規律或規則,根據需求的不同,可能是校驗整個源字符串,也可能只是校驗其中一個子串。
驗證在.NET中一般有兩種應用,一種是在驗證控件RegularExpressionValidator中,另一種是在程序中。
2.1.1 驗證控件RegularExpressionValidator
RegularExpressionValidator是.NET自帶的一種客戶端驗證控件,通過簡單的設置,即可完成對某控件輸入值的校驗。
基本應用語法如下:
<asp:RegularExpressionValidator ID="RegularExpressionValidator1" runat="server" ControlToValidate="TextBox1" ErrorMessage="RegularExpressionValidator" ValidationExpression="^([1-9][0-9]*|0)(\.[0-9][2])?$"> </asp:RegularExpressionValidator>
對于RegularExpressionValidator控件不做過多介紹,只是說明一下使用時需要注意的幾點:
1、 RegularExpressionValidator進行的是客戶端驗證;
2、 RegularExpressionValidator中正則使用的是JavaScript語法規則;
3、 RegularExpressionValidator控件不能驗證輸入是否為空。
由于RegularExpressionValidator做的是客戶端驗證,很容易被跳過,所以在使用RegularExpressionValidator驗證的同時,還要做服務器端驗證。
RegularExpressionValidator最終是要生成客戶端的JavaScript代碼進行驗證的,所以RegularExpressionValidator使用的正則要求符合JavaScript語法規則,與.NET的幾點區別:
1、 不支持逆序環視,也就是(?<=Expression)和(?<!Expression)這樣的語法;
2、 元字符僅支持ASCII碼,即\w等價于[a-zA-Z0-9_],\d等價于[0-9]
RegularExpressionValidator控件一般是用來驗證某一控件輸入的字符串整體是否符合某一規則的,所以通常情況下“^”和“$”是必不可少的;在使用“|”表示“或”的關系時,一定要用“()”來限定“|”作用范圍,比如0-100可以寫作“^([1-9]?[0-9]|100)$”。
RegularExpressionValidator是不能驗證輸入是否為空的,驗證是否為空要用RequiredFieldValidator控件。
RegularExpressionValidator驗證控件是.NET為方便客戶端驗證封裝的一組驗證控件之一,但由于RegularExpressionValidator受限于支持的正則語法規則,只能做有限的格式校驗,一些復雜的校驗可以通過自己寫JavaScript代碼來實現,也是很簡單的。
2.1.2 程序驗證——IsMatch()
程序中的校驗基本上就是使用IsMatch方法,驗證的對象可能是源字符串的整體,也可能只是其中一個子串。
驗證源字符串的整體是否符合某一規則,與使用RegularExpressionValidator時的需求基本上一致,不過由于是在.NET程序中,所以使用的是.NET的語法,比JavaScript中要強大得多。比如驗證一個文本框輸入的字符串是否符合某一規則,就是一個典型的驗證整體的需求。
舉例1:驗證textBox1輸入內容,要求整數部分為0或正整數,小數可有可無,有小數時必須為2位。
Regex reg = new Regex(@"^(?:[1-9][0-9]*|0)(?:\.[0-9]{2})?$"); if (reg.IsMatch(textBox1.Text)) { richTextBox2.Text = "輸入格式正確!"; } else { richTextBox2.Text = "輸入格式錯誤!"; }
由于是對源字符串的整體進行驗證,所以“^”和“$”是必不可少的。否則驗證的結果可能是錯誤的,比如正則表達式“(?:[1-9][0-9]*|0)(?:\.[0-9]{2})?”,在輸入“0.123”時是可以匹配成功的,匹配結果為“0.12”,此時正則只起到了匹配的作用,沒有起到驗證的作用。
驗證源字符串的局部是否符合某一規則,就是對于源字符串中子串的校驗,通常是用來判斷源字符串中是否包含,或是不包含符合某一規律的子串,作用類似于string類中的IndexOf。
舉例2(參考問兩個正則表達式):
數據:
1985aaa1985bb
bcae1958fiefadf1955fef
atijc1944cvkd
df2564isdjfef2564d
abc1234def5678ghi5678jkl
需求1:驗證字符串中任意位置出現的連續四個數字在整個字符串中是否有重復,有重復為True,無重復為False。
以上數據需求1的驗證結果為True的應為:
1985aaa1985bb
df2564isdjfef2564d
abc1234def5678ghi5678jkl
因為需求中指明是任意位置的連續4個數字是否有重復,所以在找到重復前,要遍歷源字符串中每一個位置時行驗證,這樣就不能限定開始標識符“^”;而在匹配過程中,除非一直到結尾仍找不到重復,否則只要匹配到有重復的位置就可以了,這樣也不需要結束標識符“$”,所以這是典型的對字符串的子串行驗證的需求。
代碼實現:
string[] test = new string[] { "1985aaa1985bb", "bcae1958fiefadf1955fef", "atijc1944cvkd", "df2564isdjfef2564d", "abc1234def5678ghi5678jkl" }; Regex reg = new Regex(@"(\d{4})(?:(?!\1).)*\1"); foreach (string s in test) { richTextBox2.Text += "源字符串: " + s.PadRight(25, ' ') + "驗證結果: " + reg.IsMatch(s) + "\n"; } /*--------輸出-------- 源字符串: 1985aaa1985bb 驗證結果: True源字符串: bcae1958fiefadf1955fef 驗證結果: False源字符串: atijc1944cvkd 驗證結果: False源字符串: df2564isdjfef2564d 驗證結果: True源字符串: abc1234def5678ghi5678jkl 驗證結果: True*/
由于涉及到了重復問題,所以這里用到了反向引用,對于反向引用的細節,可以參考正則基礎之——反向引用。
需求2:驗證字符串中第一個出現的連續4個數字是否有重復,有重復為True,無重復為False。
以上數據需求2的驗證結果為True的應為:
1985aaa1985bb
df2564isdjfef2564d
因為需求中指明是第一個是否有重復,所以需要有開始標識符“^”,來保證是第一個出現的連續4個數字;而在匹配過程中,除非一直到結尾仍找不到重復,否則只要匹配到有重復的位置就可以了,這樣也不需要結束標識符“$”,所以這仍是對字符串的子串行驗證的需求,只不過相對于需求1來說,加了一個限定條件。
代碼實現:
string[] test = new string[] { "1985aaa1985bb", "bcae1958fiefadf1955fef", "atijc1944cvkd", "df2564isdjfef2564d", "abc1234def5678ghi5678jkl" }; Regex reg = new Regex(@"^(?:(?!\d{4}).)*(\d{4})(?:(?!\1).)*\1"); foreach (string s in test) { richTextBox2.Text += "源字符串: " + s.PadRight(25, ' ') + "驗證結果: " + reg.IsMatch(s) + "\n"; } /*--------輸出-------- 源字符串: 1985aaa1985bb 驗證結果: True 源字符串: bcae1958fiefadf1955fef 驗證結果: False 源字符串: atijc1944cvkd 驗證結果: False 源字符串: df2564isdjfef2564d 驗證結果: True 源字符串: abc1234def5678ghi5678jkl 驗證結果: False */
2.2 提取——Match()、Matches()
提取主要是從源字符串中,取得一個或多個符合某一規律或規則的子串。一般來說,在字符串處理中,提取應用比較廣泛。提取時用得比較多的是Match()和Matches()方法,以及結果處理時Match類和MatchCollection類的一些方法,有時也會用到Capture類的一些方法。
2.2.1 提取單次匹配內容——Match()
當需要提取的內容只有一個,或是只需要獲取第一次成功匹配的內容時,可以使用Match()方法。當使用Match()方法時,只要在某一位置匹配成功,就不再繼續嘗試匹配,并返回一個Match類型的對象。
舉例:提取姓名
源字符串:姓名:張三,性別:男,年齡:25
代碼實現:
string test = "姓名:張三,性別:男,年齡:25"; Regex reg = new Regex(@"(?<=姓名:)[^,]+"); Match m = reg.Match(test); if (m.Success) //驗證是否匹配成功 { richTextBox2.Text = m.Value; } /*--------輸出-------- 張三 */
雖然Match()只是取一次匹配,但是可以通過捕獲組來獲取多個指定子串,比如獲取第一個<a…>標簽的鏈接和文本。
string test = "abc<a href=\"www.test1.com\">測試一</a>def<a href=\"www.test2.com\">測試二</a>ghi"; Regex reg = new Regex(@"(?is)<a(?:(?!href=).)href=(['""]?)(?<url>[^""\s>]*)\1[^>]*>(?<text>(?:(?!</?a\b).)*)</a>"); Match m = reg.Match(test); if(m.Success) { richTextBox2.Text += m.Groups["url"].Value + "\n"; //鏈接 richTextBox2.Text += m.Groups["text"].Value + "\n"; //文本 } /*--------輸出-------- www.test1.com 測試一 */
對于捕獲組捕獲結果的引用,還有一種方式
string test = "abc<a href=\"www.test1.com\">測試一</a>def<a href=\"www.test2.com\">測試二</a>ghi"; Regex reg = new Regex(@"(?is)<a(?:(?!href=).)href=(['""]?)(?<url>[^""\s>]*)\1[^>]*>(?<text>(?:(?!</?a\b).)*)</a>"); Match m = reg.Match(test); if(m.Success) { richTextBox2.Text += m.Result("${url}") + "\n"; //鏈接 richTextBox2.Text += m.Result("${text}") + "\n"; //文本 } /*--------輸出-------- www.test1.com 測試一 */
這兩種方法獲取的結果都是一樣的,使用哪一種,通常根據個人習慣而定。
2.2.2 提取多次匹配內容——Matches()
當需要提取的內容有多個,并且需要提取所有符合規律的子串時,可以使用Matches()方法。當使用Matches()方法時,需要遍歷源字符串的每一個位置進行嘗試匹配,匹配結束返回一個MatchCollection類型的對象。
對于1.2.1節提到的提取鏈接和文本的例子,如果提取的是全部鏈接和文本,而不僅僅是第一個時,可以使用Matches()方法。
string test = "abc<a href=\"www.test1.com\">測試一</a>def<a href=\"www.test2.com\">測試二</a>ghi"; Regex reg = new Regex(@"(?is)<a(?:(?!href=).)href=(['""]?)(?<url>[^""\s>]*)\1[^>]*>(?<text>(?:(?!</?a\b).)*)</a>"); MatchCollection mc = reg.Matches(test); foreach(Match m in mc) { richTextBox2.Text += m.Groups["url"].Value + "\n"; //鏈接 richTextBox2.Text += m.Groups["text"].Value + "\n"; //文本 } /*--------輸出-------- www.test1.com 測試一 www.test2.com 測試二 */
對于Matches(),某些場景下,也可以通過Count屬性,用做統計符合某一規律的子串出現的次數,例如統計字符串中獨立的“3”出現的次數。
string test = "137,1,33,4,3,6,21,3,35,93,2,98"; Regex reg = new Regex(@"\b3\b"); int count = reg.Matches(test).Count; //2
這時候關心的只是匹配成功的次數,對于匹配的內容是不關心的,所以實現這種需求時,正則應盡量簡潔,能達到目的即可,這樣可以加快匹配效率,減少資源占用。比如上面的提取鏈接的源字符串中,統計<a…>標簽出現的次數,一般來說,如下代碼即可達到目的了。
string test = "abc<a href=\"www.test1.com\">測試一</a>def<a href=\"www.test2.com\">測試二</a>ghi"; Regex reg = new Regex(@"(?i)<a\b"); int count = reg.Matches(test).Count; //2
2.2.3 捕獲組匹配過程集合——Capture
在某些情況下,一個正則表達式整體匹配一次時,其中的捕獲組可能會匹配多次。
舉例:
源字符串:<region name=oldslist col=1 row=2 order=asc>abcsadf </region> jfdsajf <region name=newslist class=list col=4 row=10 order=desc>abcsadf </region>
需求:提取出每個region的屬性和屬性值,按region分組。
對于這個需求,可以先提取出所有region,再對每個region標簽提取它的屬性和屬性值,但這樣做比較麻煩,可以考慮在一個正則表達式中提取。由于屬性的個數是不固定的,所以不能用固定個數的量詞來匹配屬性對,正則可以寫為
(?is)<region\s+(?:(?<key>[^\s=]+)=(?<value>[^\s>]+)\s*)+>
此時如果還用Groups來取匹配結果時,由于Groups只保留最后一次匹配結果,所以只能取到最后一次匹配成功的子串。這時就要用到Captures屬性。
string test = "<region name=oldslist col=1 row=2 order=asc>abcsadf </region> jfdsajf <region name=newslist class=list col=4 row=10 order=desc>abcsadf </region>"; MatchCollection mc = Regex.Matches(test, @"(?is)<region\s+(?:(?<key>[^\s=]+)=(?<value>[^\s>]+)\s*)+>"); for (int i = 0; i < mc.Count; i++) { richTextBox2.Text += "第" + (i + 1) + "個region的屬性:\n"; for (int j = 0; j < mc[i].Groups["key"].Captures.Count; j++) { richTextBox2.Text += "屬性: " + mc[i].Groups["key"].Captures[j].Value.PadRight(10, ' ') + " 值: " + mc[i].Groups["value"].Captures[j].Value + "\n"; } richTextBox2.Text += "\n"; } /*--------輸出-------- 第1個region的屬性:屬性: name 值: oldslist屬性: col 值: 1屬性: row 值: 2屬性: order 值: asc第2個region的屬性:屬性: name 值: newslist屬性: class 值: list屬性: col 值: 4屬性: row 值: 10屬性: order 值: desc*/
Group實際上是Capture的一個集合,在捕獲組只匹配一個子串時,這個集合只有一個元素,而在捕獲組先后匹配多個子串時,Groups[i].Value只保留最后一個匹配結果,而Capture集合卻可以記錄匹配過程中匹配到的所有子串。
Capture的應用場景并不多,對于上面的例子,如果不使用 Capture,可以通過分次匹配的方式實現,但是在一些復雜的表達式中,很難進行分次匹配,這時Capture就比較有用了。
2.3 替換
替換主要是從源字符串中,將符合某一規律或規則的子串替換為其它內容。一般來說,在字符串處理中,替換應用也比較廣泛。替換主要是用Replace()方法,在一些替換規則復雜的應用場景中,也可能會用到委托方法。
2.3.1 一般替換
替換的目的很明確,只要找出待替換子串的規律,替換為目標子串就可以了。
舉例1:
源字符串:<img src="/imgs/logo.jpg"> abc <img src="https://cache.yisu.com/upload/information/20210521/332/467817.gif">
需求:將相對地址替換為絕對地址,已經是絕對地址的不替換。
string test = "<img src=\"/imgs/logo.jpg\"> abc <img src=\"https://cache.yisu.com/upload/information/20210521/332/467817.gif\">"; Regex reg = new Regex(@"(?i)(?<=src=(['""]?))(?!http://)(?=[^'""\s>]+\1)"); string result = reg.Replace(test, "http://www.test.com"); /*--------輸出-------- <img src="https://cache.yisu.com/upload/information/20210521/332/467834.jpg"> abc <img src="https://cache.yisu.com/upload/information/20210521/332/467817.gif"> */
這里需要說明的是,在.NET中只提供了一個Replace()方法,沒有提供類似于Java中的replaceAll()和replaceFirst()兩種方法,所以如果在.NET中只替換第一次出現的符合規律的子串時,需要在正則表達式中處理。
舉例2:
源字符串:abc123def123ghi
需求:將第一次出現的“123”替換為空,其余位置不替換。
string test = "abc123def123ghi"; Regex reg = new Regex(@"(?s)^((?:(?!123).)*)123"); string result = reg.Replace(test, "$1"); /*--------輸出-------- abcdef123ghi */
這時用“^”限定只替換第一次出現的子串,由于絕大多數正則引擎都對“^”進行了優化,所以正則表達式在位置0處匹配成功或失敗后,將不再對其它位置進行嘗試匹配。
2.3.2 替換中的委托方法應用
對于一些比較復雜的替換規則,可能會用到委托方法,由于這種應用屬于比較典型的一種應用,所以將在后面的文章中單獨進行介紹
2.4 分割
分割就是用符合某一規律的子串,將源字符串分割成一個數組,主要用到的是Split()方法。由于Regex的Split()方法中,并沒有提供類似于string的Split()方法的StringSplitOptions.RemoveEmptyEntries參數,而如果符合規律的子串出現在開頭或結尾時,會出現不需要的空串,這時需要在正則表達式中進行一下處理。
舉例1:
源字符串:漢字123文字english
需求:按英文單詞和非英文單詞進行分割(英文單詞包括由a-z,A-Z,0-9,_組成的子串)。
string str = "漢字123文字english"; string[] result = Regex.Split(str, @"(?<!^)\b(?!$)", RegexOptions.ECMAScript); foreach (string s in result) { richTextBox2.Text += s + "\n"; } /*--------輸出-------- 漢字 123 文字 English */
這里分別用“(?<!^)”和“(?!$)”來限定不以開頭或結尾的子串進行分割,結果中也就不會出現不必要的空串了。
還有一些應用,其實可以算作是正則就用技巧范疇的了。
舉例2:
源字符串:Left(姓名,1),Left(姓名,1) , Left (姓名,1)
需求:以不在“()”內的“,”進行分割。
string test = "Left(姓名,1),Left(姓名,1) , Left (姓名,1)"; Regex reg = new Regex(@"(?<!\([^)]*),(?![^(]*\))"); string[] sArray = reg.Split(test); foreach (string s in sArray) { richTextBox2.Text += s + "\n"; } /*--------輸出-------- Left(姓名,1) Left(姓名,1) Left (姓名,1) */
使用正則的Split()方法時,有一點需求注意,那就是如果正則中出現了捕獲組,那么捕獲組匹配到的內容也會保存到分割結果中。
以下舉例不做詳細說明,看下結果基本上就明白了。
string test = "aa11<bbb>cc22<ddd>ee"; string[] temp = Regex.Split(test, @"[0-9]+(<[^>]*>)"); foreach (string s in temp) { richTextBox2.Text += s + "\n"; } /*--------輸出-------- aa <bbb> cc <ddd> ee */
如果不想把捕獲組匹配到的內容也存入結果,可以使用非捕獲組。
string test = "aa11<bbb>cc22<ddd>ee"; string[] temp = Regex.Split(test, @"[0-9]+(?:<[^>]*>)"); foreach (string s in temp) { richTextBox2.Text += s + "\n"; } /*--------輸出-------- aa cc ee */
3 擴展應用
這里介紹一些可能涉及到的一些.NET中的正則擴展應用。
3.1 動態生成正則時的轉義——Escape()
有時需要根據一些變量動態生成正則表達式,這時如果變量中含有正則中的元字符,會被解析成元字符,就可能會導致正則編譯不通過,從而導致程序異常,需要對變量進行轉義處理。Regex. Escape()方法通過替換為轉義碼來轉義最小的字符集(\、*、+、?、|、{、[、(、)、^、$、.、# 和空白)。
比如根據用戶輸入的id取相應的div標簽,id中沒有元字符時,可以取得正確結果。
string test = "<div id=\"test1\">abc</div><div id=\"test2\">def</div>"; string[] ids = new string[] { "test1", "test2" }; foreach (string id in ids) { Regex reg = new Regex(@"(?is)<div\s+id=""" + id + @"""[^>]*>(?:(?!</?div\b).)*</div>"); MatchCollection mc = reg.Matches(test); foreach (Match m in mc) { richTextBox2.Text += m.Value + "\n"; } } /*--------輸出-------- <div id="test1">abc</div> <div id="test2">def</div> */
但是如果輸入的id中一旦出現未經轉義的元字符,如“abc(”,就會拋類似于下面的異常。
正在分析“(?is)<div\s+id="abc("[^>]*>(?:(?!</?div\b).)*</div>”- ) 不足。
此時可以用Escape()方法對輸入的變量進行轉義處理。
string test = "<div id=\"test1\">abc</div><div id=\"test2\">def</div>"; string[] ids = new string[] { "test1", "test2", "abc(" }; foreach (string id in ids) { Regex reg = new Regex(@"(?is)<div\s+id=""" + Regex.Escape(id) + @"""[^>]*>(?:(?!</?div\b).)*</div>"); MatchCollection mc = reg.Matches(test); foreach (Match m in mc) { richTextBox2.Text += m.Value + "\n"; } } /*--------輸出-------- <div id="test1">abc</div> <div id="test2">def</div> */
使用Escape()方法轉義后,就可以得到正確的結果,而不會拋異常了。
3.2 靜態方法
.NET中一些Regex類的常見方法都提供了相應的靜態方法,可以不顯式的聲明Regex對象,而直接調用相應的方法,書寫起來更方便,代碼更簡潔、易讀。
比如替換IP地址最后一節為“*”,只需一行代碼。
string result = Regex.Replace("10.27.123.12", @"\d+$", "*"); //10.27.123.*
靜態方法每次調用都會創建一個臨時的Regex對象,使用之后釋放,所以每次調用靜態方法時,都會重新編譯,而這將會降低執行效率。因此在循環或是頻繁調用的方法中,不適合使用靜態方法,而需要進行顯式聲明Regex對象。
但是對于一些只調用一次,或是對執行效率沒有要求的場景下,靜態方法則是很不錯的選擇。
上述內容就是.NET正則類及其方法應用是怎樣的,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。