openplanning

Hướng dẫn sử dụng biểu thức chính quy trong C#

  1. Biểu thức chính quy (Regular expression)
  2. Quy tắc viết biểu thức chính quy
  3. Các ký tự đặc biệt trong C# Regex (Special characters)
  4. Sử dụng Regex.IsMatch(string)
  5. Sử dụng Regex.Split & Regex.Replace
  6. Sử dụng MatchCollection & Match
  7. Nhóm (Group)
  8. Sử dụng MatchCollection, Group và *?

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

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 (match) 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
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 chính quy 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 hoặc nhiều 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 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ộ một String có khớp với regex hay 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ụ:
s1=
  -Match . False
s2=a
  -Match . True
s3=abc
  -Match . True
s4=abc
  -Match \. False
s5=.
  -Match \. True
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 có 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 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ụ:
s2=m
  -Match ^m True
s3=MMnnnnn
  -Match ^MM True
  -Match ^MMn+ True
s4=p
  -Match p$ True
s5=122nnp
  -Match p$ True
  -Match .n{1,3}p$ True
s6=2ybcd
  -Match 2[xyz].* True
s7=2bkbv
  -Match .[abc][zv].* True
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ụ:
s=The film Tom and Jerry!
-Match .*(Tom|Jerry). True  
s=The cat
-Match .*(Tom|Jerry). False
s=The Tom cat
-Match .*(Tom|Jerry). True

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 (NewLine character).
            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);

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

            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 phương thức 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 này 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ụ:
----------------
Value: This
Index: 0
Length: 4
----------------
Value: is
Index: 7
Length: 2
----------------
Value: a
Index: 10
Length: 1
----------------
Value: String  
Index: 16
Length: 6

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 3 nhóm (group), bởi dấu ( )
string regex2 = @"(\s+)(=)(\d+)";

// Hai nhóm (group)
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à nhóm 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ể định nghĩa một group có tên (?<groupName>pattern) hoặc (?'groupName'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 được đặt tên cũng có thể được truy cập thông qua match.Groups[groupIndex] với quy tắc đánh số tương tự.
-
Hãy xem một ví dụ sử dụng nhóm (group) được đặt tên:
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ụ:
Full Text: int a = 100;
<declare>: int a
<value>: 100
------------------------------
Full Text:   float b = 130;
<declare>:   float b
<value>: 130
------------------------------
Full Text:   float c = 110 ;
<declare>:   float c
<value>: 110
------------------------------
Để 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
// Ký tự bất kỳ, xuất hiện 0 hoặc nhiều lần,
// sau đó tới ký tự  '  và tiếp theo là >
string regex = ".*'>";

// Chuỗi TEXT1 sau đây khớp với regex nói trên.
string TEXT1 = "FILE1'>";

// Chuỗi TEXT2 sau khớ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 (smallest match). 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>";

            // Định nghĩa một group có tên fileName.
            // * có nghĩa là xuất hiện 0 hoặc nhiều lần.
            // *? có nghĩa là một phù hợp nhỏ nhất (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ụ:
File Name = FILE1
------------------------------
File Name = FILE2
------------------------------