Регулярные выражения в Kotlin

Регулярные выражения в Котлине

1. Вступление

Мы можем найти использование (или злоупотребление)regular expressions практически в любом программном обеспечении, от быстрых сценариев до невероятно сложных приложений.

В этой статье мы увидим, как использовать регулярные выражения в Kotlin.

Мы не будем обсуждать синтаксис регулярных выражений; В общем, знакомство с регулярными выражениями требуется для адекватного изучения статьи, и особенно рекомендуется знаниеJava Pattern syntax.

2. Настроить

Хотя регулярные выражения не являются частью языка Kotlin, они входят в стандартную библиотеку.

У нас, вероятно, уже есть зависимость от нашего проекта:


    org.jetbrains.kotlin
    kotlin-stdlib
    1.2.21

Мы можем найти последнюю версиюkotlin-stdlib на Maven Central.

3. Создание объекта регулярного выражения

Регулярные выражения являются экземплярами классаkotlin.text.Regex. Мы можем создать один несколькими способами.

Есть возможность вызвать конструкторRegex:

Regex("a[bc]+d?")

или мы можем вызвать методtoRegex дляString:

"a[bc]+d?".toRegex()

Наконец, мы можем использовать статический метод фабрики:

Regex.fromLiteral("a[bc]+d?")

За исключением различий, описанных в следующем разделе, эти параметры эквивалентны и соответствуют личным предпочтениям. Просто помните, чтобы быть последовательным!

Совет: регулярные выражения часто содержат символы, которые можно интерпретировать как escape-последовательности в литералахString. Таким образом, мы можем использовать rawStrings, чтобы забыть о нескольких уровнях экранирования:

"""a[bc]+d?\W""".toRegex()

3.1. Варианты соответствия

И конструкторRegex, и методtoRegex позволяют нам указать один дополнительный параметр или набор:

Regex("a(b|c)+d?", CANON_EQ)
Regex("a(b|c)+d?", setOf(DOT_MATCHES_ALL, COMMENTS))
"a(b|c)+d?".toRegex(MULTILINE)
"a(b|c)+d?".toRegex(setOf(IGNORE_CASE, COMMENTS, UNIX_LINES))

Параметры перечислены в классеRegexOption, который мы удобно импортировали статически в приведенном выше примере:

  • IGNORE_CASE – включает соответствие без учета регистра

  • MULTILINE - изменяет значение^ и$ (см.Pattern)

  • LITERAL - не дает метасимволам или управляющим последовательностям в шаблоне особого значения

  • UNIX_LINES - в этом режиме только распознается как терминатор линии

  • COMMENTS - разрешает пробелы и комментарии в шаблоне

  • DOT_MATCHES_ALL - заставляет точку соответствовать любому символу, включая терминатор строки

  • CANON_EQ - включает эквивалентность каноническим разложением (см.Pattern)

4. согласование

We use regular expressions primarily to match input Strings,, а иногда и для извлечения или замены их частей.

Теперь мы подробно рассмотрим методы, предлагаемые классом KotlinRegex для сопоставленияStrings.

4.1. Проверка частичного или полного совпадения

В этих случаях нас интересуетknowing whether a String or a portion of a String satisfies our regular expression.

Если нам нужно только частичное совпадение, мы можем использоватьcontainsMatchIn:

val regex = """a([bc]+)d?""".toRegex()

assertTrue(regex.containsMatchIn("xabcdy"))

Если мы хотим, чтобы вместо этого совпадал весьString, мы используемmatches:

assertTrue(regex.matches("abcd"))

Обратите внимание, что мы также можем использоватьmatches как инфиксный оператор:

assertFalse(regex matches "xabcdy")

4.2. Извлечение совпадающих компонентов

В этих случаях нам нужноto match a String against a regular expression and extract parts of the String.

Мы могли бы захотеть сопоставить весьString:

val matchResult = regex.matchEntire("abbccbbd")

Или мы могли бы найти первую подстроку, которая соответствует:

val matchResult = regex.find("abcbabbd")

Или, может быть, найти все подходящие подстроки сразу, какSet:

val matchResults = regex.findAll("abcb abbd")

В любом случае, если соответствие будет успешным, результатом будет один или несколько экземпляров классаMatchResult. В следующем разделе мы увидим, как его использовать.

Если сопоставление не было успешным, вместо этого эти методы возвращаютnull или пустойSet в случаеfindAll.

4.3. КлассMatchResult

