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

174

J'essaie de faire correspondre un texte sur plusieurs lignes en utilisant java. Lorsque j'utilise la Patternclasse avec le Pattern.MULTILINEmodificateur, je peux faire correspondre, mais je ne peux pas le faire avec(?m).

Le même modèle avec (?m)et en utilisant String.matchesne 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.

C'est 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?
Nivas
la source

Réponses:

299

Tout d'abord, vous utilisez les modificateurs sous une hypothèse incorrecte.

Pattern.MULTILINEou (?m)dit à Java d'accepter les ancres ^et de $faire correspondre au début et à la fin de chaque ligne (sinon, ils ne correspondent qu'au début / à la fin de la chaîne entière).

Pattern.DOTALLou (?s)dit à Java d'autoriser également le point à correspondre aux caractères de nouvelle ligne.

Deuxièmement, dans votre cas, l'expression régulière échoue car vous utilisez la matches()méthode qui s'attend à ce que l'expression régulière corresponde à la chaîne entière - ce qui, bien sûr, ne fonctionne pas car il reste des caractères après (\\W)*(\\S)*avoir correspondu.

Donc, si vous recherchez simplement une chaîne qui commence par User Comments:, utilisez l'expression régulière

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

avec l' Pattern.DOTALLoption:

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:

Tim Pietzcker
la source
J'essaie de trouver un modèle qui correspondrait à n'importe quelle chaîne commençant par "Commentaires de l'utilisateur:". Après ce "Commentaires utilisateur:" est quelque chose qu'un utilisateur entre dans une zone de texte, et peut donc contenir n'importe quoi - même de nouvelles lignes. On dirait que j'ai besoin d'apprendre beaucoup de regex ...
Nivas
2
Cela fonctionne (merci!) J'ai essayé le modèle (?s)User Comments:\s*(.*). D'après la réponse de @Amarghosh, j'ai obtenu le modèle User Comments: [\\s\\S]*. Parmi ceux-ci, y a-t-il une manière meilleure ou recommandée ou s'agit-il simplement de deux façons différentes de faire la même chose?
Nivas
3
Ils signifient tous les deux la même chose; [\s\S]est un peu plus explicite ("correspond à n'importe quel caractère qui est soit un espace ou non"), .est plus facile à lire, mais vous devez rechercher le modificateur (?s)ou DOTALLafin de savoir si les retours à la ligne sont inclus ou non. Je préfère .avec le Pattern.DOTALLjeu de drapeaux (c'est plus facile à lire et à retenir qu'à (?s)mon avis. Vous devriez utiliser ce avec quoi vous vous sentez le plus à l'aise.
Tim Pietzcker
.*avec DOTALLest plus lisible. J'ai utilisé l'autre pour montrer que le problème réside dans les différences entre str.matches et matcher.find et non dans les indicateurs. +1
Amarghosh
Je préfère .*avec Pattern.DOTALL, mais je devrai aller avec (? S) car je dois utiliser String.matches.
Nivas
42

Cela n'a rien à voir avec l'indicateur MULTILINE; ce que vous voyez est la différence entre les méthodes find()et matches(). find()réussit si une correspondance peut être trouvée n'importe où dans la chaîne cible , tandis que matches()s'attend à ce que l'expression régulière 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

De plus, MULTILINEcela ne signifie pas ce que vous pensez que cela fait. Beaucoup de gens semblent sauter à la conclusion que vous devez utiliser cet indicateur si votre chaîne cible contient des retours à la ligne, c'est-à-dire si elle contient plusieurs lignes logiques. J'ai vu plusieurs réponses ici sur SO à cet effet, mais en fait, ce drapeau ne fait que changer le comportement des ancres, ^et $.

Correspond normalement ^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 contient des retours à la ligne, vous pouvez choisir pour ^et $faire correspondre au début et à la fin de toute ligne logique, pas seulement au début et à la fin de la chaîne entière, en définissant l'indicateur MULTILINE.

Alors oubliez ce que MULTILINE signifie et rappelez-vous simplement ce que cela fait : change le comportement des ancres ^et $. DOTALLLe mode était à l'origine appelé "single-line" (et l'est toujours dans certaines saveurs, y compris Perl et .NET), et il a toujours causé une confusion similaire. Nous avons la chance que les développeurs Java aient choisi le nom le plus descriptif dans ce cas, mais il n'y avait pas d'alternative raisonnable au mode "multiligne".

En Perl, où toute cette folie a commencé, ils ont admis leur erreur et se sont débarrassés des modes "multiligne" et "simple ligne" dans les expressions rationnelles Perl 6. Dans vingt ans, peut-être que le reste du monde aura emboîté le pas.

Alan Moore
la source
5
Difficile de croire qu'ils ont utilisé le nom de la méthode «#matches» pour signifier «correspond à tous» yikes
rogerdpack
@ alan-moore Désolé je descends cela même si c'est correct [besoin de plus de sommeil :)]
Raymond Naseef
22

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

truesi, et seulement si, toute la séquence d'entrée correspond au modèle de ce matcher

Tandis que matcher.find() tente de trouver la sous-séquence suivante de la séquence d'entrée qui correspond au modèle et renvoie

truesi, et seulement si, une sous -séquence de la séquence d'entrée correspond au modèle de ce matcher

Le problème vient donc de l'expression régulière. 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 bref, la (\\W)*(\\S)*partie de votre première expression régulière correspond à une chaîne vide comme *signifiant zéro ou plusieurs occurrences et la vraie chaîne correspondante est User Comments:et non la chaîne entière comme vous vous attendez. Le second échoue car il essaie de faire correspondre la chaîne entière mais il ne peut pas \\Wcorrespondre à un caractère non mot, c'est [^a-zA-Z0-9_]-à- dire que le premier caractère est Tun caractère mot.

Amarghosh
la source
Je veux faire correspondre n'importe quelle chaîne qui commence par "Commentaires de l'utilisateur", et la chaîne peut également contenir des retours à la ligne. J'ai donc utilisé le modèle User Comments: [\\s\\S]*et cela a fonctionné. (merci!) D'après la réponse de @Tim, j'ai obtenu le modèle User Comments:(.*), c'est également correct. Maintenant, y a-t-il un moyen recommandé ou meilleur parmi ceux-ci, ou s'agit-il simplement de deux façons de faire la même chose?
Nivas
@Nivas Je ne pense pas qu'il y aurait de différence en termes de performances; mais je pense que (.*)le DOTALLdrapeau est plus évident / lisible que([\\s\\S]*)
Amarghosh
C'est la meilleure réponse .... fournit à la fois l'accès au code Java et aux options de chaîne de modèle, pour la capacité MultiLine.
GoldBishop
0

L'indicateur multiligne indique à regex de faire correspondre le modèle à chaque ligne par opposition à la chaîne entière pour vos besoins, un caractère générique suffira.

Yehuda Schwartz
la source