Hướng dẫn sử dụng biểu thức chính quy trong C#
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Biểu thức chính quy (Regular expression)

1.1- Tổng quan

Một biểu thức chính quy (Regular expressions) định nghĩa một khuôn mẫu (pattern) tìm kiếm chuỗi. Nó có thể được sử dụng tìm kiếm, sửa đổi, và thao tác trên văn bản. Khuôn mẫu được định nghĩa bởi biểu thức chính quy có thể khớp một hoặc một vài lần, hoặc không khớp với một văn bản cho trước.

Viết tắt của biểu thức chính quy là regex

1.2- Hỗ trợ các ngôn ngữ

Biểu thức chính quy (Regular expression) được hỗ trợ bởi hầu hết các ngôn ngữ lập trình, ví dụ, C#, Java, Perl, Groovy, v..v... Thật không may mỗi ngôn ngữ hỗ trợ biểu thức thông thường hơi khác nhau.

2- Quy tắc viết biểu thức chính quy

No Regular Expression Mô tả
1 . Khớp (match) với một hoặc nhiều ký tự.
2 ^regex Biểu thức chính quy phải  khớp tại điểm bắt đầu
3 regex$ Biểu thức chính quy phải khớp ở cuối dòng.
4 [abc] Thiết lập định nghĩa, có thể khớp với a hoặc b hoặc c.
5 [abc][vz] Thiết lập định nghĩa, có thể khớp với a hoặc b hoặc c theo sau là v hoặc z.
6 [^abc] Khi dấu ^ xuất hiện như là nhân vật đầu tiên trong dấu ngoặc vuông, nó phủ nhận mô hình. Điều này có thể khớp với bất kỳ ký tự nào ngoại trừ a hoặc b hoặc c.
7 [a-d1-7] Phạm vi: phù hợp với một chuỗi giữa a và điểm d và con số từ 1 đến 7.
8 X|Z Tìm X hoặc Z.
9 XZ Tìm X và theo sau là Z.
10 $ Kiểm tra kết thúc dòng.
 
11 \d Số bất kỳ, viết ngắn gọn cho [0-9]
12 \D Ký tự không phải là số, viết ngắn gon cho [^0-9]
13 \s Ký tự khoảng trắng, viết ngắn gọn cho [ \t\n\x0b\r\f]
14 \S Ký tự không phải khoản trắng, viết ngắn gọn cho [^\s]
15 \w Ký tự chữ, viết ngắn gọn cho [a-zA-Z_0-9]
16 \W Ký tự không phải chữ, viết ngắn gọn cho [^\w]
17 \S+ Một số ký tự không phải khoảng trắng (Một hoặc nhiều)
18 \b Ký tự thuộc a-z hoặc A-Z hoặc 0-9 hoặc _, viết ngắn gọn cho [a-zA-Z0-9_].
 
19 * Xuất hiện 0 hoặc nhiều lần, viết ngắn gọn cho {0,}
20 + Xuất hiện 1 hoặc nhiều lần, viết ngắn gọn cho {1,}
21 ? Xuất hiện 0 hoặc 1 lần, ? viết ngắn gọn cho {0,1}.
22 {X} Xuất hiện X lần, {}
23 {X,Y} Xuất hiện trong khoảng X tới Y lần.
24 *? * có nghĩa là xuất hiện 0 hoặc nhiều lần, thêm ? phía sau nghĩa là tìm kiếm khớp nhỏ nhất.

3- Các ký tự đặc biệt trong C# Regex (Special characters)

