1. Regular Expressions
1. Regular expression là gì?
+ Biểu thức chính quy.
+ Hiểu nôm na là 1 chuỗi có quy tắc để mô tả những chuỗi(string) khác
2. Cú pháp cơ bản (ở đây mình chỉ trình bày và ví dụ cho ngôn ngữ php)
Example:
PHP Code:
<?php
$re = '/hello/'; // biểu thức chính quy cho một string có chuỗi “hello” ở trong đó.
$str = 'hello world';
if(preg_match($re, $str)) {
echo 'Yes';
}
?>
Output: Yes
+ Ký hiệu “^” và “$”: bắt đầu và kết thúc 1 string
Example:
PHP Code:
<?php
$re = '/^hello/'; // biểu thức chính quy cho một string bắt đầu bởi chuỗi “hello”
$str = 'hello world';
if(preg_match($re, $str)) {
echo 'Yes';
}
else {
echo 'No';
}
?>
Output: Yes
PHP Code:
<?php
$re = '/hello$/'; // biểu thức chính quy cho một string kết thúc bởi chuỗi “hello”
$str = 'hello world';
if(preg_match($re, $str)) {
echo 'Yes';
}
else {
echo 'No';
}
?>
Output: No
+ Ký hiệu: “*”, “+”, “?”
$re = '/^ab*$/' ; // biểu thức chính quy cho một string bắt đầu bởi a, và kết thúc là 0 hoặc
2. nhiều b (ví dụ: a, ab, abb, abbb, …);
$re = '/^ab+$/' ; // biểu thức chính quy cho một string bắt đầu bởi a, và kết thúc là 1 hoặc
nhiều b (ví dụ: ab, abb, abbb, …);
$re = '/^ab?$/' : // biểu thức chính quy cho một string bắt đầu bởi a, và kết thúc là b hoặc
là không (ví dụ: ab hoặc a).
Example:
PHP Code:
<?php
$re = '/^ab*$/';
$str = 'abbc';
if(preg_match($re, $str)) {
echo 'Yes';
}
else {
echo 'No';
}
?>
Output: No
+ Sử dụng: {}:
$re = '/^ab{2}$/'; // biểu thức chính quy cho một string bắt đầu bởi a, và kết thúc là 2 chũ
b (là abb);
$re = '/^ab{2,}$/'; // biểu thức chính quy cho một string bắt đầu bởi a, và kết thúc là ít
nhất 2 chũ b (ví dụ: abb, abbb, abbbb, …);
$re = '/^ab{2,5}$/'; // biểu thức chính quy cho một string bắt đầu bởi a, và kết thúc là ít
nhất 2 chũ b và nhiều nhất là 5 chữ b (ví dụ: abb, abbb, abbbb, abbbbb);
+ Sử dụng : () và |
$re = '/^a(bc)*$/'; // biểu thức chính quy cho một string bắt đầu bởi a, và kết thúc là 0
hoặc nhiều 'bc' (ví dụ abc, abcbc, abcbcbcbc, …)
$re = '/^a(b|c)*$/'; // biểu thức chính quy cho một string bắt đầu bởi a, và kết thúc là 0
hoặc nhiều 'b' hoặc nhiều 'c' hoặc 'b' 'c' lẫn lộn :D (ví dụ abc, abbcccccccccc,
abccccbbbcbc, …)
+ Sử dụng symbol '.': đại diện cho một ký tự đơn bất kỳ
$re = '/^.{3}$/'; //Biểu thức chính quy cho một chuỗi có đúng 3 ký tự bất kỳ.
PHP Code:
<?php
$re = '/^.{3}$/';
//Biểu thức chính quy cho một chuỗi có đúng 3 ký tự bất kỳ.
$str = '&#%';
if(preg_match($re, $str)) {
echo 'Yes';
}
else {
3. echo 'No';
}
?>
Output: Yes
+ Sử dụng: '-':
[0-9] : Một chữ số
[a-zA-Z]: một ký tự A->Z, a->z
[a-d] : ~ (a|b|c|d)
[^a-zA-Z]: một ký tự không phải là A->Z, a->z
[^0-9]: một ký tự không phải là số
+ Sử dụng: ''
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.
3. Các hàm cơ bản vận dụng regular expression
+ preg_match : http://php.net/manual/en/function.preg-match.php
int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int
$offset = 0 ]]] )
Cơ bản là để tìm kiếm 1 string có làm việc theo một $re.
Ví dụ:
PHP Code:
<?php
$re = '/^w+$/';
// một string toàn ký tự A->Z, a->z, 0->9
$str = 'quya*';
if(preg_match($re, $str)) {
echo 'Yes';
}
else {
echo 'No';
}
?>
Output: No
+ preg_replace: http://www.php.net/manual/en/function.preg-replace.php
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit
= -1 [, int &$count ]] )
Cơ bản là để tìm kiếm trong 1 string những chuỗi có cấu trúc theo $re để thay thế
Ví dụ:
4. PHP Code:
<?php
$re = '/w+$/';
$str = '*quya';
echo preg_replace($re, 'hi', $str);
?>
Output: *hi
+ preg_split: http://php.net/manual/en/function.preg-split.php
array preg_split ( string $pattern , string $subject [, int $limit = -1 [, int $flags = 0 ]] )
Ví dụ:
Các bạn xem tạm ví dụ ở trong php manual nhé. Mệt rùi.
Để viết ra bài này, mình đã tham khảo nhiều nguồn trên mạng, và kiến thức từ những lần
sử dụng regular expressions.
Có zì sai hay chưa đủ thì các bạn pm để mình bổ xung thêm.
Giới thiệu Regular Expression Nâng cao
I. Thêm comment vào biểu thức Regular Expression:
Trong trường hợp biểu thức chính quy của bạn quá dài. Và muốn sau này người dùng sau
có thể hiểu và chỉnh sửa lại (reuse), thì chúng ta cần phải ghi chú lại. hì hì.
Để thêm comment vào 1 biểu thức Regular Expression hiện giờ có 2 cách thì phải.
- Sử dụng cú pháp (?# nội dung ghi chú).
Ví dụ: kiểm tra ngày có đúng định dạng dd/mm/yyyy không ta có parttern sau:
Mã:
$parttern = “/^d{2}/d{2}}/d{4}$/” //cái này ngắn quá
tham khảo thôi.
Sau khi thêm comment vào:
Mã:
$parttern = “/^d{2}(?# ngày là số nguyên 2 kí
tự)/d{2}(?# Tháng là số nguyên 2 kí tự)/d{4}(?# Năm là
số nguyên 4 kí tự)$/”
- Cách này nhìn sáng sủa và dễ hiểu hơn:
Mã:
$parttern = “/^
d{2} # ngày là số nguyên 2 kí tự
/ # dấu phân cách “/”
d{2} # Tháng là số nguyên 2 kí tự
/ # dấu phân cách “/”
d{4} # Năm là số nguyên 4 kí tự
$/x”; // Sử dụng modifier là x.
5. Khi sử dụng modifier x thì khoảng trắng trong biểu thức chính quy sẽ được bỏ qua, nếu
không nó sẽ được escaped (s). Điều này dễ dàng để thêm comment. Comment sẽ bắt đầu
bằng dấu “#” và kết thúc tại 1 dòng mới.
II. Greedy và Ungreedy:
Để hiểu được phần này bạn cần xem qua ví dụ sau:
Ví dụ: Mình có 1 đoạn mã html sau khi view source của 1 trang web
Mã:
<html>
<head><title>Greedy và Ungeedy</title></head>
<body>
<p id=”weather”>
Nội dung cần lấy nằm ở đây
</p>
<p>
Nội dung khác
</p>
</body>
</html>
Vậy để lấy nội dung trong thẻ <p id=”weather”></p>, mình sẽ phải viết biểu thức sau:
PHP Code:
$parttern = “/<p id=”weather”>(.*)</p>/s”;
if(preg_match($parttern, $html, $match))
{
print_r($match);
}
Hì hì, kết quả thế nào các bạn sẽ lấy được nội dung cần lấy chứ.
Mã:
Array (
[0] =>
Nội dung cần lấy ở đây
Nội dung khác
[1] =>
Nội dung cần lấy ở đây
Nội dung khác
)
Như vậy, nội dung mình cần lấy vẫn chưa lấy được, tìm hiểu nguyên nhân tại sao:
Xem lại $parttern của mình: $parttern = “/<p id=”weather”>(.*)</p>/s”;
Mô tả parttern: bắt đầu bằng thẻ p có id=”weather”, tiếp sau là nội dung bắt kì có thể có
nhiều hoặc không có kí tự nào và kết thúc là html tag </p>.
Khi dùng modifier s: cho biết so khớp parttern với mọi kí tự kể cả kí tự xuống dòng, nếu
không có nó kí tự xuống dòng sẽ bị loại trừ.
6. - Mặc định trong so khớp mẫu thì nó sẽ lấy thẻ p có id = "weather" và nội dung bất kì bên
trong đến khi gặp tag đóng </p>. Nhưng phải là tag đóng </p> cuối cùng thì nó mới dừng
lại (Greedy, tiếng việt là tham lam, tham ăn gì đó) so tới cuối luôn đó mà.
- Vậy làm sao để parttern hiểu rằng khi so khớp mình chỉ lấy nội dung bất kì giữa 2 tag
<p id="weather">nội dung đến tag đóng </p> đầu tiên thôi. Lúc đó, mình sẽ dùng kí tự
dấu hỏi "?" (Ungreedy) để thêm vào sau quantifier của parttern muốn so khớp. Hì hì Cái
quantifier tham khảo lại nha, vì bài này đang hướng dẫn Advanced Regular Expression
mà.
- Vì vậy để lấy được nội dung chính xác với parttern của mình
Mã:
$parttern = '/<p id="weather">(.*)</p>/s';
thì mình chỉ cần thêm dấu hỏi sau quantifier "*" là xong.
Mã:
$parttern = '/<p id="weather">(.*?)</p>/s';
Chạy lại code
PHP Code:
$parttern = “/<p id=”weather”>(.*?)</p>/s”;
if(preg_match($parttern, $html, $match))
{
print_r($match);
}
bạn sẽ nhận được kết quả như ý
Mã:
Array (
[0] =>
Nội dung cần lấy ở đây
[1] =>
Nội dung cần lấy ở đây
)
III. Lookahead and Lookbehind Assertions (được gọi chung là lookaround)
1. Positive and Negative Lookahead:
- cú pháp: positive lookahead (?=), negative lookahead (?!)
- Bạn có thể xem qua ví dụ để hiểu rõ hơn:
Mã:
$parttern = "/hellos*(?=world)/";
- Ý nghĩa: đầu tiên mẫu sẽ so khớp bắt đầu bằng hello, tiếp theo sẽ là có thể có nhiều
hoặc không có khoảng trắng, và cuối cùng theo sau nó có phải là world không. Các bạn
thấy đó, $parttern để thực hiện mục đích này không thật sự hữu dụng vì sao, chúng ta
không cần biết lookahead thì vẫn thực hiện được với:
7. Mã:
$parttern = "/hellos*world/";
điều này là sai lầm. hì hì. Nếu bạn chỉ muốn tìm xem chuỗi có đúng như vậy không, chỉ
nhận về kết quả true hoặc false là có thể bạn cho là sử dụng lookahead lúc này là không
có khả dụng. Tuy nhiên, nếu đi sâu vào hơn, bạn sử dụng kết quả trả về của code:
PHP Code:
$chuoi = "hello world";
$parttern = "/hellos*(?=world)/";
$parttern1 = "/hellos*world/";
if(preg_match($parttern, $chuoi))
{
echo "valid";
}
// tương đương với
if(preg_match($parttern1, $chuoi))
{
echo "valid";
}
Nhưng nếu bạn muốn sử dụng kết quả trả về. Ở đây mình lấy ví dụ của bài đầu Greedy và
Ungreedy
Mã html sau khi viewsource 1 trang web
PHP Code:
//tạo biến $html giữ nội dung trang web.
$html = '<html>
<head><title>Greedy và Ungeedy</title></head>
<body>
<p id=”weather”>
Nội dung cần lấy nằm ở đây
</p>
<p>
Nội dung khác
</p>
</body>
</html>';
và nội dung trả về sau khi thực hiện đoạn code là gì nhỉ các bạn
PHP Code:
$parttern = “/<p id=”weather”>(.*?)</p>/s”;
if(preg_match($parttern, $html, $match))
{
print_r($match[0]);
}
8. kết quả:
Mã:
Nội dung cần lấy ở đây
Có phải nội dung bạn cần lấy là nội dung nằm giữa thẻ <p id="weather"> và thẻ đóng
</p>. Nhìn kết quả xuất ra khi dùng print_r($match[0]). các bạn cho rằng là đúng. Mình
cũng thấy đúng mà, nhưng khi bạn view source thử xem, kết quả thế nào nhỉ. không tồi,
hihi
kết quả viewsource
Mã:
<p id="weather">
Nội dung cần lấy nằm ở đây
</p>
Kết quả là nó lấy luôn thẻ p đóng và mở àh các bạn, vậy làm sao mẫu so khớp vẫn kiểm
tra đúng mà kết quả trả về, mình sẽ loại bỏ cái tag p đóng và mở àh nhỉ.
--> lookahead and lookbehind was born. (rồi thấy được công dụng nha các bạn).
- Sửa lại cấu trúc $parttern
PHP Code:
$parttern_old = '/<p id="weather">(.*?)</p>/s';
$parttern_new = '/(?<=<p id="weather">)(.*?)(?=</p>)/s';
- Giải thích $parttern_new: lấy nội dung bất kì mà theo trước nó là tab mở <p
id="weather"> và theo sau nó là tag đóng </p> (Không bao gồm tag mở và đóng nha
bạn).
2. Positive and Negative Lookbehind:
- Cú pháp: positive lookbehind (?<=), negative lookbehind.
- Mình chỉ đưa ra ví dụ và nêu ý nghĩa, giống ở trên các bạn tìm hiểu nha.
(?<!)
IV. Conditional (If-Then-Else) Patterns
- Regular Expression cung cấp cho chúng ta chức năng kiểm tra điều kiện.
- Cú pháp: (?(condition)true-pattern|false-pattern) hoặc (?(condition)true-pattern)
- Giải thích: nếu condition đúng thì so khớp true-parttern, ngược lại so khớp với false-
parttern.
ví dụ:
PHP Code:
// nếu bắt đầu với h thì , so khớp he
// ngược lại so khớp với she
$pattern = '/^(?(?=h)he|she)/';
preg_match($pattern, 'he'); // true
preg_match($pattern, 'she'); // true
preg_match($pattern, 'shoes'); // false
9. - chú ý: điều kiện condition có thể là 1 con số. Trong trường hợp này, nó sẽ tham chiếu
đến capture subpartter ở phía trước nó.
ví dụ:
PHP Code:
// parttern (a)?b(?(1)c|d) khớp với bd và abc. nhưng nó khô
ng khớp với bc
$parttern = "(a)?b(?(1)c|d)";
preg_match($pattern, 'bd'); // true
preg_match($pattern, 'abc'); // true
preg_match($pattern, 'bc'); // false
- Phân tích: $pattern = "(a)?b(?(1)c|d)";
bắt đầu bằng 1 subparttern (a) hoặc không có theo sau là b và cuối là 1 parttern if-else
condition (?(1)c|d).
- Giải thích:
PHP Code:
$parttern = "(a)?b(?(1)c|d)";
preg_match($pattern, 'bd');
/* so khớp parttern với chuỗi 'bd'.
ta thấy, bắt đầu bằng b vì subpartter a có thể có hoặc khô
ng,
tiếp đến là condition số 1 sẽ tham chiếu tới subparrtern a
ở phía trước,
vì không có a nên (?(1)c|d) sẽ trả về kết quả false,
nên biểu thức tiếp tục so khớp với d.
và kết thúc chuỗi là d nên thỏa => kết quả true
*/
V. Non-capturing Subpatterns
- Cú pháp: (?:subparttern)
- Subparttern kèm theo dấu ngoặc đơn, được lưu giữ vào 1 mảng để chúng ta có thể sử
dụng chúng sau này khi cần thiết. Nhưng đối với những biểu thức chúng ta chỉ muốn giữ
những giá trị của subparttern cần thiết, và không lưu lại những giá trị của parttern khác,
chúng ta sẽ sử dụng cú pháp ở trên (?:subparttern) (gọi là non - capturing subparttern).
- Các bạn xem ví dụ sau để dễ hiểu hơn:
PHP Code:
preg_match('/(f.*)(b.*)/', 'Hello foobar', $matches);
echo "f* => " . $matches[1]; // prints 'f* => foo'
echo "b* => " . $matches[2]; // prints 'b* => bar'
- Thay đổi 1 chút bằng cách thêm 1 subparttern (H*) ở phía trước
PHP Code:
10. preg_match('/(H.*) (f.*)(b.*)/', 'Hello foobar', $matches);
echo "f* => " . $matches[1]; // prints 'f* => Hello'
echo "b* => " . $matches[2]; // prints 'b* => foo'
- Mảng $matches lúc này đã thay đổi, giá trị của subparttern (H*) được giữ lại và tương
ứng với chỉ số của mảng $matches là $matches[1]. Chúng ta thực sự không muốn giữ lại
giá trị của subparttern này chúng ta chỉ cần làm cho nó thành non - capturing subparttern
bằng cách:
PHP Code:
preg_match('/(?:H.*) (f.*)(b.*)/', 'Hello foobar', $matches
);
echo "f* => " . $matches[1]; // prints 'f* => foo'
echo "b* => " . $matches[2]; // prints 'b* => bar'
VI. Named Subpatterns
- Cú pháp: (?P<named_subparttern>subpattern)
- sau khi lấy giá trị của subparttern gán vào biến $matches, nếu chúng ta muốn truy xuất
đến các giá trị của subpattern bằng tên thì chúng ta sử dụng cú pháp trên. Việc sử dụng
tên của subpattern sẽ dễ dàng cho chúng ta quản lý và truy xuất hơn.
- Các bạn xem ví dụ sau:
PHP Code:
$html='
ip: 192.168.1.1
name: truong-pc
ip: 192.168.1.2
name: thuy
';
$patternip = '/(?P<ip>(?<=ip:)s*d+.d+.d+.d+)/';
preg_match_all($patternip,$html, $matches);
echo "<pre>";
print_r($matches);
echo "</pre>";
- Mảng $matches sẽ có giá trị
Mã:
Array
(
[0] => Array
(
[0] => 192.168.1.1
[1] => 192.168.1.2
11. )
[ip] => Array
(
[0] => 192.168.1.1
[1] => 192.168.1.2
)
[1] => Array
(
[0] => 192.168.1.1
[1] => 192.168.1.2
)
)
- Khi đó chúng ta muốn lấy giá trị của subparttern ip chúng ta chỉ cần truy cập
$matches['ip'] là có thể lấy giá trị của ip rồi.