Passen Sie mehrzeiligen Text mit regulären Ausdrücken an

174

Ich versuche, einen mehrzeiligen Text mit Java abzugleichen. Wenn ich die PatternKlasse mit dem Pattern.MULTILINEModifikator verwende, kann ich übereinstimmen, aber ich kann dies nicht tun(?m).

Das gleiche Muster mit (?m)und unter Verwendung String.matchesscheint nicht zu funktionieren.

Ich bin sicher, ich vermisse etwas, aber keine Ahnung was. Bin nicht sehr gut in regulären Ausdrücken.

Das habe ich versucht

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
quelle

Antworten:

298

Zunächst verwenden Sie die Modifikatoren unter einer falschen Annahme.

Pattern.MULTILINEoder (?m)weist Java an, die Anker zu akzeptieren ^und $am Anfang und Ende jeder Zeile übereinzustimmen (andernfalls stimmen sie nur am Anfang / Ende der gesamten Zeichenfolge überein).

Pattern.DOTALLoder (?s)weist Java an, zuzulassen, dass der Punkt auch mit Zeilenumbruchzeichen übereinstimmt.

Zweitens schlägt in Ihrem Fall der reguläre Ausdruck fehl, weil Sie die matches()Methode verwenden, bei der erwartet wird, dass der reguläre Ausdruck mit der gesamten Zeichenfolge (\\W)*(\\S)*übereinstimmt. Dies funktioniert natürlich nicht, da nach dem Abgleichen noch einige Zeichen übrig sind.

Wenn Sie also einfach nach einer Zeichenfolge suchen, die mit beginnt User Comments:, verwenden Sie den regulären Ausdruck

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

mit der 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 wird dann den Text nach enthalten User Comments:

