Các quy tắc Regular Expression căn bản (tiếp theo)

1. Regex đại diện cho một ký tự


Bài trước ta đã được học cách xác định phạm vi của một chuỗi bằng cách dùng cú pháp [min-max] hoặc [list_char], nhưng giả sử tôi muốn một Regex chấp nhận một ký tự bất kì nào đó thì không thể sử dụng cú pháp đó được. Và thật may trong Regular Expression đã cho ta một cách đó là dùng ký tự . để định nghĩa cho một kí tự bất kì.
Ví dụ:
1
2
3
4
5
6
// Pattern là ký tự bất kỳ dài từ 3 đến 10 ký tự
$pattern = '/^.{3,10}$/';
$subject = '3232';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

2. Ký hiệu đặc biệt cho các từ khóa Regex


Giả sử giờ tôi cần kiểm tra một chuỗi có tồn tại dấu chấm . hay không? Nếu áp dụng kí tự . thì ta sẽ làm như sau:
1
2
3
4
5
6
// Kết quả ko mong đợi
$pattern = '/./';
$subject = 'demo';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}
Chạy đoạn code này lên kết quả trả về true, điều này sai với kết quả ta mong đợi vì trong $subject không tồn tại dấu chấm .. Lý do Regular Expression đã hiểu nhầm ký tự dấu chấm ở $pattern là đại diện cho ký tự bất kì nên nó so khớp với $subject là hoàn toàn đúng.
Những ký tự như dấu chấm ., mở ngoặc và đóng ngoặc vuông [], hoặc những ký tự liên quan đến quy tắc của Regular Expression đều được quy về dạng ký tự đặc biệt trong Regular Expression. Vì thế để phân biệt giữa ký tự đặc biệt Regex và ký tự bình thường thì ta thêm dấu \ vào đầu ký tự đó.
Như ở ví dụ trên tôi sẽ code lại như sau:
1
2
3
4
5
6
// Dấu chấm là ký tự đặc biệt trong regex nên phải thêm dấu \
$partern = '/\./';
$subject = 'demo';
if (preg_match($partern, $subject)){
    echo 'Chuỗi regex so khớp';
}
Chạy lên kết quả như mong đợi đó là trả về FALSE vì $subject = 'demo', nếu bạn đổi giá trị $subject = '.' thì kết quả sẽ TRUE.

3. Regex A hoặc B

Giả sử tôi cần kiểm tra $subject = 'A' hoặc bằng $subject = 'B' sẽ trả về đúng thì ta dùng dấu |, đây là kí hiệu biểu diễn mối quan hệ OR.
1
2
3
4
5
$pattern = '/^A|B$/';
$subject = 'A';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}
Kết quả sẽ trả về TRUE. Bây giờ bạn thử thay đổi giá trị của $subject và kiểm tra lại nhé.

4. Gom nhóm Regex lại

Đôi lúc ta muốn gom nhóm Regex lại cho dễ nhìn, việc này đơn giản ta chỉ cần đặt đoạn mã Regex bên trong cặp đóng và mở (). Khi sử dụng gom nhóm thì việc so khớp vẫn bình thường, tuy nhiên với kết quả về của biến $matches thì sẽ có sự thay đổi và chi tiết thế nào thì ở phần Capturing Group dưới đây mình sẽ đề cập tới.
Ví dụ:
1
2
3
4
5
6
// Gom nhóm A hoặc B lại thành 1 nhóm
$pattern = '/(A|B)/';
$subject = 'A';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

5. Regex kiểm tra chiều dài không giới hạn

Ở bài trước ta đã được học để xác định chiều dài cho chuỗi Regex $pattern thì dùng cú pháp {min,max}, tuy nhiên vẫn còn một số quy tắc ngắn gọn hơn đó là sử dụng các ký tự *+? để thiết lập chiều dài cho chuỗi.

Ký tự *

