Trích xuất text bất kỳ từ một string cho trước bằng cách sử dụng regex trong Dart
Làm việc và xử lý text là một vấn đề lập trình phổ biến. Tuy nhiên, tôi thường cố gắng tránh sử dụng các regular expressions (regex) vì chúng hoàn toàn không thể đọc được và hơi khó hiểu, khó tiếp thu và khó nhớ:
RegExp _email = RegExp(r"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$");
Tuy nhiên, thỉnh thoảng, việc sử dụng chúng sẽ dễ dàng hơn so với việc bạn phải tìm ra cách để viết một lớp parser để lấy được đoạn text mà bạn mong muốn trong một String cho trước theo quy tắc nào đó.
1. Xác định vấn đề
Giả sử bạn đang cố gắng trích xuất thời gian và lời bài hát từ tệp text LRC:
[00:12.30]Twinkle, twinkle, little star
[00:17.60]How I wonder what you are
[00:21.50]Up above the world so high
[00:25.30]Like a diamond in the sky
Bạn có thể lấy từng dòng khá dễ dàng như thế này:
List<String> rawLines = text.split('\n');
print(rawLines[0]); // [00:12.30]Twinkle, twinkle, little star
Nhưng điều bạn thực sự muốn làm là lấy thời gian và từ ngữ cho từng dòng, đại loại như sau:
final time = Duration(minutes: 0, seconds: 12, milliseconds: 300);
final words = 'Twinkle, twinkle little star';
Điều đó có nghĩa là có bốn nhóm riêng biệt để trích xuất:
Tôi biết có 1 một regex có thể giúp ta làm điều ta mong muốn ở trên. Tuy nhiên, nếu tôi đưa bạn luôn thì bạn không thể nào đọc hiểu được regex đó làm gì. Do đó, chúng ta hãy cùng nhau giải quyết vấn đề từng bước một.
2. Tìm hiểu Regex cơ bản
Để tạo một trình so sánh regex (regex matcher), bạn sử dụng class RegExp
trong Dart.
final regex = RegExp(r'');
Bạn đặt regular expression bên trong dấu ngoặc kép. Sẽ rất hữu ích khi sử dụng một string thô (nghĩa là một string bắt đầu bằng r
) để bạn không phải tránh quá nhiều thứ sau này.
2.1 Match the start and end of the line (Khớp đầu và cuối dòng)
Điều này không hoàn toàn cần thiết, nhưng nếu bạn vẫn đang làm việc với toàn bộ dòng, thì việc so khớp phần đầu và phần cuối của dòng có thể tránh một số bất ngờ mà bạn có thể gặp phải.
^
khớp với đầu dòng$
khớp với cuối dòng
Điều đó có nghĩa là regex của bạn sẽ trông như thế này:
final regex = RegExp(r'^$');
2.2 Match the constant parts (Khớp các phần không đổi)
Các ký tự sẽ không thay đổi trong string là [
, :
, .
, và ]
:
Nhưng [
, .
, và ]
đều có nghĩa đặc biệt trong regex, vì vậy bạn phải thoát khỏi chúng bằng cách thêm tiền tố \
:
\[
\.
\]
Điều đó làm cho regex hiện tại trông như thế này:
final regex = RegExp(r'^\[:\.\]$');
2.3 Match the variable parts
Một lần nữa, bốn nhóm mà bạn muốn nắm bắt là phút(minutes), giây(seconds), giây phân số(fractional seconds) và các từ:
Bạn có thể sử dụng các pattern sau để kiểm tra xem các từ có match với điều kiện của chúng ta hay không:
[0-9]+
— Khớp một hoặc nhiều chữ số. Dấu ngoặc khớp với một trong bất kỳ mục nào trong phạm vi và+
là ký tự đại diện có nghĩa là một hoặc nhiều kết quả phù hợp. (Ngoài ra, bạn có thể sử dụng\d
thay vì[0-9]
, nhưng tôi thấy[0-9]
dễ nhớ hơn.).*
— Khớp 0 hoặc nhiều ký tự. Các.
khớp với bất kỳ ký tự đơn nào và*
là ký tự đại diện để khớp với 0 hoặc nhiều lần xuất hiện của bất kỳ ký tự nào đứng trước nó. Chúng ta sẽ sử dụng điều này cho lời bài hát để cho phép vài bài hát có dòng trống trong khi vẫn chứa time stamp.
Điều đó làm cho regex trông như thế này:
final regex = RegExp(r'^\[[0-9]+:[0-9]+\.[0-9]+\].*$');
Quá trình khớp thực sự đã hoàn thành, nhưng bạn không có cách nào để trích xuất các phần biến. Bạn phải sử dụng các nhóm để làm điều đó.
2.4 Capture the groups
Bạn có thể gộp các nhóm bằng cách bao quanh chúng bằng dấu ngoặc đơn:
final regex = RegExp(r'^\[([0-9]+):([0-9]+)\.([0-9]+)\](.*)$');
Bây giờ bạn đã sẵn sàng để trích xuất các phần bên trong dấu ngoặc đơn.
3. Pulling it all together (Kết hợp các điều trên lại)
Đây là cách bạn trích xuất text bạn muốn:
final line = '[00:12.30]Twinkle, twinkle little star';
final regex = RegExp(r'^\[([0-9]+):([0-9]+)\.([0-9]+)\](.*)$');
final match = regex.firstMatch(line);
final everything = match.group(0); // [00:12.30]Twinkle, twinkle little star
final minutes = match.group(1); // 00
final seconds = match.group(2); // 12
final fraction = match.group(3); // 30
final words = match.group(4); // Twinkle, twinkle little star
Chú ý:
- Bạn có thể kiểm tra matching hay không bằng cách gọi hàm
firstMatch
trong regex. Bạn hoàn toàn sử dụng được vởi vì bạn đã chắc chắn nó đã khớp với toàn bộ dòng. Nếu ban đầu bạn không chia toàn bộ text thành các dòng thì bạn có thể gọi hàmregex.allMatches
, nó sẽ cung cấp cho bạn một collection các kết quả phù hợp. - Như bạn có thể thấy,
group(0)
khớp với mọi thứ, trong khigroup(1)
đếngroup(4)
khớp với các phần mà bạn đã bao lại bằng dấu ngoặc đơn.
Các nhóm thời gian được trích xuất vẫn là string, vì vậy nếu bạn muốn chuyển đổi chúng thành Duration, thì bạn chỉ cần thực hiện như sau:
final time = Duration(
minutes: int.parse(minutes),
seconds: int.parse(seconds),
milliseconds: int.parse(fraction.padLeft(3, '0')),
);
Đó là toàn bộ những gì có trong regex của ngôn ngữ Dart. Nếu bạn có thể vượt qua được độ khó đọc, khó hiểu của các regex matching pattern thì regex là một cách vô cùng thuận tiện để trích xuất những gì bạn cần từ các text.
Bài viết được dịch từ đây.
Kieu Hoa
Khi mình yêu cuộc đời, cuộc đời cũng sẽ yêu mình đắm say
Bài viết liên quan
Tự học Dart: Các Dart Operators (toán tử) bạn cần biết
Sep 02, 2023 • 18 min read
Flutter cơ bản: Điều cần biết khi lập trình ứng dụng đầu tiên
Aug 23, 2023 • 13 min read
Dart là gì? Giới thiệu cơ bản về ngôn ngữ lập trình Dart
Aug 21, 2023 • 11 min read
Flutter là gì? Vì sao nên học công cụ lập trình Flutter?
Aug 19, 2023 • 11 min read
Flutter cơ bản: Widget Tree, Element Tree & Render Tree
Aug 19, 2023 • 10 min read
So sánh StatelessWidget và StatefulWidget
Jul 17, 2022 • 12 min read