Tim Pietzcker
quelle
Ich versuche, ein Muster zu finden, das mit jeder Zeichenfolge übereinstimmt, die mit "Benutzerkommentare:" beginnt. Danach gibt "Benutzerkommentare:" etwas ein, das ein Benutzer in einen Textbereich eingibt, und kann daher alles enthalten - sogar neue Zeilen. Sieht so aus, als müsste ich viel in Regex lernen ...
Nivas
2
Das funktioniert (danke!) Ich habe das Muster ausprobiert (?s)User Comments:\s*(.*). Aus der Antwort von @Amarghosh habe ich das Muster erhalten User Comments: [\\s\\S]*. Unter diesen gibt es einen besseren oder empfohlenen Weg oder gibt es nur zwei verschiedene Wege, dasselbe zu tun?
Nivas
3
Sie bedeuten beide dasselbe; [\s\S]ist etwas expliziter ("mit jedem Zeichen übereinstimmen, das entweder ein Leerzeichen oder ein Nicht-Leerzeichen ist"), .ist leichter zu lesen, aber Sie müssen nach dem Modifikator (?s)oder suchen DOTALL, um herauszufinden, ob Zeilenumbrüche enthalten sind oder nicht. Ich würde es vorziehen, wenn .die Pattern.DOTALLFlagge gesetzt ist (dies ist leichter zu lesen und zu merken als (?s)meiner Meinung nach. Sie sollten das verwenden, womit Sie sich am wohlsten fühlen.
Tim Pietzcker
.*mit DOTALList besser lesbar. Ich habe den anderen verwendet, um zu zeigen, dass das Problem in den Unterschieden zwischen str.matches und matcher.find und nicht in den Flags liegt. +1
Amarghosh
Ich bevorzuge .*mit Pattern.DOTALL, muss aber mit (? S) gehen, weil ich verwenden muss String.matches.
Nivas
42

Dies hat nichts mit dem MULTILINE-Flag zu tun. Was Sie sehen, ist der Unterschied zwischen den find()und matches()Methoden. find()ist erfolgreich, wenn eine Übereinstimmung an einer beliebigen Stelle in der Zielzeichenfolge gefunden werden kann , während matches()erwartet wird , dass der reguläre Ausdruck mit der gesamten Zeichenfolge übereinstimmt .

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

Darüber hinaus MULTILINEbedeutet nicht, was Sie denken, dass es tut. Viele Leute scheinen zu dem Schluss zu kommen, dass Sie dieses Flag verwenden müssen, wenn Ihre Zielzeichenfolge Zeilenumbrüche enthält, dh wenn sie mehrere logische Zeilen enthält. Ich habe hier auf SO mehrere Antworten zu diesem Zweck gesehen, aber tatsächlich ändert dieses Flag nur das Verhalten der Anker, ^und $.

Entspricht normalerweise ^dem Anfang der Zielzeichenfolge und $dem Ende (oder vor einer neuen Zeile am Ende, aber das lassen wir vorerst beiseite). Wenn die Zeichenfolge jedoch Zeilenumbrüche enthält, können Sie am Anfang und am Ende jeder logischen Zeile und nicht nur am Anfang und Ende der gesamten Zeichenfolge auswählen ^und $übereinstimmen, indem Sie das Flag MULTILINE setzen.

Vergessen Sie also, was es MULTILINE bedeutet, und denken Sie daran, was es tut : Ändert das Verhalten der ^und $Anker. DOTALLDer Modus wurde ursprünglich als "einzeilig" bezeichnet (und ist immer noch in einigen Varianten erhältlich, einschließlich Perl und .NET), und hat immer zu ähnlicher Verwirrung geführt. Wir haben das Glück, dass die Java-Entwickler in diesem Fall den aussagekräftigeren Namen gewählt haben, aber es gab keine vernünftige Alternative für den "mehrzeiligen" Modus.

In Perl, wo all dieser Wahnsinn begann, haben sie ihren Fehler eingestanden und sowohl den "mehrzeiligen" als auch den "einzeiligen" Modus in Perl 6-Regexen beseitigt. In weiteren zwanzig Jahren wird vielleicht der Rest der Welt nachziehen.

Alan Moore
quelle
5
Kaum zu glauben, dass sie den Methodennamen "#matches" verwendeten, um "passt zu allen" Yikes zu bedeuten
Rogerdpack
@ Alan-Moore Tut mir leid, dass ich das hier runter
mache,
22

str.matches(regex) verhält sich wie Pattern.matches(regex, str) der Versuch, die gesamte Eingabesequenz mit dem Muster abzugleichen, und kehrt zurück

truegenau dann, wenn die gesamte Eingabesequenz mit dem Muster dieses Matchers übereinstimmt

Während matcher.find() versucht wird, die nächste Teilsequenz der Eingabesequenz zu finden, die dem Muster entspricht und zurückkehrt

truegenau dann, wenn eine Teilsequenz der Eingabesequenz mit dem Muster dieses Matchers übereinstimmt

Das Problem liegt also bei der Regex. Versuche Folgendes.

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

Kurz gesagt, der (\\W)*(\\S)*Teil in Ihrem ersten regulären Ausdruck entspricht einer leeren Zeichenfolge, was *bedeutet, dass keine oder mehrere Vorkommen vorhanden sind, und die tatsächlich übereinstimmende Zeichenfolge ist User Comments:und nicht die gesamte Zeichenfolge, wie Sie es erwarten würden. Der zweite Fehler schlägt fehl, da er versucht, mit der gesamten Zeichenfolge übereinzustimmen, kann jedoch nicht \\Wmit einem Nicht-Wort-Zeichen übereinstimmen, dh [^a-zA-Z0-9_]und das erste Zeichen ist Tein Wort-Zeichen.

Amarghosh
quelle
Ich möchte mit jeder Zeichenfolge übereinstimmen, die mit "Benutzerkommentare" beginnt, und die Zeichenfolge kann auch Zeilenumbrüche enthalten. Also habe ich das Muster verwendet User Comments: [\\s\\S]*und das hat funktioniert. (danke!) Aus der Antwort von @Tim habe ich das Muster erhalten User Comments:(.*), das ist auch in Ordnung. Gibt es nun einen empfohlenen oder besseren Weg unter diesen oder sind dies nur zwei Wege, dasselbe zu tun?
Nivas
@Nivas Ich glaube nicht, dass es einen Leistungsunterschied geben würde. aber ich denke (.*)zusammen mit DOTALLFlagge ist offensichtlicher / lesbarer als([\\s\\S]*)
Amarghosh
Dies ist die beste Antwort. Bietet sowohl Zugriff auf Java-Code als auch auf Optionen für Musterzeichenfolgen für MultiLine-Funktionen.
GoldBishop
0

Das mehrzeilige Flag weist Regex an, das Muster an jede Zeile anzupassen, im Gegensatz zur gesamten Zeichenfolge, damit für Ihre Zwecke ein Platzhalter ausreicht.

Yehuda Schwartz
quelle