web-dev-qa-db-fra.com

Faire correspondre le texte multiligne en utilisant une expression régulière

J'essaie de faire correspondre un texte de plusieurs lignes en utilisant Java. Lorsque j'utilise la classe Pattern avec le modificateur Pattern.MULTILINE, je peux faire correspondre, mais je ne peux pas le faire avec (?m).

Le même modèle 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. Suis pas très bon à des 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?
150
Nivas

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

Pattern.MULTILINE ou (?m) indique à Java d'accepter que les ancres ^ et $ correspondent au début et à la fin de chaque ligne (sinon, elles ne correspondent qu'au début/à la fin de la chaîne complète).

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

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

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

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

avec l'option Pattern.DOTALL:

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:

260
Tim Pietzcker

Cela n’a rien à voir avec le drapeau 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, alors que matches() s'attend à ce que l'expression rationnelle 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, MULTILINE ne signifie pas ce que vous pensez que cela fait. De nombreuses personnes semblent en venir à la conclusion que vous devez utiliser cet indicateur si votre chaîne cible contient des nouvelles lignes, c'est-à-dire si elle contient plusieurs lignes logiques. J'ai vu plusieurs réponses ici sur SO à cet effet, mais en fait, tout ce drapeau ne change que le comportement des ancres, ^ et $

Normalement, ^ correspond au tout début de la chaîne cible et $ à la fin (ou avant une nouvelle ligne à la fin, mais nous la laisserons de côté pour le moment). Toutefois, si la chaîne contient des nouvelles lignes, vous pouvez choisir de faire correspondre ^ et $ au début et à la fin de toute ligne logique, et pas seulement au début et à la fin de la chaîne entière, en définissant l'indicateur MULTILINE.

Donc oubliez ce que MULTILINEsignifie et souvenez-vous simplement de ce qu’il fait: modifie le comportement des ancres ^ et $. Le mode DOTALL s'appelait à l'origine "une seule ligne" (et existe toujours dans certaines versions, y compris Perl et .NET), et il a toujours provoqué une confusion similaire. Nous sommes chanceux que les développeurs Java aient opté pour un nom plus descriptif, mais il n’existait aucune 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, le reste du monde aura peut-être emboîté le pas.

37
Alan Moore

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

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

Considérant que matcher.find() _ { recherche) la sous-séquence suivante de la séquence d'entrée qui correspond au modèle et retourne 

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

Ainsi, le problème est avec 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

En bref, la partie (\\W)*(\\S)* de votre première expression rationnelle correspond à une chaîne vide, car * signifie zéro ou plusieurs occurrences et la chaîne réellement recherchée est User Comments: et non la chaîne entière comme prévu. Le second échoue car il essaie de faire correspondre la chaîne entière, mais ne peut pas, car \\W correspond à un caractère autre que Word, c'est-à-dire [^a-zA-Z0-9_], et le premier caractère est T, un caractère Word.

19
Amarghosh

Le drapeau multiligne indique à regex de faire correspondre le motif à chaque ligne, par opposition à la chaîne entière correspondant à vos besoins, un caractère générique suffit.

0
Yehuda Schwartz