Một số ký tự đặc biệt trong C# Regex:
\.[{(*+?^$|
 
Những ký tự liệt kê ở trên là các ký tự đặc biệt. Trong C# Regex bạn muốn nó hiểu các ký tự đó theo cách thông thường bạn cần thêm dấu \ ở phía trước.

Chẳng hạn ký tự chấm . C# regex đang hiểu là một ký tự bất kỳ, nếu bạn muốn nó hiểu là một ký tự chấm thông thường, cần phải có dấu \ phía trước.
// Mẫu regex mô tả một hoặc nhiều ký tự bất kỳ.
string regex = ".";

// Mẫu regex mô tả  ký tự dấu chấm.
string regex = "\\.";
string regex = @"\.";

4- Sử dụng Regex.IsMatch(string)

  • Regex class
...

// Kiểm tra đối tượng toàn bộ String có khớp với regex hay không.
public bool IsMatch(string regex)
..
Sử dụng  phương thức Regex.IsMatch(string regex) cho phép bạn kiểm tra toàn bộ String có khớp với regex không. Đây là một cách thông dụng nhất. Hãy xem các ví dụ:

Regex .

Trong biểu thức chính quy của C#, ký tự dấu chấm (.) là một ký tự đặc biệt. Nó đại diện cho một hoặc nhiều ký tự bất kỳ. Khi bạn muốn C# hiểu nó là một dấu chấm theo nghĩa thông thường bạn cần viết là "\\." hoặc @"\.";
DotExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace RegularExpressionTutorial
{
  class DotExample
  {
      public static void Main(string[] args)
      {

          // Chuỗi có 0 ký tự (Rỗng)          
          string s1 = "";
          Console.WriteLine("s1=" + s1);

     
          // Kiểm tra chuỗi s1
          // Khớp với 1 hoặc nhiều ký tự
          // Quy tắc .
          // ==> False
          bool match = Regex.IsMatch(s1, ".");
          Console.WriteLine("  -Match . " + match);


          // Chuỗi có 1 ký tự.  
          string s2 = "a";
          Console.WriteLine("s2=" + s2);

          // Kiểm tra chuỗi s2
          // Khớp với 1 hoặc nhiều ký tự
          // Quy tắc .
          // ==> True
          match = Regex.IsMatch(s2, ".");
          Console.WriteLine("  -Match . " + match);


          // Chuỗi có 3 ký tự.
          string s3 = "abc";
          Console.WriteLine("s3=" + s3);

           // Kiểm tra s3
           // Khớp với một hoặc nhiều ký tự.
           // Quy tắc .
           // ==> true
          match = Regex.IsMatch(s3, ".");
          Console.WriteLine("  -Match . " + match);

          // Chuỗi có 3 ký tự.
          string s4 = "abc";
          Console.WriteLine("s4=" + s4);

          // Kiểm tra chuỗi s4
          // Khớp với ký tự dấu chấm
          // ==> False
          match = Regex.IsMatch(s4, @"\.");
          Console.WriteLine("  -Match \\. " + match);


          // Chuỗi có 1 ký tự (Dấu chấm).
          string s5 = ".";
          Console.WriteLine("s5=" + s5);

          // Kiểm tra chuỗi s5
          // Khớp với ký tự dấu chấm
          // ==> True
          match = Regex.IsMatch(s5, @"\.");
          Console.WriteLine("  -Match \\. " + match);

          Console.Read();
      }
  }

}
Chạy ví dụ:
Một ví dụ khác sử dụng Regex.IsMath(string):
RegexIsMatchExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace RegularExpressionTutorial
{
   class RegexIsMatchExample
   {
       public static void Main(string[] args)
       {
      
           // Chuỗi với 1 ký tự
           string s2 = "m";
           Console.WriteLine("s2=" + s2);

       
           // Kiểm tra s2
           // Bắt đầu bởi ký tự m
           // Quy tắc ^
           // ==> true
           bool  match = Regex.IsMatch(s2, "^m");
           Console.WriteLine("  -Match ^m " + match);

     
           // Chuỗi có 7 ký tự
           string s3 = "MMnnnnn";
           Console.WriteLine("s3=" + s3);

  
           // Kiểm tra s3
           // Bắt đầu bởi MM
           // Quy tắc ^
           // ==> true
           match = Regex.IsMatch(s3, "^MM");
           Console.WriteLine("  -Match ^MM " + match);

    
           // Kiểm tra s3
           // Bắt đầu bởi ký tự MM
           // Tiếp theo là ký tự n xuất hiện một hoặc nhiều lần
           // Quy tắc ^ và +
           match = Regex.IsMatch(s3, "^MMn+");
           Console.WriteLine("  -Match ^MMn+ " + match);

  
           // Chuỗi với 1 ký tự
           String s4 = "p";
           Console.WriteLine("s4=" + s4);
     
           // Kiểm tra s4 kết thúc bởi p
           // Quy tắc $
           // ==> true
           match = Regex.IsMatch(s4, "p$");
           Console.WriteLine("  -Match p$ " + match);
     
           // Chuỗi có 6 ký tự
           string s5 = "122nnp";
           Console.WriteLine("s5=" + s5);


           // Kiểm tra s5 kết thúc bởi p
           // ==> true
           match = Regex.IsMatch(s5, "p$");
           Console.WriteLine("  -Match p$ " + match);

           // Kiểm tra s5
           // Bắt đầu bởi một hoặc nhiều ký tự (Quy tắc .)
           // Theo sau là ký tự n, xuất hiện 1 tới 3 lần (Quy tắc n{1,3} )
           // Kết thúc bởi ký tự p (Quy tắc $)
           // Kết hợp các quy tắc ., {x,y}, $
           // ==> true
           match = Regex.IsMatch(s5, ".n{1,3}p$");
           Console.WriteLine("  -Match .n{1,3}p$ " + match);


           String s6 = "2ybcd";
           Console.WriteLine("s6=" + s6);

 
           // Kiểm tra s6
           // Bắt đầu bởi 2
           // Tiếp theo là x hoặc y hoặc z  (Quy tắc [xyz])
           // Tiếp theo là bất kỳ xuất hiện 0 hoặc nhiều lần (Quy tắc *)
           match = Regex.IsMatch(s6, "2[xyz].*");

           Console.WriteLine("  -Match 2[xyz].* " + match);

           string s7 = "2bkbv";
           Console.WriteLine("s7=" + s7);

           // Kiểm tra s7, bắt đầu bất kỳ (một hoặc nhiều lần)
           // Tiếp theo là a hoặc b hoặc c (Quy tắc [abc] )
           // Tiếp theo là z hoặc v (Quy tắc [zv] )
           // Cuối cùng là bất kỳ, 0 hoặc nhiều lần (Quy tắc .*)
           // ==> true
           match = Regex.IsMatch(s7, ".[abc][zv].*");

           Console.WriteLine("  -Match .[abc][zv].* " + match);


           Console.Read();
       }
   }


}
Kết quả chạy ví dụ:
Ví dụ tiếp theo:
RegexIsMatchExample2.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace RegularExpressionTutorial
{
   class RegexIsMatchExample2
   {
       public static void Main(string[] args)
       {
           String s = "The film Tom and Jerry!";
 
           // Kiểm tra chuỗi s
           // Bắt đầu bởi ký tự bất kỳ, Xuất hiện 0 hoặc nhiều lần (Quy tắc: .*)
           // Tiếp theo là Tom hoặc Jerry
           // Kết thúc với bất kỳ, xuất hiện 1 hoặc nhiều lần (Quy tắc .)
           // Kết hợp các quy tắc: ., *, X|Z

           bool match = Regex.IsMatch(s, ".*(Tom|Jerry).");
           Console.WriteLine("s=" + s);
           Console.WriteLine("-Match .*(Tom|Jerry). " + match);

           s = "The cat";
           // ==> false
           match = Regex.IsMatch(s, ".*(Tom|Jerry).");
           Console.WriteLine("s=" + s);
           Console.WriteLine("-Match .*(Tom|Jerry). " + match);

           s = "The Tom cat";
           // ==> true
           match = Regex.IsMatch(s, ".*(Tom|Jerry).");
           Console.WriteLine("s=" + s);
           Console.WriteLine("-Match .*(Tom|Jerry). " + match);

           Console.Read();
       }
   }

}
Kết quả chạy ví dụ:

5- Sử dụng Regex.Split & Regex.Replace

Một trong các phương thức hữu ích khác là Regex.Split(string,string), phương thức này phân tách một chuỗi thành các chuỗi con. Chẳng hạn bạn có chuỗi "One,Two,Three" và bạn muốn phân tách thành 3 chuỗi con ngăn cách bởi dấu phẩy.
SplitWithRegexExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace RegularExpressionTutorial
{
   class SplitWithRegexExample
   {
       public static void Main(string[] args)
       {
    
           // \t: Ký tự TAB
           // \n: Ký tự xuống dòng
           string TEXT = "This \t\t is \n my \t text";
           
           Console.WriteLine("TEXT=" + TEXT);

   
           // Định nghĩa một Regex:
           // Khoảng trắng xuất hiện 1 hoặc nhiều lần.
           // Các ký tự khoảng trắng: \t\n\x0b\r\f
           // Kết hợp quy tắc: \s và +
           String regex = @"\s+";

           Console.WriteLine(" -------------- ");

           String[] splitString = Regex.Split(TEXT, regex);

           // 4
           Console.WriteLine(splitString.Length);

           foreach (string str in splitString)
           {
               Console.WriteLine(str);
           }

            Console.WriteLine(" -------------- ");

           // Thay thế tất cả các khoảng trắng với ký tự tab.
           String newText = Regex.Replace(TEXT, "\\s+", "\t");
           Console.WriteLine("New text=" + newText);



           Console.Read();
       }
   }

}
Chạy ví dụ:

6- Sử dụng MatchCollection & Match

Sử dụng Regex.Matches(...)  để tìm kiếm tất cả các chuỗi con của một chuỗi, phù hợp với một biểu thức chính quy, phương thức trả về một đối tượng MatchCollection.
** Regex.Matches() **
public MatchCollection Matches(
    string input
)

public MatchCollection Matches(
    string input,
    int startat
)

public static MatchCollection Matches(
    string input,
    string pattern
)

public static MatchCollection Matches(
    string input,
    string pattern,
    RegexOptions options,
    TimeSpan matchTimeout
)

public static MatchCollection Matches(
    string input,
    string pattern,
    RegexOptions options
)
Ví dụ dưới đây tách một chuỗi thành các chuỗi con phân cách bởi các khoảng trắng (whitespace).
MatchCollectionExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace RegularExpressionTutorial
{
   class MatchCollectionExample
   {
       public static void Main(string[] args)
       {

           string TEXT = "This \t is a \t\t\t String";

    
           // \w : Ký tự chữ, viết ngắn gọn cho [a-zA-Z_0-9]
           // \w+ : Ký tự chữ, xuất hiện một hoặc nhiều lần.
           string regex = @"\w+";


           MatchCollection matchColl = Regex.Matches(TEXT, regex);

           foreach (Match match in matchColl)
           {
               Console.WriteLine(" ---------------- ");
               Console.WriteLine("Value: " + match.Value);
               Console.WriteLine("Index: " + match.Index);
               Console.WriteLine("Length: " + match.Length);
           }

         
           Console.Read();
       }
   }

}
Kết quả chạy ví dụ:

7- Nhóm (Group)

Một biểu thức chính quy bạn có thể tách ra thành các nhóm (group):
// Một biểu thức chính quy
string regex = @"\s+=\d+";

// Viết dưới dạng group, bởi dấu ()
string regex2 = @"(\s+)(=)(\d+)";

// Một cách khác.
string regex3 = @"(\s+)(=\d+)";
Các group có thể lồng nhau, và như vậy cần một quy tắc đánh chỉ số các group.  Toàn bộ pattern được định nghĩa là group số 0. Còn lại được mô tả giống hình minh họa dưới đây:

Chú ý: Sử dụng (?:pattern) để thông báo với C# không xem đây là một group (None-capturing group)

Bạn có thể xác định một group có tên (?<name>pattern) hoặc (?'name'pattern), Và bạn có thể truy cập các nội dung khớp với match.Groups["groupName"]. Điều này làm Regex dài hơn, nhưng mã này là có ý nghĩa hơn, dễ hơn.

Nhóm bắt theo tên cũng có thể được truy cập thông qua match.Groups[groupIndex] với các đề án đánh số tương tự.
-
Hãy xem một ví dụ sử dụng đánh tên cho nhóm (group):
NamedGroupExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace RegularExpressionTutorial
{
  class NamedGroupExample
  {
      public static void Main(string[] args)
      {
          string TEXT = " int a = 100;  float b= 130;  float c= 110 ; ";

          // Sử dụng (?<groupName>pattern) để định nghĩa một Group có tên: groupName
          // Định nghĩa group có tên declare: sử dụng (?<declare> ...)
          // Và một group có tên value: sử dụng: (?<value> ..)
          string regex = @"(?<declare>\s*(int|float)\s+[a-z]\s*)=(?<value>\s*\d+\s*);";

          MatchCollection matchCollection = Regex.Matches(TEXT, regex);


          foreach (Match match in matchCollection)
          {
              string group = match.Groups["declare"].Value;
              Console.WriteLine("Full Text: " + match.Value);
              Console.WriteLine("<declare>: " + match.Groups["declare"].Value);
              Console.WriteLine("<value>: " + match.Groups["value"].Value);
              Console.WriteLine("------------------------------");
          }

          Console.Read();
      }
  }

}
Kết quả chạy ví dụ:
Để dễ hiểu bạn có thể xem hình minh họa dưới đây:

8- Sử dụng MatchCollection, Group và *?

Trong một số tình huống *? rất quan trọng, hãy xem một ví dụ sau:
// Đây là một regex
// Bắt gặp ký tự bất kỳ 0 hoặc nhiều lần,
// sau đó tới ký tự ' và tiếp theo là >
string regex = ".*'>";

// Đoạn TEXT1 sau đây có vẻ hợp với regex nói trên.
string TEXT1 = "FILE1'>";

// Đoạn TEXT2 sau cũng hợp với regex nói trên.
string TEXT2 = "FILE1'> <a href='http://HOST/file/FILE2'>";
*? sẽ tìm ra một phù hợp nhỏ nhất. Chúng ta xem ví dụ sau:
NamedGroupExample2.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace RegularExpressionTutorial
{
   class NamedGroupExample2
   {
       public static void Main(string[] args)
       {

           string TEXT = "<a href='http://HOST/file/FILE1'>File 1</a>"
                        + "<a href='http://HOST/file/FILE2'>File 2</a>";


           // Define group named fileName.
           // *? ==> ? after a quantifier makes it a reluctant quantifier.
           // It tries to find the smallest match.
           string regex = "/file/(?<fileName>.*?)'>";

           MatchCollection matchCollection = Regex.Matches(TEXT, regex);


           foreach (Match match in matchCollection)
           {
               Console.WriteLine("File Name = " + match.Groups["fileName"].Value);
               Console.WriteLine("------------------------------");
           }

           Console.Read();
       }
   }

}
Kết quả chạy ví dụ: