203 votes

Faire correspondre un texte multiligne à l'aide d'une expression régulière

J'essaie de faire correspondre un texte de plusieurs lignes en utilisant Java. Lorsque j'utilise la fonction Pattern avec la classe Pattern.MULTILINE je suis en mesure d'établir une correspondance, mais pas avec le modificateur (?m).

Le même schéma avec (?m) et en utilisant String.matches ne semble pas fonctionner.

Je suis sûr qu'il me manque quelque chose, mais je ne sais pas quoi. Je ne suis pas très doué pour les expressions régulières.

Voici ce que j'ai essayé

String test = "User Comments: This is \t a\ta \n test \n\n message \n";

String pattern1 = "User Comments: (\\W)*(\\S)*";
Pattern p = Pattern.compile(pattern1, Pattern.MULTILINE);
System.out.println(p.matcher(test).find());  //true

String pattern2 = "(?m)User Comments: (\\W)*(\\S)*";
System.out.println(test.matches(pattern2));  //false - why?

362voto

Tim Pietzcker Points 146308

D'abord, vous utilisez les modificateurs en partant d'une hypothèse incorrecte.

Pattern.MULTILINE ou (?m) indique à Java d'accepter les ancres ^ et $ pour qu'ils correspondent au début et à la fin de chaque ligne (sinon, ils ne correspondent qu'au début et à la fin de la chaîne entière).

Pattern.DOTALL ou (?s) indique à Java d'autoriser le point à correspondre aux caractères de nouvelle ligne, également.

Deuxièmement, dans votre cas, la regex échoue parce que vous utilisez l'attribut matches() qui s'attend à ce que l'expression rationnelle corresponde à l'objet de l'enquête. tout le site ce qui, bien sûr, ne fonctionne pas puisqu'il reste quelques caractères après (\\W)*(\\S)* ont correspondu.

Donc si vous cherchez simplement une chaîne qui commence par User Comments: utiliser le regex

^\s*User Comments:\s*(.*)

avec le Pattern.DOTALL option :

Pattern regex = Pattern.compile("^\\s*User Comments:\\s+(.*)", Pattern.DOTALL);
Matcher regexMatcher = regex.matcher(subjectString);
if (regexMatcher.find()) {
    ResultString = regexMatcher.group(1);
} 

ResultString contiendra alors le texte après User Comments:

0 votes

J'essaie de trouver un motif qui correspondrait à toute chaîne commençant par "User Comments :". Après ce "Commentaires de l'utilisateur :" est quelque chose qu'un utilisateur saisit dans une zone de texte, et peut donc contenir tout ce qui est - même de nouvelles lignes. On dirait que j'ai besoin d'apprendre beaucoup de choses en regex...

3 votes

Cela fonctionne (merci !) J'ai essayé le modèle (?s)User Comments:\s*(.*) . De la réponse de @Amarghosh j'ai obtenu le modèle User Comments: [\\s\\S]* . Parmi ceux-ci, il y a un meilleur ou recommandé ou s'agit-il simplement de deux façons différentes de faire la même chose ?

3 votes

Les deux signifient la même chose ; [\s\S] est un peu plus explicite ("correspond à tout caractère qui est soit un espace ou un non-espace"), . est plus facile à lire, mais vous devez chercher l'élément (?s) ou DOTALL afin de savoir si les nouvelles lignes sont incluses ou non. Je préférerais . avec le Pattern.DOTALL (ce qui est plus facile à lire et à retenir que l'option (?s) à mon avis. Vous devez utiliser ce avec quoi vous vous sentez le plus à l'aise.

50voto

Alan Moore Points 39365

Cela n'a rien à voir avec le drapeau MULTILINE ; ce que vous voyez, c'est la différence entre l'affichage de l'écran de l'ordinateur et celui de l'écran de l'ordinateur. find() et matches() méthodes. find() réussit si une correspondance est trouvée n'importe où dans la chaîne cible alors que matches() attend de la regex qu'elle corresponde à la chaîne entière .

Pattern p = Pattern.compile("xyz");

Matcher m = p.matcher("123xyzabc");
System.out.println(m.find());    // true
System.out.println(m.matches()); // false

Matcher m = p.matcher("xyz");
System.out.println(m.matches()); // true

En outre, MULTILINE ne signifie pas ce que vous pensez. De nombreuses personnes semblent en conclure que vous devez utiliser ce drapeau si votre chaîne cible contient des nouvelles lignes, c'est-à-dire si elle contient plusieurs lignes logiques. J'ai vu plusieurs réponses à cet effet sur SO, mais en fait, ce drapeau ne fait que modifier le comportement des ancres, ^ et $ .

Normalement ^ correspond au tout début de la chaîne cible, et $ correspond à la toute fin (ou avant une nouvelle ligne à la fin, mais nous laisserons cela de côté pour l'instant). Mais si la chaîne de caractères contient des nouvelles lignes, vous pouvez choisir pour ^ et $ pour correspondre au début et à la fin de n'importe quelle ligne logique, et pas seulement au début et à la fin de la chaîne entière, en activant le drapeau MULTILINE.

Alors oublie ce que MULTILINE signifie et souviens-toi juste de ce qu'il fait : modifie le comportement de la ^ et $ ancres. DOTALL était à l'origine appelé "single-line" (et l'est toujours dans certaines versions, notamment Perl et .NET), et il a toujours suscité la même confusion. Nous avons de la chance que les développeurs de Java aient choisi le nom le plus descriptif dans ce cas, mais il n'y avait pas d'alternative raisonnable pour le mode "multiligne".

En Perl, où toute cette folie a commencé, ils ont admis leur erreur et se sont débarrassés des modes "multiline" et "single-line" dans les regex de Perl 6. Dans vingt ans, le reste du monde aura peut-être suivi le mouvement.

6 votes

Il est difficile de croire qu'ils ont utilisé le nom de la méthode "#matches" pour signifier "matches all".

0 votes

@alan-moore Désolé d'avoir descendu ceci même si c'est correct [ besoin de plus de sommeil :) ].

23voto

Amarghosh Points 33957

str.matches(regex) se comporte comme Pattern.matches(regex, str) qui tente de faire correspondre la séquence d'entrée entière avec le modèle et renvoie

true si, et seulement si, le tout le site la séquence d'entrée correspond au modèle de ce matcheur

Considérant que matcher.find() tente de trouver la sous-séquence suivante de la séquence d'entrée qui correspond au motif et retourne

true si, et seulement si, un sous-séquence de la séquence d'entrée correspond au motif de ce matcheur

Le problème se situe donc au niveau de la regex. Essayez ce qui suit.

String test = "User Comments: This is \t a\ta \ntest\n\n message \n";

String pattern1 = "User Comments: [\\s\\S]*^test$[\\s\\S]*";
Pattern p = Pattern.compile(pattern1, Pattern.MULTILINE);
System.out.println(p.matcher(test).find());  //true

String pattern2 = "(?m)User Comments: [\\s\\S]*^test$[\\s\\S]*";
System.out.println(test.matches(pattern2));  //true

Ainsi, en résumé, le (\\W)*(\\S)* dans votre première regex correspond à une chaîne vide comme * signifie qu'il y a zéro ou plusieurs occurrences et que la véritable chaîne correspondante est User Comments: et non pas la chaîne entière comme on pourrait s'y attendre. La seconde échoue car elle essaie de faire correspondre la chaîne entière mais elle ne peut pas car \\W correspond à un caractère non verbal, c'est-à-dire [^a-zA-Z0-9_] et le premier caractère est T un caractère de mot.

0 votes

Je veux faire correspondre toute chaîne qui commence par "Commentaires de l'utilisateur", et la chaîne peut également contenir des nouvelles lignes. J'ai donc utilisé le modèle User Comments: [\\s\\S]* et ça a marché. (merci !) De la réponse de @Tim j'ai obtenu le modèle User Comments:(.*) c'est aussi ok. Maintenant, y a-t-il une recommandé ou meilleur ou s'agit-il simplement de deux façons de faire la même chose ?

0 votes

@Nivas Je ne pense pas qu'il y ait de différence au niveau des performances, mais je pense que (.*) ainsi que DOTALL est plus évident/lisible que ([\\s\\S]*)

0 votes

Il s'agit de la meilleure réponse : ..... Elle offre à la fois un accès au code Java et des options de chaîne de caractères, pour la capacité MultiLine.

1voto

Yehuda Schwartz Points 1211

Le drapeau multiligne indique à regex de faire correspondre le motif à chaque ligne plutôt qu'à la chaîne entière ; pour vos besoins, un joker suffira.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X