Đại diện cho không hoặc nhiều ký tự.
Ví dụ: kiểm tra chuỗi có phải trống hoặc là gồm các chữ cái thường.
1
2
3
4
5
6
// Chuỗi có phải trống hoặc có những chữ cái in thường
$pattern = '/[a-z]*/';
$subject = 'dsada';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}
Với bài này ta cũng có thể giải cách sau:
1
2
3
4
5
6
// Chuỗi có phải trống hoặc có những chữ cái in thường
$pattern = '/[a-z]{0,}/';
$subject = 's';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Ký tự +

Đại diện cho 1 hoặc nhiều ký tự.
Ví dụ: kiểm tra chuỗi có ít nhất một chữ thường
1
2
3
4
5
6
// chuỗi ít nhất có 1 ký tự chữ thường
$pattern = '/[a-z]+/';
$subject = 's';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}
Với bài này ta có thể giải bằng cách sau:
1
2
3
4
5
6
// chuỗi ít nhất có 1 ký tự chữ thường
$pattern = '/[a-z]{1,}/';
$subject = 's';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Ký tự ?

Đại diện cho một hoặc không có ký tự nào
Ví dụ:
1
2
3
4
5
6
// chuỗi có 1 hoặc không có ký tự thường nào
$pattern = '/[a-z]?/';
$subject = 's';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}
Bài này ta có thể giải bằng cách sau:
1
2
3
4
5
6
// chuỗi có 1 hoặc không có ký tự thường nào
$pattern = '/[a-z]{0,1}/';
$subject = 's';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

6. Regex phủ định - NOT

Ta dùng ký tự ^ để phủ định một Regex nào đó, ví dụ trả về đúng nếu $subject không phải là số.
1
2
3
4
5
6
// Chuoi không có ký tự số
$pattern = '/[^0-9]{1,2}/';
$subject = 'sd';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

Các ký tự Regex đặc biệt

Danh sách các ký tự Regex đặc biệt như sau:
  • \d - Chữ số bất kỳ ~ [0-9]
  • \D - Ký tự bất kỳ không phải là chữ số (ngược với \d) ~ [^0-9]
  • \w - Ký tự từ a-z, A-Z, hoặc 0-9 ~ [a-zA-Z0-9]
  • \W - Ngược lại với \w (nghĩa là các ký tự không thuộc các khoảng: a-z, A-Z, hoặc 0-9) ~[^a-zA-Z0-9]
  • \s - Khoảng trắng (space)
  • \S - Ký tự bất kỳ không phải là khoảng trắng.
Ví dụ: kiểm tra một chuỗi là số hoặc không phải là số
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Kiểm tra là số
$pattern = '/\d/';
$subject = '2';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}
// Kiểm tra không phải là số
$pattern = '/\D/';
$subject = 'dsd';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi regex so khớp';
}

7. Capturing value trong Regular Expression

Capturing value là gì? để trả lời câu hỏi này tôi sẽ đưa ra một đoạn RegEx để mổ xẻ nó nhé $pattern = '/([a-z]+)([0-9]+)/'.
Trong đoạn $pattern trên ta sẽ phân tích thành 3 phần RegEx khác nhau:
  • Phần 1: Là toàn bộ pattern, tức là ([a-z]+)([0-9]+)
  • Phần 2: Là đoạn con nhỏ pattern đầu tiên ([a-z]+)
  • Phần 3: Là đoạn con nhỏ cuối cùng ([0-9]+)
Những phần trên ta gọi là Captering Value Trong Regular Expression.
Ví dụ
1
2
3
4
preg_match('/([a-z]+)([0-9]+)/', 'freetuts2014', $matches);
echo '<pre>';
print_r($matches);
echo '</pre>';
Kết quả:
Array
(
    [0] => freetuts2014
    [1] => freetuts
    [2] => 2014
)
Các bạn thấy:
  • Phần tử thứ nhất tương ứng với Phần 1.
  • Phần tử thứ hai tương ứng với Phần 2
  • Phần tử thứ ba tương ứng với Phần 3
