Expressions régulières à Kotlin

1. Introduction

Nous pouvons trouver l'utilisation (ou l'abus) d'expressions régulières dans à peu près tous les types de logiciels, des scripts rapides aux applications incroyablement complexes.

Dans cet article, nous verrons comment utiliser les expressions régulières dans Kotlin.

Nous ne discuterons pas de la syntaxe des expressions régulières; une connaissance des expressions régulières, en général, est nécessaire pour suivre correctement l'article, et la connaissance de la syntaxe Java Pattern est spécifiquement recommandée.

2. Configuration

Alors que les expressions régulières ne font pas partie du langage Kotlin, elles sont livrées avec sa bibliothèque standard.

Nous l'avons probablement déjà en tant que dépendance de notre projet:

 org.jetbrains.kotlin kotlin-stdlib 1.2.21 

Nous pouvons trouver la dernière version de kotlin-stdlib sur Maven Central.

3. Création d'un objet d'expression régulière

Les expressions régulières sont des instances de la classe kotlin.text.Regex . Nous pouvons en créer un de plusieurs manières.

Une possibilité est d'appeler le constructeur Regex :

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

ou nous pouvons appeler la méthode toRegex sur une chaîne:

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

Enfin, nous pouvons utiliser une méthode de fabrique statique:

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

Sauf une différence expliquée dans la section suivante, ces options sont équivalentes et correspondent à vos préférences personnelles. N'oubliez pas d'être cohérent!

Conseil: les expressions régulières contiennent souvent des caractères qui seraient interprétés comme des séquences d'échappement dans les littéraux String . On peut ainsi utiliser des chaînes brutes pour oublier plusieurs niveaux d'échappement:

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

3.1. Options correspondantes

Le constructeur Regex et la méthode toRegex nous permettent de spécifier une seule option supplémentaire ou un ensemble:

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

Les options sont énumérées dans la classe RegexOption , que nous avons importé de manière statique dans l'exemple ci-dessus:

  • IGNORE_CASE - active la correspondance insensible à la casse
  • MULTILINE - change la signification de ^ et $ (voir Pattern)
  • LITERAL - fait que les métacaractères ou les séquences d'échappement dans le modèle n'ont pas de signification particulière
  • UNIX_LINES - dans ce mode, seul le \ n est reconnu comme terminateur de ligne
  • COMMENTAIRES - autorise les espaces et les commentaires dans le motif
  • DOT_MATCHES_ALL - fait correspondre le point à n'importe quel caractère, y compris un terminateur de ligne
  • CANON_EQ - active l'équivalence par décomposition canonique (voir Pattern)

4. Correspondance

Nous utilisons des expressions régulières principalement pour faire correspondre les chaînes d' entrée , et parfois pour en extraire ou en remplacer des parties.

Nous allons maintenant examiner en détail les méthodes proposées par la classe Regex de Kotlin pour faire correspondre les chaînes.

4.1. Vérification des correspondances partielles ou totales

Dans ces cas d'utilisation, nous souhaitons savoir si une chaîne ou une partie d'une chaîne satisfait notre expression régulière.

Si nous n'avons besoin que d'une correspondance partielle, nous pouvons utiliser containsMatchIn :

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

Si nous voulons que la chaîne entière corresponde à la place, nous utilisons des correspondances :

assertTrue(regex.matches("abcd"))

Notez que nous pouvons également utiliser des correspondances comme opérateur d'infixe:

assertFalse(regex matches "xabcdy")

4.2. Extraction des composants correspondants

Dans ces cas d'utilisation, nous voulons faire correspondre une chaîne à une expression régulière et extraire des parties de la chaîne.

Nous pourrions vouloir faire correspondre la chaîne entière :

val matchResult = regex.matchEntire("abbccbbd")

Ou nous pourrions vouloir trouver la première sous-chaîne qui correspond:

val matchResult = regex.find("abcbabbd")

Ou peut-être pour trouver toutes les sous-chaînes correspondantes à la fois, sous la forme d'un ensemble :

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

Dans les deux cas, si la correspondance réussit, le résultat sera une ou plusieurs instances de la classe MatchResult . Dans la section suivante, nous verrons comment l'utiliser.

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

Si nous avons besoin de transmettre notre expression régulière au code Java, ou à une autre API de langage JVM qui attend une instance de java.util.regex.Pattern , nous pouvons simplement convertir notre Regex :

regex.toPattern()

8. Conclusions

Dans cet article, nous avons examiné la prise en charge des expressions régulières dans la bibliothèque standard Kotlin.

Pour plus d'informations, consultez la référence Kotlin.

L'implémentation de tous ces exemples et extraits de code se trouve dans le projet GitHub - il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.