Cocokkan teks multiline menggunakan ekspresi reguler

174

Saya mencoba mencocokkan teks multi-baris menggunakan java. Ketika saya menggunakan Patternkelas dengan Pattern.MULTILINEpengubah, saya dapat mencocokkan, tetapi saya tidak dapat melakukannya dengan(?m).

Pola yang sama dengan (?m)dan menggunakanString.matches tampaknya tidak berhasil.

Saya yakin saya kehilangan sesuatu, tetapi tidak tahu apa. Tidak terlalu bagus dalam ekspresi reguler.

Ini yang saya coba

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?
Nivas
sumber

Jawaban:

298

Pertama, Anda menggunakan pengubah dengan asumsi yang salah.

Pattern.MULTILINEatau (?m)memberitahu Java untuk menerima jangkar ^dan $untuk mencocokkan pada awal dan akhir setiap baris (jika tidak, mereka hanya cocok pada awal / akhir seluruh string).

Pattern.DOTALLatau (?s)memberitahu Java untuk mengizinkan titik untuk mencocokkan karakter baris baru juga.

Kedua, dalam kasus Anda, regex gagal karena Anda menggunakan matches()metode yang mengharapkan regex untuk mencocokkan seluruh string - yang tentu saja tidak berfungsi karena ada beberapa karakter yang tersisa setelah (\\W)*(\\S)*dicocokkan.

Jadi jika Anda hanya mencari string yang dimulai dengan User Comments:, gunakan regex

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

dengan Pattern.DOTALLopsi:

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

ResultString kemudian akan berisi teks setelahnya User Comments:

Tim Pietzcker
sumber
Saya mencoba menemukan pola yang cocok dengan string apa pun yang dimulai dengan "Komentar Pengguna:". Setelah ini, "Komentar Pengguna:" adalah sesuatu yang dimasukkan pengguna ke dalam textarea, dan oleh karena itu dapat berisi apa saja - bahkan baris baru. Sepertinya saya perlu belajar banyak di regex ...
Nivas
2
Ini berhasil (terima kasih!) Saya mencoba polanya (?s)User Comments:\s*(.*). Dari jawaban @Amarghosh saya mendapatkan polanya User Comments: [\\s\\S]*. Di antaranya ada cara yang lebih baik atau direkomendasikan atau hanya dua cara yang berbeda untuk melakukan hal yang sama?
Nivas
3
Keduanya sama artinya; [\s\S]sedikit lebih eksplisit ("cocok dengan karakter apa pun yang merupakan spasi putih atau non-spasi putih"), .lebih mudah dibaca, tetapi Anda perlu mencari (?s)atau DOTALLpengubah untuk mengetahui apakah baris baru disertakan atau tidak. Saya lebih suka .dengan Pattern.DOTALLset bendera (ini lebih mudah dibaca dan diingat daripada (?s)menurut saya. Anda harus menggunakan apa yang Anda rasa paling nyaman.
Tim Pietzcker
.*dengan DOTALLlebih mudah dibaca. Saya menggunakan yang lain untuk menunjukkan bahwa masalah ini adalah perbedaan antara str.matches dan matcher.find dan bukan bendera. +1
Amarghosh
Saya lebih suka .*dengan Pattern.DOTALL, tetapi harus pergi dengan (? S) karena saya harus menggunakan String.matches.
Nivas
42

Ini tidak ada hubungannya dengan bendera MULTILINE; apa yang Anda lihat adalah perbedaan antarafind()matches() metode dan metode. find()berhasil jika kecocokan dapat ditemukan di mana saja di string target , sementara matches()mengharapkan regex untuk mencocokkan seluruh string .

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

Selain itu, MULTILINEtidak berarti apa yang Anda pikirkan. Banyak orang tampaknya melompat ke kesimpulan bahwa Anda harus menggunakan bendera itu jika string target Anda berisi baris baru - yaitu, jika itu berisi beberapa baris logis. Saya telah melihat beberapa jawaban di sini pada SO untuk efek itu, tetapi pada kenyataannya, semua yang dilakukan flag adalah mengubah perilaku jangkar, ^dan $.

Biasanya ^cocok dengan yang paling awal dari string target, dan $cocok dengan yang paling akhir (atau sebelum baris baru di akhir, tapi kami akan mengesampingkannya untuk saat ini). Tetapi jika string berisi baris baru, Anda dapat memilih ^dan $mencocokkan pada awal dan akhir dari setiap baris logis, bukan hanya awal dan akhir dari seluruh string, dengan mengatur bendera MULTILINE.

Jadi lupakan apa MULTILINE berarti dan hanya ingat apa yang dilakukannya : mengubah perilaku ^dan $jangkar. DOTALLmode awalnya disebut "single-line" (dan masih dalam beberapa rasa, termasuk Perl dan .NET), dan selalu menyebabkan kebingungan yang sama. Kami beruntung bahwa para pengembang Java menggunakan nama yang lebih deskriptif dalam kasus itu, tetapi tidak ada alternatif yang masuk akal untuk mode "multiline".

Di Perl, di mana semua kegilaan ini dimulai, mereka mengakui kesalahan mereka dan menyingkirkan mode "multiline" dan "single-line" di Perl 6 regex. Dalam dua puluh tahun lagi, mungkin seluruh dunia akan mengikutinya.

Alan Moore
sumber
5
Sulit dipercaya mereka menggunakan nama metode "#matches" yang berarti "cocok dengan semua"
ya
@ alan-moore Maaf saya turun ini meskipun sudah benar [perlu lebih banyak tidur :)]
Raymond Naseef
22

