Accueil  / Semaine 5 / Regex avec Java

Regex avec Java

Avertissement Avant de continuer, assurez-vous que vous pouvez compiler et exécuter des programmes Java sans problème. Retournez à l’activité de rappel au besoin.

Java et les expressions régulières

Depuis la version 1.4, Java dispose d’un moteur d’expressions régulières puissant et très complet. C’est un moteur basé sur un automate fini non déterministe, et il est donc possible pour certaines expressions régulières de requérir un très long temps de traitement même si le moteur d’expressions régulières de Java est très rapide en général. De plus, il supporte les quantificateurs avides, paresseux et possessifs et la plupart des autres fonctions dont nous avons discuté jusqu’à présent.

Les classes qui nous concernent sont dans le paquetage java.util.regex. Les deux principales classes sont :
- Pattern : elle représente un motif défini par une expression régulière ; la documentation de la classe en question comprend un rappel très utile sur les expressions régulières.
- Matcher : cette classe permet de visiter les différentes occurrences des motifs.

Un « bémol » en débutant

Nous avons vu, par exemple, qu’un espace peut être noté « \s » avec les expressions régulières. Cependant, la chaîne « \s » en Java ne peut être utilisée comme telle parce que le symbole « ~» est utilisé en Java pour les caractères d’échappement. Par exemple, un retour de chariot est noté « \r », un espace de tabulation « \t », etc. Ainsi, pour représenter « \w », « \s », etc., en Java, dans le contexte des expressions régulières, il faut doubler le symbole « ~» et écrire plutôt « \w », « \s », etc. Cependant, ce n’est pas le cas pour les notations en « \p... » telles que « \pLu » supportées par Java (voir la documentation Java).

Notons aussi que si vous voulez inclure des guillemets dans une chaîne de caractères, il faut les « échapper », « "\"" » (Windows et la plupart des autres plateformes), ou bien utiliser les apostrophes pour délimiter votre chaîne de caractères, « ’"’ » (Linux, macOS mais pas Windows).

Programmation de base avec les expressions régulières

La classe « java.lang.String » en Java contient déjà un support assez avancé pour les expressions régulières. On peut ainsi vérifier la concordance d’un motif, séparer une chaîne de caractères en sous-chaînes en utilisant des motifs et, finalement, utiliser la fonction rechercher/remplacer.

Étant donné une chaîne de caractères en Java, la méthode « matches » permet de vérifier si elle correspond à une expression régulière. Dans cet exemple, la variable « test » prendra la valeur « vrai ».

String s = "La vie";
boolean test = s.matches("La.*");

On peut aussi séparer une chaîne de caractères en sous-chaînes dans lesquelles les points de segmentation sont déterminés par une expression régulière. Dans l’exemple suivant, la variable « résultat » aura pour valeur ("La vie est belle", "Jean", "il ne m’aime pas").

String s = "La vie est belle. Jean, il ne m'aime pas.";
String[] resultat = s.split(",|\\.");

Finalement, la fonction rechercher/remplacer avec des expressions régulières fait appel à la méthode « replaceAll ». Pour remplacer les chiffres par des « X » dans une chaîne de caractères, on procède ainsi :

String s = "Mon numéro de carte de crédit est le 4243243243.";
String résultat = s.replaceAll("\\d","X");

Le résultat sera « Mon numéro de carte de crédit est le XXXXXXXXXX. ».

Notez qu’un objet String, en Java, est immuable : la méthode « replaceAll » ne modifie donc pas l’objet, mais présente plutôt une version modifiée de l’objet.

Programmation avancée avec les expressions régulières

Outre l’objet java.lang.String, il existe un paquetage dédié aux expressions régulières qui permet, par exemple, de ne compiler l’expression régulière qu’une seule fois en utilisant un objet de classe « Pattern ». Quand on doit traiter plusieurs chaînes de caractères avec les expressions régulières, il est sans doute préférable d’utiliser ce paquetage.

Aller chercher le paquetage

Il faut toujours commencer par :

import java.util.regex.*;

quand on veut utiliser les expressions régulières en Java.

Lire une expression régulière

La première étape est toujours la même : il faut « compiler » l’expression régulière pour en faire un objet Java :

Pattern pattern = Pattern.compile("\\w+");

Appliquer l’expression à une chaîne de caractères

On applique une expression régulière à une chaîne de caractères avec la méthode « matcher ».

Matcher matcher = pattern.matcher("La vie est belle");

Tester si la chaîne est le motif

Une fois un objet « Matcher » obtenu, on peut tester si la chaîne de caractères est un motif correspondant à l’expression régulière avec la méthode « matches » :

boolean correspond = matcher.matches();

Dans notre cas, la réponse est négative parce que notre chaîne n’est pas une lettre répétée plusieurs fois.

Tester la présence du motif

Une fois un objet « Matcher » obtenu, on peut tester si le motif se trouve dans la chaîne de caractères avec la méthode « find ».

boolean trouve = matcher.find();

