1. Обзор
Иногда мы можем столкнуться с трудностями при сопоставлении строки с регулярным выражением . Например, мы можем не знать, что именно мы хотим сопоставить, но мы можем осознавать его окружение, например, что находится непосредственно перед ним или чего не хватает после него. В этих случаях мы можем использовать утверждения обхода. Эти выражения называются утверждениями, потому что они только указывают, является ли что-то совпадением или нет, но не включаются в результат.
В этом руководстве мы рассмотрим, как мы можем использовать четыре типа утверждений обхода регулярных выражений.
2. Положительный взгляд вперед
Допустим, мы хотели бы проанализировать импорт java-файлов. Во-первых, давайте найдем операторы импорта, которые являются статическими
, проверив, что ключевое слово static
следует за ключевым словом import
.
Давайте воспользуемся утверждением положительного просмотра вперед с синтаксисом (?=criteria)
в нашем выражении, чтобы сопоставить группу статических символов после
импорта
нашего основного выражения :
Pattern pattern = Pattern.compile("import (?=static).+");
Matcher matcher = pattern
.matcher("import static org.junit.jupiter.api.Assertions.assertEquals;");
assertTrue(matcher.find());
assertEquals("import static org.junit.jupiter.api.Assertions.assertEquals;", matcher.group());
assertFalse(pattern.matcher("import java.util.regex.Matcher;").find());
3. Отрицательный прогноз
Далее давайте сделаем прямо противоположное предыдущему примеру и поищем операторы импорта, которые не являются статическими
. Давайте сделаем это, проверив, что ключевое слово static
не следует за ключевым словом import
.
Давайте воспользуемся отрицательным прогнозным утверждением с синтаксисом (?!criteria) в нашем выражении, чтобы гарантировать, что группа
статических
символов не может совпадать после импорта
нашего основного выражения :
Pattern pattern = Pattern.compile("import (?!static).+");
Matcher matcher = pattern.matcher("import java.util.regex.Matcher;");
assertTrue(matcher.find());
assertEquals("import java.util.regex.Matcher;", matcher.group());
assertFalse(pattern
.matcher("import static org.junit.jupiter.api.Assertions.assertEquals;").find());
4. Ограничения Lookbehind в Java
Вплоть до Java 8 мы могли столкнуться с ограничением, заключающимся в том, что несвязанные квантификаторы, такие как +
и *
, не разрешены в утверждении просмотра назад. То есть, например, следующие утверждения вызовут PatternSyntaxException
вплоть до Java 8:
(?<!fo+)bar
, где мы не хотим сопоставлятьbar
, если перед ним стоитfo
с одним или несколькими символамиo
(?<!fo*)bar
, где мы не хотим сопоставлятьbar
, если ему предшествует символf
, за которым следует ноль или более символовo
(?<!fo{2,})bar
, где мы не хотим сопоставлятьbar
, еслиfoo
с двумя или более символамиo стоит перед ним
В качестве обходного пути мы можем использовать квантификатор фигурных скобок с указанным верхним пределом, например (?<!fo{2,4})bar
, где мы максимизируем количество символов o
, следующих за символом f
, до 4.
Начиная с Java 9, мы можем использовать несвязанные квантификаторы в просмотре назад. Однако из-за потребления памяти реализацией регулярных выражений по-прежнему рекомендуется использовать квантификаторы в ретроспективных просмотрах только с разумным верхним пределом, например (?<!fo{2,20})bar
вместо (?<!fo{ 2,2000})бар
.
5. Положительный взгляд назад
Допустим, мы хотели бы различать импорт JUnit 4 и JUnit 5 в нашем анализе. Во-первых, давайте проверим, получен ли оператор импорта для метода assertEquals из пакета
jupiter
.
Давайте используем положительное ретроспективное утверждение с синтаксисом (?<=criteria)
в нашем выражении, чтобы сопоставить группу символов jupiter
перед нашим основным выражением .* assertEquals
:
Pattern pattern = Pattern.compile(".*(?<=jupiter).*assertEquals;");
Matcher matcher = pattern
.matcher("import static org.junit.jupiter.api.Assertions.assertEquals;");
assertTrue(matcher.find());
assertEquals("import static org.junit.jupiter.api.Assertions.assertEquals;", matcher.group());
assertFalse(pattern.matcher("import static org.junit.Assert.assertEquals;").find());
6. Отрицательный взгляд назад
Далее, давайте сделаем прямую противоположность предыдущему примеру и поищем операторы импорта, которые не входят в пакет jupiter
.
Для этого давайте воспользуемся отрицательным просмотром назад с синтаксисом (?<!criteria)
в нашем выражении, чтобы гарантировать, что группа символов jupiter.{0,30}
не может совпасть перед нашим основным выражением assertEquals
:
Pattern pattern = Pattern.compile(".*(?<!jupiter.{0,30})assertEquals;");
Matcher matcher = pattern.matcher("import static org.junit.Assert.assertEquals;");
assertTrue(matcher.find());
assertEquals("import static org.junit.Assert.assertEquals;", matcher.group());
assertFalse(pattern
.matcher("import static org.junit.jupiter.api.Assertions.assertEquals;").find());
7. Заключение
В этой статье мы увидели, как использовать четыре типа поиска регулярных выражений для решения некоторых сложных случаев сопоставления строк с регулярным выражением.
Как всегда, исходный код этой статьи доступен на GitHub .