Reguläre Ausdrücke in Kotlin

1. Einleitung

Wir können reguläre Ausdrücke in so ziemlich jeder Art von Software verwenden (oder missbrauchen), von schnellen Skripten bis zu unglaublich komplexen Anwendungen.

In diesem Artikel erfahren Sie, wie Sie reguläre Ausdrücke in Kotlin verwenden.

Wir werden die Syntax regulärer Ausdrücke nicht diskutieren. Um den Artikel angemessen zu befolgen, ist im Allgemeinen eine Vertrautheit mit regulären Ausdrücken erforderlich, und es wird empfohlen, die Java-Pattern-Syntax speziell zu kennen.

2. Setup

Während reguläre Ausdrücke nicht Teil der Kotlin-Sprache sind, werden sie mit der Standardbibliothek geliefert.

Wir haben es wahrscheinlich schon als Abhängigkeit von unserem Projekt:

 org.jetbrains.kotlin kotlin-stdlib 1.2.21 

Wir können die neueste Version von kotlin-stdlib auf Maven Central finden.

3. Erstellen eines Objekts mit regulären Ausdrücken

Reguläre Ausdrücke sind Instanzen der Klasse kotlin.text.Regex . Wir können eine auf verschiedene Arten erstellen.

Eine Möglichkeit besteht darin, den Regex- Konstruktor aufzurufen :

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

oder wir können die toRegex- Methode für einen String aufrufen :

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

Schließlich können wir eine statische Factory-Methode verwenden:

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

Abgesehen von einem Unterschied, der im nächsten Abschnitt erläutert wird, sind diese Optionen gleichwertig und entsprechen den persönlichen Vorlieben. Denken Sie daran, konsequent zu sein!

Tipp: Reguläre Ausdrücke enthalten häufig Zeichen, die in String- Literalen als Escape-Sequenzen interpretiert werden . Wir können also rohe Strings verwenden , um mehrere Ebenen der Flucht zu vergessen:

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

3.1. Übereinstimmende Optionen

Sowohl der Regex- Konstruktor als auch die toRegex- Methode ermöglichen es uns, eine einzelne zusätzliche Option oder einen Satz anzugeben:

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))

Optionen werden in der RegexOption- Klasse aufgelistet , die wir im obigen Beispiel bequem statisch importiert haben:

  • IGNORE_CASE - Aktiviert die Übereinstimmung ohne Berücksichtigung der Groß- und Kleinschreibung
  • MULTILINE - ändert die Bedeutung von ^ und $ (siehe Muster)
  • LITERAL - bewirkt, dass Metazeichen oder Escape-Sequenzen im Muster keine besondere Bedeutung erhalten
  • UNIX_LINES - In diesem Modus wird nur das \ n als Zeilenabschluss erkannt
  • KOMMENTARE - Ermöglicht Leerzeichen und Kommentare im Muster
  • DOT_MATCHES_ALL - bewirkt, dass der Punkt mit einem beliebigen Zeichen übereinstimmt, einschließlich eines Zeilenabschlusses
  • CANON_EQ - ermöglicht die Äquivalenz durch kanonische Zerlegung (siehe Muster)

4. Matching

Wir verwenden reguläre Ausdrücke hauptsächlich, um Eingabezeichenfolgen abzugleichen und manchmal Teile davon zu extrahieren oder zu ersetzen.

Wir werden uns nun die Methoden der Regex- Klasse von Kotlin zum Abgleichen von Strings genauer ansehen .

4.1. Überprüfen von Teil- oder Gesamtübereinstimmungen

In diesen Anwendungsfällen möchten wir wissen, ob ein String oder ein Teil eines Strings unseren regulären Ausdruck erfüllt.

Wenn wir nur eine teilweise Übereinstimmung benötigen, können wir verwenden containsMatchIn :

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

Wenn stattdessen der gesamte String übereinstimmen soll, verwenden wir Übereinstimmungen :

assertTrue(regex.matches("abcd"))

Beachten Sie, dass wir Übereinstimmungen auch als Infix-Operator verwenden können:

assertFalse(regex matches "xabcdy")

4.2. Übereinstimmende Komponenten extrahieren

In diesen Anwendungsfällen möchten wir einen String mit einem regulären Ausdruck abgleichen und Teile des Strings extrahieren .

Möglicherweise möchten wir den gesamten String abgleichen:

val matchResult = regex.matchEntire("abbccbbd")

Oder wir möchten den ersten passenden Teilstring finden:

val matchResult = regex.find("abcbabbd")

Oder um alle passenden Teilzeichenfolgen gleichzeitig als Set zu finden :

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

In beiden Fällen ist das Ergebnis bei erfolgreicher Übereinstimmung eine oder mehrere Instanzen der MatchResult- Klasse. Im nächsten Abschnitt werden wir sehen, wie man es benutzt.

If the match is not successful, instead, these methods return null or the empty Set in case of findAll.

4.3. The MatchResult Class

Instances of the MatchResult class represent successful matches of some input string against a regular expression; either complete or partial matches (see the previous section).

As such, they have a value, which is the matched String or substring:

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

And they have a range of indices to indicate what portion of the input was matched:

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

4.4. Groups and Destructuring

We can also extract groups (matched substrings) from MatchResult instances.

We can obtain them as Strings:

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

Or we can also view them as MatchGroup objects consisting of a value and a range:

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

The group with index 0 is always the entire matched String. Indices greater than 0, instead, represent groups in the regular expression, delimited by parentheses, such as ([bc]+) in our example.

We can also destructure MatchResult instances in an assignment statement:

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. Multiple Matches

MatchResult also has a next method that we can use to obtain the next match of the input String against the regular expression, if there is any:

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)

As we can see, next returns null when there are no more matches.

5. Replacing

Another common use of regular expressions is replacing matching substrings with other Strings.

For this purpose, we have two methods readily available in the standard library.

One, replace, is for replacing all occurrences of a matching 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)

The other, replaceFirst, is for replacing only the first occurrence:

val shiny = regex.replaceFirst(beautiful, "rainbow") assertEquals("Roses are rainbow, Violets are blue", shiny)

5.1. Complex Replacements

For more advanced scenarios, when we don't want to replace matches with constant Strings, but we want to apply a transformation instead, Regex still gives us what we need.

Enter the replace overload taking a closure:

val reallyBeautiful = regex.replace(beautiful) { m -> m.value.toUpperCase() + "!" } assertEquals("Roses are RED!, Violets are BLUE!", reallyBeautiful)

As we can see, for each match, we can compute a replacement String using that match.

6. Splitting

Finally, we might want to split a String into a list of substrings according to a regular expression. Again, Kotlin's Regex has got us covered:

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

Here, the regular expression matches one or more non-word characters, so the result of the split operation is a list of words.

We can also put a limit on the length of the resulting list:

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

7. Java Interoperability

Wenn wir unseren regulären Ausdruck an Java-Code oder eine andere JVM-Sprach-API übergeben müssen, die eine Instanz von java.util.regex.Pattern erwartet , können wir einfach unseren Regex konvertieren :

regex.toPattern()

8. Schlussfolgerungen

In diesem Artikel haben wir die Unterstützung regulärer Ausdrücke in der Kotlin-Standardbibliothek untersucht.

Weitere Informationen finden Sie in der Kotlin-Referenz.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie im GitHub-Projekt - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.