Dans notre cas, la réponse serait positive parce qu’il y a la lettre « l » répétée dans notre chaîne. La méthode « find() » a aussi comme effet de trouver la prochaine occurrence du motif dans la chaîne. On peut ensuite aller chercher la position et la nature du motif avec les méthodes « group », « start » et « end » :

           String texte = matcher.group();         // "ll"
           int debut = matcher.start();            // 13
           int fin = matcher.end();                // 15

Pour repérer toutes les occurrences, il suffit d’écrire une boucle comme ceci :

while (matcher.find()) {
           String texte = matcher.group();         // "ll"
           int debut = matcher.start();            // 13
           int fin = matcher.end();                // 15
           System.out.println("Trouvé "+texte+" à la position "+debut);
}

Les groupes de capture

Il arrive qu’on veuille capturer avec une seule expression régulière plusieurs motifs. Par exemple, supposons qu’on cherche un motif qui prend la forme d’un nombre suivi d’une virgule suivi d’un autre nombre (4343,4443). On peut capturer ce motif avec une simple expression régulière (\d+,\d+). Mais comment faire, en Java, pour avoir facilement accès au premier nombre puis au second ? On peut obtenir ce résultat en ajoutant des paranthèses (dites capturantes) autour des motifs qui nous intéressent ((\d+),(\d+)). On peut alors spécifier quel motif on souhaite retrouver avec la méthode "group" à laquelle on passe l’index correspondant :

Pattern pattern = Pattern.compile ("(\\d+),(\\d+)");
Matcher matcher = pattern.matcher ("4343,4443");
if (matcher.matches())
{
   System.out.println ("Premier nombre : " + matcher.group (1));
   System.out.println ("Deuxième nombre : " + matcher.group (2));
}

La fonction recherche/remplace

Supposons que l’on veuille remplacer toutes les lettres répétées par le symbole « * ». Ce résultat est aisément obtenu avec la méthode « replaceAll » :
 matcher.replaceAll("*").
La seule mise en garde est que si on tente de remplacer un texte qui contient le symbole du dollar, il faut l’indiquer comme ceci : « \$ ».

Supposons que l’on veuille remplacer toutes les lettres répétées par une seule occurrence de la même lettre, de sorte que « belle » devienne « bele » ; on peut obtenir ce résultat avec la méthode « replaceAll ». Il faut alors utiliser les parenthèses capturantes et la convention selon laquelle « $1 » fait référence à la première parenthèse capturante rencontrée et ainsi de suite.

 Pattern pattern = Pattern.compile("(\\w)\1+"); //notons la parenthèse capturante
matcher.replaceAll("$1"); // le $1 fait référence à la première lettre

Deux exemples de programmes Java

Premier exemple : pour tester les expressions régulières

Ce premier programme vise à vous permettre de tester votre compréhension des expressions régulières. Il vérifie que l’expression régulière correspond bien au texte donné, et il vous donne la liste des motifs correspondants trouvés. Il s’utilise de cette manière :

java Motif "foo.*" "foolalala"

Voici le code source :

import java.util.regex.*;


public class Motif {
        public static void main(String[] args) {
                Pattern RegexCompile = Pattern.compile(args[0]);
                System.out.println("Reconnaissance globale: "
                                + RegexCompile.matcher(args[1]).matches());
               Matcher m = RegexCompile.matcher(args[1]);
               while( m.find() ) {
                     System.out.println(m.group());
               }
        }        
}

https://github.com/lemire/notesdeco...

Pour tester les limites du traitement des expressions régulières en Java, vous pouvez essayer l’exemple suivant [1] :

java Motif "^(\\s*foo\\s*)*$" "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo fo"

Second exemple : un programme simulant grep

On peut écrire un programme qui ressemble beaucoup à grep en utilisant Java. Le programme appliquera alors une expression régulière à chaque ligne d’un programme donné et il n’affichera une ligne que si elle contient un motif correspondant.

Pour trouver toutes les lignes dans le fichier toto.txt qui contiennent deux guillemets, il suffit de faire (macOS ou Linux) :

java Regex '".*"' toto.txt

ou cette version plus complexe (Windows et autres plateformes)

java Regex "\".*\"" toto.txt

On peut aussi traiter plusieurs fichiers ainsi :

java Regex '".*"' toto1.txt toto2.txt

Pour traiter un ensemble de fichiers dans le répertoire courant, on peut aussi utiliser l’astérisque :

java Regex '".*"' *.txt

Pour obtenir plus de détails sur le traitement, il suffit d’ajouter le drapeau « -v » juste après « Regex » sur la ligne de commande :

java Regex -v '".*"' *.txt

Finalement, il est toujours possible de rediriger le résultat vers un fichier qu’on pourra ouvrir plus tard avec un éditeur de texte comme Bloc-notes, comme ceci :

java Regex  '".*"' *.txt > resultats.txt

https://github.com/lemire/notesdeco...

Pour en savoir plus...

Mehran Habibi, Java Regular Expressions : Taming the java.util.regex Engine, Berkeley (CA), Apress, 29 octobre 2003, 280 p.


[1Selon votre environnement de travail, il est possible que vous deviez remplacer, en ligne de commande, java Motif "^(\\s*foo\\s*)*$" par java Motif "^(\s*foo\s*)*$".