Совместить многострочный текст с использованием регулярного выражения
Я пытаюсь сопоставить многострочный текст с помощью 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
, символ слова.