Зіставити багаторядковий текст, використовуючи регулярний вираз


174

Я намагаюся відповідати багаторядковому тексту за допомогою Java. Коли я використовую Patternклас з Pattern.MULTILINEмодифікатором, я можу відповідати, але я не в змозі це зробити(?m).

Схожа картина із (?m)використанням та використанням String.matches, здається, не працює.

Я впевнений, що мені щось не вистачає, але поняття чого не маю. Я не дуже хороший у регулярних висловлюваннях.

Це те, що я спробував

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?

Відповіді:


298

По-перше, ви використовуєте модифікатори за неправильним припущенням.

Pattern.MULTILINEабо (?m)вказує Java прийняти якірі ^та $збігатися на початку та в кінці кожного рядка (інакше вони відповідають лише на початку / кінці всього рядка).

Pattern.DOTALLабо (?s)вказує Java дозволити крапці також відповідати символам нового рядка.

По-друге, у вашому випадку регулярний вираз не вдається, оскільки ви використовуєте matches()метод, який передбачає, що регулярний вираз відповідає всій рядку - що, звичайно, не працює, оскільки після символу залишилися деякі символи (\\W)*(\\S)*.

Тож якщо ви просто шукаєте рядок, з якої починається User Comments:, використовуйте регулярний вираз

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

з Pattern.DOTALLопцією:

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

ResultString буде містити текст після User Comments:


Я намагаюся знайти шаблон, який би відповідав будь-якій рядку, що починається з "Коментарі користувача:". Після цього "Коментарі користувача": це щось, що користувач вводить у текстову область, і тому може містити що завгодно - навіть нові рядки. Схоже, мені потрібно багато чого навчитися з регексу ...
Nivas

2
Це працює (спасибі!) Я спробував шаблон (?s)User Comments:\s*(.*). З відповіді @Amarghosh я отримала схему User Comments: [\\s\\S]*. Серед них є кращий або рекомендований спосіб чи це лише два різні способи зробити те саме?
Nivas

3
Вони обоє означають те саме; [\s\S]є трохи більш явним ("відповідати будь-якому символу, який є або пробілом, або непробілом"), .легше читати, але вам потрібно шукати (?s)або DOTALLмодифікатор, щоб дізнатися, включені чи ні нові рядки. Я волів би .з Pattern.DOTALLвстановленим прапором (це легше читати і пам'ятати , що (?s)на мій погляд , ви повинні використовувати те , що ви відчуваєте себе найбільш комфортно ..
Tim Pietzcker

.*з DOTALLбільш читабельним. Я використовував інший, щоб показати, що проблема полягає у відмінності між str.matches та matcher.find, а не прапорами. +1
Amarghosh

Я вважаю .*за краще Pattern.DOTALL, але мені доведеться їхати (з), тому що мені доведеться користуватися String.matches.
Nivas

42

Це не має нічого спільного з прапором MULTILINE; що ви бачите, це різниця між методами find()та matches(). find()успішно, якщо відповідність можна знайти в будь-якому місці цільового рядка , тоді як matches()очікує, що регулярний вираз відповідає всій рядку .

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

Крім того, MULTILINEне означає, що ви думаєте, що це робить. Здається, багато людей приходять до висновку, що вам потрібно використовувати цей прапор, якщо ваша цільова рядок містить нові рядки - тобто якщо він містить кілька логічних рядків. Я бачив тут декілька відповідей на SO щодо цього ефекту, але насправді все, що прапор робить, - це зміни поведінки якорів, ^і $.

Зазвичай ^відповідає самому початку цільового рядка і $відповідає самому кінці (або перед новим рядком наприкінці, але ми покинемо це осторонь). Але якщо рядок містить нові рядки, ви можете вибрати ^та $відповідати на початку та в кінці будь-якого логічного рядка, а не лише початку та кінця всього рядка, встановивши прапор MULTILINE.

Тож забудьте про те, що MULTILINE означає, і просто пам’ятайте, що це робить : змінює поведінку ^та $прив’язки. DOTALLСпочатку режим називався "однолінійним" (і досі він є в деяких ароматах, включаючи Perl і .NET), і він завжди викликав подібну плутанину. Нам пощастило, що Java-розробники пішли з більш описовою назвою в цьому випадку, але розумної альтернативи режиму "багаторядковий" не було.

У Перлі, де почалося все це божевілля, вони визнали свою помилку і позбулися як «багатолінійного», так і «однолінійного» режимів у регексах Perl 6. Ще через двадцять років, можливо, решта світу піде за цим прикладом.


5
Важко повірити, що вони використовували назву методу "#matches", щоб означати "відповідає всім"
вигідності

@ alan-moore Вибачте, що це не так, хоча це правильно [потрібно більше сну :)]
Raymond Naseef

22

str.matches(regex) поводиться так, як Pattern.matches(regex, str) намагається зіставити всю послідовність введення з шаблоном і повертається

trueякщо і лише якщо вся послідовність введення відповідає шаблону цього відповідника

Тоді як matcher.find() спроби знайти наступну послідовність вхідної послідовності, яка відповідає шаблону та повертається

trueякщо, і тільки якщо, А підпослідовність послідовності введення відповідає шаблоном цього узгодження по

Таким чином, проблема полягає в регулярному вираженні. Спробуйте наступне.

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

Таким чином, коротше, (\\W)*(\\S)*частина у вашому першому регулярному виразі відповідає порожній рядку, оскільки *означає нуль або більше подій, а реальна відповідна рядок є, User Comments:а не цілою рядком, як ви очікували. Другий не вдається, оскільки він намагається співставити весь рядок, але він не може \\Wвідповідати символу, який не є словом, тобто [^a-zA-Z0-9_]перший символ є Tсимволом слова.


Я хочу відповідати будь-якій рядку, що починається з "Коментарі користувача", і рядок також може містити нові рядки. Тому я використав шаблон, User Comments: [\\s\\S]*і це спрацювало. (спасибі!) З відповіді @Tim я отримав схему User Comments:(.*), це також нормально. Чи є рекомендований чи кращий спосіб серед них, чи це лише два способи зробити те саме?
Nivas

@Nivas Я не думаю, що не було б різниці в продуктивності; але я думаю , що (.*)поряд з DOTALLпрапором більш очевидною / читабельним , ніж([\\s\\S]*)
Amarghosh

Це найкраща відповідь .... надає як доступ до коду Java, так і параметрів рядкової шаблону для можливості MultiLine.
GoldBishop

0

Багаторядковий прапор повідомляє, що регулярний вирівнювання повинен відповідати шаблону кожному рядку, а не цілому рядку для ваших цілей.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.