Khớp văn bản đa dòng bằng cách sử dụng biểu thức chính quy


174

Tôi đang cố gắng để phù hợp với một văn bản nhiều dòng bằng cách sử dụng java. Khi tôi sử dụng Patternlớp với công cụ Pattern.MULTILINEsửa đổi, tôi có thể khớp, nhưng tôi không thể làm như vậy với(?m).

Mô hình tương tự với (?m)và sử dụng String.matchesdường như không hoạt động.

Tôi chắc chắn tôi đang thiếu một cái gì đó, nhưng không biết gì. Tôi không giỏi trong các biểu thức thông thường.

Đây là những gì tôi đã cố gắng

String test = "User Comments: This is \t a\ta \n test \n\n message \n";

String pattern1 = "User Comments: (\\W)*(\\S)*";
Pattern p = Pattern.compile(pattern1, Pattern.MULTILINE);
System.out.println(p.matcher(test).find());  //true

String pattern2 = "(?m)User Comments: (\\W)*(\\S)*";
System.out.println(test.matches(pattern2));  //false - why?

Câu trả lời:


298

Đầu tiên, bạn đang sử dụng công cụ sửa đổi theo giả định không chính xác.

Pattern.MULTILINEhoặc (?m)yêu cầu Java chấp nhận các neo ^$khớp ở đầu và cuối của mỗi dòng (nếu không chúng chỉ khớp ở đầu / cuối của toàn bộ chuỗi).

Pattern.DOTALLhoặc (?s)bảo Java cũng cho phép dấu chấm khớp với các ký tự dòng mới.

Thứ hai, trong trường hợp của bạn, regex không thành công vì bạn đang sử dụng matches()phương thức hy vọng regex khớp với toàn bộ chuỗi - tất nhiên là không hoạt động vì có một số ký tự còn lại sau khi (\\W)*(\\S)*khớp.

Vì vậy, nếu bạn chỉ đơn giản là tìm kiếm một chuỗi bắt đầu bằng User Comments:, hãy sử dụng regex

^\s*User Comments:\s*(.*)

với Pattern.DOTALLtùy chọn:

Pattern regex = Pattern.compile("^\\s*User Comments:\\s+(.*)", Pattern.DOTALL);
Matcher regexMatcher = regex.matcher(subjectString);
if (regexMatcher.find()) {
    ResultString = regexMatcher.group(1);
} 

ResultString sau đó sẽ chứa văn bản sau User Comments:


Tôi đang cố gắng tìm một mẫu phù hợp với bất kỳ chuỗi nào bắt đầu bằng "Nhận xét của người dùng:". Sau "Nhận xét người dùng:" này là thứ mà người dùng nhập vào văn bản, và do đó có thể chứa bất cứ thứ gì - ngay cả các dòng mới. Có vẻ như tôi cần học hỏi rất nhiều về regex ...
Nivas

2
Công việc này (cảm ơn!) Tôi đã thử mô hình (?s)User Comments:\s*(.*). Từ câu trả lời của @Amarghosh tôi đã nhận được mô hình User Comments: [\\s\\S]*. Trong số này có cách nào tốt hơn hay được đề xuất hay đây chỉ là hai cách khác nhau để làm giống nhau?
Nivas

