Совместить многострочный текст с использованием регулярного выражения

Я пытаюсь сопоставить многострочный текст с помощью 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?

Ответ 1

Во-первых, вы используете модификаторы под неправильным допуском.

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:

Ответ 2

Это не имеет ничего общего с флагом 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, где началось все это безумие, они признали свою ошибку и избавились от "многострочных" и "однострочных" режимов в регулярных выражениях Perl 6. Еще через двадцать лет, возможно, весь остальной мир последует этому примеру.

Ответ 3

str.matches(regex) ведет себя как Pattern.matches(regex, str), который пытается сопоставить всю входную последовательность с шаблоном и возвращает

true, если и только если входная последовательность целая соответствует этому шаблону сопряжения

В то время как matcher.find() пытается найти следующую подпоследовательность входной последовательности, которая соответствует шаблону и возвращает

true, если и только если a подпоследовательность входной последовательности соответствует этому шаблону сопряжения

Таким образом, проблема связана с регулярным выражением. Попробуйте следующее.

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, символ слова.