, October 26, 2021

0 kết quả được tìm thấy

Tìm hiểu regex trong ngôn ngữ Dart qua ví dụ cơ bản

  • Đăng bởi  Kieu Hoa
  •  Aug 31, 2021

  •   7 min reads
Tìm hiểu regex trong ngôn ngữ Dart qua ví dụ cơ bản

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

Text extraction from an LRC lyrics file

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 khi group(1) đến group(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.

Bài viết liên quan

[FREE EBOOK] TIPS & TRICKS GIÚP X3 TỐC ĐỘ CODE FLUTTER CỦA BẠN

Cuốn ebook "TIPS & TRICKS GIÚP X3 TỐC ĐỘ CODE FLUTTER CỦA BẠN" chỉ ra 32 trường hợp giúp bạn code nhanh hơn so với việc code thông thường...

[FREE EBOOK] TIPS & TRICKS GIÚP X3 TỐC ĐỘ CODE FLUTTER CỦA BẠN
Flutter 2.5 có gì mới?

Bản phát hành Flutter 2.5 lần này có rất nhiều bản cập nhật mới thú vị. Cùng bắt đầu tìm hiểu trong bài viết này nhé!...

Flutter 2.5 có gì mới?
Tìm hiểu Flutter Opacity Widget qua ví dụ cơ bản

Bài viết sau sẽ giới thiệu một cách tổng quan về Opacity widget và cung cấp đoạn code để bạn có thể tự thực hành thông qua những ví dụ cơ bản....

Tìm hiểu Flutter Opacity Widget qua ví dụ cơ bản
Flutter Tutorial 2021 #34 - Hướng dẫn tạo hiệu ứng "fancy" khi chuyển màn hình

Thêm một hiệu ứng mới nữa mà các bạn sẽ được giới thiệu tới! Đó chính là hiệu ứng "fancy" khi chuyển màn hình. Hiệu ứng này sẽ giúp tăng trải nghiệm và gây ấn tượng cực mạnh với người dùng....

Flutter Tutorial 2021 #34 - Hướng dẫn tạo hiệu ứng "fancy" khi chuyển màn hình
Flutter Tutorial 2021 #33 - "Giao tiếp" giữa 2 màn hình bất kỳ trong Flutter

Làm sao để giao tiếp 2 hoặc 3, 4 màn hình trong app với nhau? Chúng ta cùng xem video bên dưới để hiểu được cách truyền dữ liệu từ màn hình A sang màn hình B và ngược lại nhé!...

Flutter Tutorial 2021 #33 - "Giao tiếp" giữa 2 màn hình bất kỳ trong Flutter
You've successfully subscribed to 200Lab Blog
Great! Next, complete checkout for full access to 200Lab Blog
Xin chào mừng bạn đã quay trở lại
OK! Tài khoản của bạn đã kích hoạt thành công.
Success! Your billing info is updated.
Billing info update failed.
Your link has expired.