Các bạn phải nắm định nghĩa này nhé, nó rất là quan trong khi các bạn kết hớp xử lý với các hàm như preg_replacepreg_match trong php

8. Greedy trong Regular Expression

Greedy là gì? Cũng như phần Capturing Value tôi sẽ đưa ra một ví dụ cho dễ hiểu nhé.
1
2
3
4
5
// Tìm chuỗi bắt đầu bằng h và kết thúc chữ o
preg_match('/h(.+)o/', 'hello la xin chao', $matches);
echo '<pre>';
print_r($matches);
echo '</pre>';
Chạy và kết quả như sau:
Array
(
    [0] => hello la xin chao
    [1] => ello la xin cha
)
Tại sao kết quả tứ lung tung thế nhỉ. Tại vì nó tìm chữ h và nó thấy chữ h ngay chuỗi hello đầu tiên, tiếp theo là một chuỗi bất kì và kết thúc là chữ o nên nó duyệt và thấy chữ o ở ngay chữ chao nên nó lấy từ đầu đến cuối. Như vậy có cách nào để lấy chứ hello đầu tiên không? Bạn xem code nhé.
1
2
3
4
5
// Tìm chuỗi bắt đầu bằng h và kết thúc chữ o
preg_match('/h(.+?)o/', 'hello la xin chao', $matches);
echo '<pre>';
print_r($matches);
echo '</pre>';
Chạy lên kết quả sẽ là:
Array
(
    [0] => hello
    [1] => ell
)
Đây chính là kết quả ta mong đợi, sự khác biệt giữa 2 cách là ở cách 2 tôi thêm dấu ? ở kế ngay dấu + ở chuỗi pattern.
Vậy Greedy trong Regular Expression là tính chất tham ăn, nó lấy hết cho tới khi gặp ký tự cuối cùng. Và ta dùng dấu ? đặt sau các Regex để khắc phục tình trạng Greedy.

9. Các ví dụ về Regular Expression

Kiểm tra chuỗi có phải định dạng năm hay không?

1
2
3
4
5
6
7
8
// Pattern đúng nếu là chữ A hoặc chữ B
// Kiểm tra một chuỗi có phải là định dạng năm không,
// Gợi ý: Định dạng năm có 4 ký tự số
$pattern = '/^\d{4}$/';
$subject = '1234';
if (preg_match($pattern, $subject)){
    echo 'Năm đúng';
}

kiểm tra chuỗi có phải là số hay không

1
2
3
4
5
$pattern = '/^[0-9]+$/';
$subject = '1234';
if (preg_match($pattern, $subject)){
    echo 'Chuoi la số';
}

kiểm tra chuỗi phải là dạng ngay/thang/nam hay không

Vú dụ ngày 20/06/2015,
1
2
3
4
5
$pattern = '/^[0-9]{2}/[0-9]{2}/[0-9]{4}$/';
$subject = '12/10/2014';
if (preg_match($pattern, $subject)){
    echo 'Đúng định dạng ngày tháng năm';
}

Kiểm tra chỗi là ABC hoặc CDF

1
2
3
4
5
6
// Chuỗi là ABC hoặc là CDF
$pattern = '/^(ABC)|(CDF)$/';
$subject = 'ABC';
if (preg_match($pattern, $subject)){
    echo 'Chuỗi so khớp';
}
Ví du này tôi đã dùng kỹ thuật gom nhóm Capturing Value và dùng toáng tử OR.

10. Lời kết

Như vậy là mình đã giới thiệu với các bạn đầy đủ các quy tắc căn bản thường sử dụng trong Regular Expression, vẫn còn một số kiến thức nâng cao nữa như LookAhead, LookBehind, Condition, ... nhưng mình sẽ giới thiệu mở một bài khác. Và mình nhắc luôn là phần Greedy và Capturing Value rất là quan trọng nên bắt buộc bạn phải hiểu nó thì ở các bài tiếp theo mới tiếp thu bài học được.

Comments

Popular Posts