Экземпляры классаMatchResult представляют собой успешные совпадения некоторой входной строки с регулярным выражением; полное или частичное совпадение (см. предыдущий раздел).

Таким образом, у них естьvalue, который является совпадающимString или подстрокой:

val regex = """a([bc]+)d?""".toRegex()
val matchResult = regex.find("abcb abbd")

assertEquals("abcb", matchResult.value)

И у них естьrange индексов, чтобы указать, какая часть ввода была сопоставлена:

assertEquals(IntRange(0, 3), matchResult.range)

4.4. Группы и деструктуризация

Мы также можем извлекать группы (совпадающие подстроки) из экземпляровMatchResult.

Их можно получить какStrings:

assertEquals(listOf("abcb", "bcb"), matchResult.groupValues)

Или мы также можем рассматривать их как объектыMatchGroup, состоящие изvalue иrange:

assertEquals(IntRange(1, 3), matchResult.groups[1].range)

Группа с индексом 0 всегда представляет собой все сопоставленныеString.. Индексы больше 0, вместо этого представляют группы в регулярном выражении, ограниченные круглыми скобками, например([bc]+) в нашем примере.

Мы также можем деструктурировать экземплярыMatchResult в операторе присваивания:

val regex = """([\w\s]+) is (\d+) years old""".toRegex()
val matchResult = regex.find("Mickey Mouse is 95 years old")
val (name, age) = matchResult!!.destructured

assertEquals("Mickey Mouse", name)
assertEquals("95", age)

4.5. Несколько матчей

MatchResult также имеет методnext, который мы можем использоватьto obtain the next match of the input String against the regular expression, если он есть:

val regex = """a([bc]+)d?""".toRegex()
var matchResult = regex.find("abcb abbd")

assertEquals("abcb", matchResult!!.value)

matchResult = matchResult.next()
assertEquals("abbd", matchResult!!.value)

matchResult = matchResult.next()
assertNull(matchResult)

Как мы видим,next возвращает null, если совпадений больше нет.

5. Замена

Еще одно распространенное использование регулярных выражений -replacing matching substrings with other Strings.

Для этой цели у нас есть два метода, легко доступных в стандартной библиотеке.

Один,replace, предназначен для замены всех совпаденийString:

val regex = """(red|green|blue)""".toRegex()
val beautiful = "Roses are red, Violets are blue"
val grim = regex.replace(beautiful, "dark")

assertEquals("Roses are dark, Violets are dark", grim)

Другой,replaceFirst, предназначен для замены только первого вхождения:

val shiny = regex.replaceFirst(beautiful, "rainbow")

assertEquals("Roses are rainbow, Violets are blue", shiny)

5.1. Комплексные замены

Дляmore advanced scenarios, когда мы не хотим заменять совпадения константойStrings,, но хотим вместо этогоto apply a transformation,Regex по-прежнему дает нам то, что нам нужно.

Введите перегрузкуreplace с замыканием:

val reallyBeautiful = regex.replace(beautiful) {
    m -> m.value.toUpperCase() + "!"
}

assertEquals("Roses are RED!, Violets are BLUE!", reallyBeautiful)

Как мы видим, для каждого совпадения мы можем вычислить заменуString, используя это совпадение.

6. расщепляющий

Наконец, нам может понадобитьсяto split a String into a list of substrings according to a regular expression.. Опять же,Regex Kotlin нас покрывает:

val regex = """\W+""".toRegex()
val beautiful = "Roses are red, Violets are blue"

assertEquals(listOf(
  "Roses", "are", "red", "Violets", "are", "blue"), regex.split(beautiful))

Здесь регулярное выражение соответствует одному или нескольким несловарным символам, поэтому результатом операции разбиения является список слов.

Мы также можем ограничить длину полученного списка:

assertEquals(listOf("Roses", "are", "red", "Violets are blue"), regex.split(beautiful, 4))

7. Совместимость с Java

Если нам нужно передать наше регулярное выражение в код Java или какой-либо другой API-интерфейс JVM, который ожидает экземплярjava.util.regex.Pattern, мы можем просто преобразовать нашRegex:

regex.toPattern()

8. Выводы

В этой статье мы рассмотрели поддержку регулярных выражений в стандартной библиотеке Kotlin.

Для получения дополнительной информации см.the Kotlin reference.

Реализацию всех этих примеров и фрагментов кода можно найти вthe GitHub project - это проект Maven, поэтому его будет легко импортировать и запускать как есть.