TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI
VIỆN CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG
BÀI TIỂU LUẬN
BIỂU THỨC CHÍNH QUY VÀ THƯ VIỆN BIỂU THỨC CHÍNH QUY
CỦA CÁC NGÔN NGỮ LẬP TRÌNH PHP, JAVA, .NET
Giáo viên hướng dẫn:
TS. Nguyễn Hữu Đức
Học viên thực hiện :
Đỗ Ngọc Phục
CB120105
Lê Gia Vĩnh
CB120125
Nguyên Hồng Tâm
CB120110
Lớp
:
Công nghệ thông tin 2 (KT)
Nếu sử dụng tốt những kỹ năng về regular expression. Chúng sẽ đơn giản hơn
nhiều trong lập trình và quá trình xử lý văn bản, và có những vấn đề sẽ không thể
giải quyết được nếu không sử dụng regular expression. Sẽ cần đến hàng trăm thủ tục
để trích xuất tất cả các địa chỉ email từ một số tài liệu, đây có thể nói là một việc làm
tẻ nhạt và vất vã. Nhưng với regular expression thì chỉ cần một số dòng lệnh hoặc
thậm chí một dòng lệnh để làm việc này.
Tuy nhiên, Regular expression cũng thường đem đến cho người sử dụng
những phiền toái không mong muốn như: sử dụng một biểu thức chính quy không
phù hợp với biểu thức muốn tìm, hoặc số văn bản tìm được với biểu thức chính quy
đó không phù hợp… Để sử dụng regular expression cần phải có một kiến thức từ cơ
bản đến nâng cao về những biểu thức và cách thức hoạt động của nó trong các ngôn
ngữ lập trình.
Regular expression là một công cụ mạnh mẽ trong việc thao tác và trích xuất
văn bản trên máy tính. Do đó nắm vững các biểu thức chính quy sẽ giúp tiết kiệm
nhiều thời gian và công sức.
Có thể hiểu đơn giản Regex là 1 cái mẫu (pattern) dùng để mô tả 1 lớp ký tự
nào đó.
VD: lazydog là 1 regex. Nó là 1 mẫu đơn giản nhất vì nó so khớp (match) với đoạn
văn bản lazydog. 1 match là 1 đoạn văn bản được so khớp với mẫu.
VD phức tạp hơn 1 chút: \b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b Đây là mẫu
mô tả 1 địa chỉ email. Mẫu này có thể được dùng để tìm 1 địa chỉ email trong 1 đoạn
văn bản, hoặc kiểm tra xem 1 chuỗi có phải là địa chỉ email hợp lệ hay không.
Regex có thể được sử dụng với bất kỳ dữ liệu nào mà ta có thể truy cập, thông
qua ứng dụng hoặc ngôn ngữ lập trình. Có thể kể đến 1 số ứng dụng xử lý văn bản
hỗ trợ regex: PowerGREP, EditPad Pro, RegexBuddy,…
II.
metacharacter.
Nếu cần dùng các ký tự này với ý nghĩa thông thường, ta phải giải phóng nó
bằng ký tự \. VD nếu cần so khớp 1+1=2, thì regex đúng sẽ là 1\+1=2. Chú ý
rằng 1+1=2 cũng là regex đúng, nên sẽ không báo lỗi, nhưng nó sẽ không cho ta kết
quả như mong muốn. Regex 1+1=2 sẽ so khớp với 111=2 trong chuỗi 123+111=234,
vì dấu + ở đây mang ý nghĩa đặc biệt.
Nếu ta quên không giải phóng ký tự đặc biệt ở những chỗ nó không được phép
đứng thì sẽ gặp lỗi. VD: +1
Hầu hết các loại cú pháp regex đều coi { như 1 ký tự thông thường, trừ khi nó
là 1 phần của toán tử nhắc lại (repetition operator), VD: {1, 3}. Vì vậy ta không cần
giải phóng ký tự này.
Ta chỉ dùng \ để giải phóng các ký tự đặc biệt, còn các ký tự khác thì không
nên, vì \ cũng là 1 ký tự đặc biệt. \ khi kết hợp với 1 ký tự thông thường sẽ có ý
nghĩa đặc biệt, VD: \d sẽ so khớp với 1 chữ số từ 0 - 9.
Tất cả các loại cú pháp regex đều cho phép giải phóng 1 ký tự đặc biệt bằng \.
Rất nhiều cú pháp khác còn hỗ trợ kiểu giải phóng \Q... \E. Tất cả các ký tự nằm
trong cặp \Q và \E sẽ được coi như ký tự thông thường. VD: \Q*\d+*\E sẽ so khớp
với đoạn văn bản *\d+* . Kiểu cú pháp này được hỗ trợ bởi JGsoft engine, Perl,
PCRE, ...
c. Ký tự đặc biệt và ngôn ngữ lập trình
Khác với trong ngôn ngữ lập trình, trong regex, ký tự ' và " không phải là ký
tự đặc biệt. Vì vậy, không cần phải giải phóng nó.
Trong mã nguồn của 1 chương trình, cần luôn ghi nhớ những ký tự nào được
ngôn ngữ lập trình xử lý đặc biệt. Bởi vì những ký tự này sẽ được trình biên dịch xử
lý trước khi được engine regex xử lý. VD: regex 1\+1=2 phải được viết thành
1\\+1=2 trong mã nguồn C++. Trình biên dịch C++ sẽ chuyển \\ thành \ trong chuỗi
trên, sau đó nó mới được chuyển đến regex engine. VD khác: đế so khớp c:\temp,
cần dùng regex c:\\temp (vì \t trong regex mang ý nghĩa đặc biệt). Và trong mã
nguồn C++, regex này cần được viết là c:\\\\temp.
quantifiers, backreferences,...
Có thể dễ dàng kiểm tra xem loại cú pháp đang sử dụng thuộc về engine nào
qua việc kiểm tra xem lazy quantifiers và backreferences có được hỗ trợ không. Hãy
thử dùng biểu thức regex regex|regex not vào chuỗi regex not. Nếu kết quả so khớp
là regex, thì engine đang dùng thuộc loại regex-directed. Nêu kết quả là regex not,
thì engine thuộc loại text-directed.
Trong các VD ở các bài tiếp theo, ta sẽ phân tích cụ thể cách thức làm việc
của regex engine, qua đó giúp sử dụng regex hiệu quả nhất và tránh mắc lỗi.
Regex-directed engine luôn trả về kết quả so khớp bên trái nhất, thậm chí nếu
1 match tốt hơn có thể được tìm thấy nếu tiếp tục so khớp. Đây là điều cần ghi nhớ.
Regex-directed engine luôn bắt đầu so khớp với ký tự đầu tiên của chuỗi.
Hãy lấy 1 VD đơn giản nhất để minh hoạ: ta dùng regex cat vào chuỗi “He
captured a catfish for his cat”. Engine sẽ bắt đầu so khớp dấu hiện đầu tiên trong
regex là c với ký tự đầu tiên của chuỗi là H. Không khớp, vì vậy nó tiếp tục lần lượt
so khớp với ký tự thứ 2 và 3 là e và space. Đều không khớp, đến ký tự thứ 4, c đã
khớp với c. Xong, giờ engine bắt đầu so khớp dấu hiệu thứ 2 trong regex là a với ký
tự thứ 5 của chuỗi là a, khớp, nhưng đến dấu hiệu thứ 3 của regex là t thì không
khớp với ký tự thứ 6 của chuỗi là p. Lúc này engine nhận ra rằng không thể tìm ra 1
match bắt đầu từ ký tự thứ 4 của chuỗi. Vì vậy, nó bắt đầu lại công việc từ đầu, từ ký
tự thứ 5 của chuỗi, regex c không khớp với a. Cứ tiếp tục như vậy cho đến ký tự thứ
15 của chuỗi, regex c đã khớp với c. Engine lần lượt so khớp các dấu hiệu còn lại
trong regex với các ký tự tiếp theo trong chuỗi: a khớp a, t khớp t. Và như vậy 1
match đã được tìm thấy bắt đầu từ ký tự 15. Engine sẽ trả về kết quả và ngừng luôn,
không tiếp tục tìm xem còn match nào tốt hơn không (VD: cat ở cuối chuỗi).
4. Lớp ký tự (Character Classes - Character Sets)
a. Lớp ký tự
Sử dụng lớp ký tự, ta sẽ khiến regex engine chỉ chọn ra 1 ký tự để so khớp. Để
mà nó sẽ không có ý nghĩa đặc biệt. Ta nên dùng cách thứ 2 để biểu thức regex trông
dễ nhìn hơn như sau:
•
Với ^, đặt nó ở bất kỳ chỗ nào trừ vị trí ngay sau [ . VD: [x^] sẽ khớp
với x hoặc ^.
•
Với ], đặt nó ngay sau [ hoặc [^ . VD: []x] sẽ khớp với ] hoặc x. [^]x] sẽ
khớp với bất kỳ ký tự nào không phải là ] hoặc x.
•
Với -, đặt nó ngay sau [ hoặc [^ , hoặc ngay trước ]. VD: cả [-x] và [x-] đều
so khớp với - hoặc x.
Có thể sử dụng tất cả các ký tự không in được trong lớp ký tự giống như dùng
chúng ngoài lớp ký tự. VD: [$\u20AC] sẽ khớp với $ hoặc ký tự đồng euro (với giả
định cú pháp regex đang dùng hỗ trợ unicode).
JGsoft engine, Perl và PCRE còn hỗ trợ kiểu \Q…\E trong lớp ký tự để giải
phóng 1 chuỗi ký tự. VD: [\Q[-]\E] sẽ khớp với [ hoặc - hoặc ].
Cú pháp regex của POSIX lại xử lý \ trong lớp ký tự như 1 ký tự thông
thường. Đồng nghĩa với việc ta không thể dùng \ để giải phóng ] ^ -. Để làm việc này
ta chỉ còn cách đặt chúng vào các vị trí như trình bày ở trên. Ngoài ra điều này cũng
đồng nghĩa với việc các cú pháp tắt (shorthand, VD: \d) không còn hiệu lực.
d. Lớp ký tự viết tắt (Shorthand Character Classes)
lớp ký tự chứ không chỉ nhắc lại ký tự mà nó so khớp. VD: regex [0-9]+ sẽ khớp với
cả 837 lẫn 222.
Nếu muốn nhắc lại chỉ các ký tự được so khớp, ta cần dùng tham chiếu ngược
(backreferences). ([0-9])\1+ sẽ khớp với 222 chứ không khớp với 837. Khi áp dùng
regex này vào chuỗi 833337, nó sẽ khớp với 3333.
g. Ký tự chấm (Dot)
Ký tự Dot khớp với hầu hết các ký tự. Trong biểu thức regex, dấu . là
metacharacter được sử dụng nhiều nhất, và cũng là ký tự bị sử dụng sai nhiều nhất.
Dấu . khớp với 1 ký tự đơn bất kỳ ngoại trừ ký tự newline. Vì vậy, dấu . tương
đương với [^\n] (trong UNIX) hoặc [^\r\n] (trong Windows).
Trong Perl, dấu . có thể khớp với cả newline nếu ta dùng chế độ “single-line
mode”. Để sử dụng chế độ này, ta thêm s vào sau biểu thức regex, VD: m/^regex$/s;
JavaScript và VBScript không có chế độ nào hỗ trợ Dot so khớp với các ký tự
line break. Vì vậy, để so khớp với bất kỳ ký tự nào ta phải dùng [\s\S] thay cho
Dot. [\s\S] so khớp với 1 ký tự là ký tự trắng (bao gồm cả các ký tự line break) hoặc
không phải ký tự trắng, nghĩa là nó so khớp với bất kỳ ký tự nào.
Dấu . là 1 metacharacter đầy uy lực. Nó có thể khớp với bất kỳ ký tự nào,
nhưng cũng có thể khớp với ký tự mà ta không muốn. Những trường hợp như thế có
thể rất khó nhận ra.
Hãy lấy 1 VD đơn giản để minh hoạ: giả sử ta muốn tìm 1 chuỗi ngày tháng
năm dưới dạng mm/dd/yy, trong đó dấu phân cách ngày tháng năm ta để người dùng
tuỳ chọn. Giải pháp nhanh nhất là \d\d.\d\d.\d\d, trông có vẻ ổn. Nó sẽ khớp 1 chuỗi
kiểu như 02/12/03. Vấn đề là 1 chuỗi kiểu như 02512703 cũng được coi là 1 ngày
hợp lệ với regex trên (chấm thứ 1 khớp với 5, chấm thứ 2 khớp với 7).
Giải pháp tốt hơn là: \d\d[- /.]\d\d[- /.]\d\d. Regex này cho phép – hoặc space
hoặc . hoặc / làm dấu phân cách ngày tháng năm. Lưu ý rằng dấu . trong lớp ký tự là
1 ký tự thông thường, do đó không cần phải giải phóng. Nhưng regex này vẫn chưa