str.matches(regex) berperilaku seperti Pattern.matches(regex, str) yang mencoba untuk mencocokkan seluruh urutan input terhadap pola dan kembali

truejika, dan hanya jika, seluruh urutan input cocok dengan pola pasangan ini

Sedangkan matcher.find() upaya untuk menemukan urutan berikutnya dari urutan input yang cocok dengan pola dan kembali

truejika, dan hanya jika, sebuah subsequence dari urutan input sesuai dengan pola ini matcher ini

Jadi masalahnya adalah dengan regex. Coba yang berikut ini.

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

Jadi singkatnya, (\\W)*(\\S)*bagian dalam regex pertama Anda cocok dengan string kosong yang *berarti nol atau lebih kejadian dan string yang cocok sebenarnya adalah User Comments:dan bukan seluruh string seperti yang Anda harapkan. Yang kedua gagal karena mencoba untuk mencocokkan seluruh string tetapi tidak bisa \\Wcocok dengan karakter non-kata, yaitu [^a-zA-Z0-9_]dan karakter pertama adalah T, karakter kata.

Amarghosh
sumber
Saya ingin mencocokkan string apa pun yang dimulai dengan "Komentar Pengguna", dan string tersebut dapat berisi baris baru juga. Jadi saya menggunakan polanya User Comments: [\\s\\S]*dan ini berhasil. (terima kasih!) Dari jawaban @Tim saya mendapatkan polanya User Comments:(.*), ini juga ok Sekarang, apakah ada cara yang disarankan atau lebih baik di antara ini, atau hanya dua cara melakukan hal yang sama?
Nivas
@Nivas Saya tidak berpikir akan ada perbedaan kinerja yang bijaksana; tapi saya pikir (.*)bersama dengan DOTALLbendera lebih jelas / mudah dibaca daripada([\\s\\S]*)
Amarghosh
Ini adalah jawaban terbaik .... menyediakan akses ke kode Java dan opsi Pattern String, untuk kapabilitas MultiLine.
GoldBishop
0

Bendera multiline memberi tahu regex untuk mencocokkan pola ke setiap baris sebagai lawan dari seluruh string untuk tujuan Anda, kartu liar sudah cukup.

Yehuda Schwartz
sumber