3
Cả hai đều có nghĩa giống nhau; [\s\S]rõ ràng hơn một chút ("khớp với bất kỳ ký tự nào là khoảng trắng hoặc không phải khoảng trắng"), .dễ đọc hơn, nhưng bạn cần tìm (?s)hoặc DOTALLsửa đổi để tìm hiểu xem dòng mới có được đưa vào hay không. Tôi thích .với bộ Pattern.DOTALLcờ (điều này dễ đọc và dễ nhớ hơn (?s)theo ý kiến ​​của tôi. Bạn nên sử dụng những gì bạn cảm thấy thoải mái nhất.
Tim Pietzcker

.*với DOTALLlà dễ đọc hơn. Tôi đã sử dụng một cái khác để chỉ ra rằng vấn đề nằm ở sự khác biệt giữa str.matches và matcher.find chứ không phải các cờ. +1
Amarghosh

Tôi thích .*với Pattern.DOTALL, nhưng sẽ phải đi với (?) Vì tôi phải sử dụng String.matches.
Nivas

42

Điều này không liên quan gì đến cờ MULTILINE; những gì bạn đang thấy là sự khác biệt giữa find()matches()phương pháp. find()thành công nếu một trận đấu có thể được tìm thấy ở bất cứ đâu trong chuỗi mục tiêu , trong khi matches()hy vọng regex khớp với toàn bộ chuỗi .

Pattern p = Pattern.compile("xyz");

Matcher m = p.matcher("123xyzabc");
System.out.println(m.find());    // true
System.out.println(m.matches()); // false

Matcher m = p.matcher("xyz");
System.out.println(m.matches()); // true

Hơn nữa, MULTILINEkhông có nghĩa là những gì bạn nghĩ nó làm. Nhiều người dường như nhảy đến kết luận rằng bạn phải sử dụng cờ đó nếu chuỗi mục tiêu của bạn chứa dòng mới - nghĩa là, nếu nó chứa nhiều dòng logic. Tôi đã thấy một số câu trả lời ở đây về SO cho hiệu ứng đó, nhưng trên thực tế, tất cả những gì cờ đó làm là thay đổi hành vi của các neo, ^$.

Thông thường ^khớp với phần đầu của chuỗi mục tiêu và $khớp với phần cuối (hoặc trước một dòng mới ở cuối, nhưng chúng ta sẽ bỏ qua phần đó ngay bây giờ). Nhưng nếu chuỗi chứa dòng mới, bạn có thể chọn ^$khớp ở đầu và cuối của bất kỳ dòng logic nào, không chỉ đầu và cuối của toàn bộ chuỗi, bằng cách đặt cờ MULTILINE.

Vì vậy, hãy quên đi những gì MULTILINE có nghĩa và chỉ cần nhớ những gì nó làm : thay đổi hành vi của ^$neo. DOTALLChế độ ban đầu được gọi là "một dòng" (và vẫn còn trong một số hương vị, bao gồm Perl và .NET), và nó luôn gây ra sự nhầm lẫn tương tự. Chúng tôi may mắn rằng các nhà phát triển Java đã sử dụng tên mô tả nhiều hơn trong trường hợp đó, nhưng không có sự thay thế hợp lý nào cho chế độ "đa dòng".

Ở Perl, nơi tất cả sự điên rồ này bắt đầu, họ đã thừa nhận sai lầm của mình và thoát khỏi cả hai chế độ "đa dòng" và "một dòng" trong chế độ Perl 6. Trong hai mươi năm nữa, có lẽ phần còn lại của thế giới sẽ theo sau.


5
Khó tin rằng họ đã sử dụng tên phương thức "#matches" để có nghĩa là "khớp với tất cả"
yike

@ alan-moore Xin lỗi tôi xuống điều này mặc dù nó đúng [cần ngủ nhiều hơn :)]
Raymond Naseef

22

str.matches(regex) hành xử như thế Pattern.matches(regex, str) nào cố gắng khớp toàn bộ chuỗi đầu vào so với mẫu và trả về

truenếu và chỉ khi, toàn bộ chuỗi đầu vào khớp với mẫu của trình so khớp này

Trong khi đó, matcher.find() cố gắng tìm chuỗi tiếp theo của chuỗi đầu vào khớp với mẫu và trả về

truenếu, và chỉ nếu, một dãy con của dãy đầu vào phù hợp với mô hình của khớp này

Do đó, vấn đề là với regex. Hãy thử như sau.

String test = "User Comments: This is \t a\ta \ntest\n\n message \n";

String pattern1 = "User Comments: [\\s\\S]*^test$[\\s\\S]*";
Pattern p = Pattern.compile(pattern1, Pattern.MULTILINE);
System.out.println(p.matcher(test).find());  //true

String pattern2 = "(?m)User Comments: [\\s\\S]*^test$[\\s\\S]*";
System.out.println(test.matches(pattern2));  //true

Do đó, trong ngắn hạn, (\\W)*(\\S)*phần trong regex đầu tiên của bạn khớp với một chuỗi trống *có nghĩa là không có hoặc nhiều lần xuất hiện và chuỗi khớp thực sự User Comments:không phải là toàn bộ chuỗi như bạn mong đợi. Cái thứ hai thất bại vì nó cố khớp với toàn bộ chuỗi nhưng nó không thể \\Wkhớp với một ký tự không phải từ, nghĩa là [^a-zA-Z0-9_]và ký tự đầu tiên là T, một ký tự từ.


Tôi muốn khớp với bất kỳ chuỗi nào bắt đầu bằng "Nhận xét của người dùng" và chuỗi cũng có thể chứa các dòng mới. Vì vậy, tôi đã sử dụng mô hình User Comments: [\\s\\S]*và điều này làm việc. (cảm ơn!) Từ câu trả lời của @Tim tôi đã nhận được mô hình User Comments:(.*), điều này cũng ổn Bây giờ, có cách nào được đề xuất hoặc tốt hơn trong số này không, hay đây chỉ là hai cách làm giống nhau?
Nivas

@Nivas Tôi không nghĩ sẽ có hiệu suất khác biệt khôn ngoan; nhưng tôi nghĩ (.*)cùng với DOTALLcờ là rõ ràng / dễ đọc hơn([\\s\\S]*)
Amarghosh

Đây là câu trả lời tốt nhất .... cung cấp cả quyền truy cập vào mã Java và các tùy chọn Chuỗi mẫu, cho khả năng MultiLine.
GoldBishop

0

Cờ multiline yêu cầu regex khớp mẫu với từng dòng trái ngược với toàn bộ chuỗi cho mục đích của bạn, một thẻ hoang dã sẽ đủ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.