Dopasuj tekst wielowierszowy za pomocą wyrażenia regularnego


174

Próbuję dopasować tekst wielowierszowy za pomocą języka Java. Kiedy używam Patternklasy z Pattern.MULTILINEmodyfikatorem, jestem w stanie dopasować, ale nie jestem w stanie tego zrobić z(?m).

Wydaje się, że ten sam wzorzec zi (?m)używania String.matchesnie działa.

Jestem pewien, że czegoś mi brakuje, ale nie mam pojęcia, co. Nie jestem dobry w wyrażeniach regularnych.

Właśnie tego próbowałem

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?

Odpowiedzi:


298

Po pierwsze, używasz modyfikatorów przy nieprawidłowym założeniu.

Pattern.MULTILINElub (?m)mówi Javie, aby zaakceptowała kotwice ^i $dopasowała na początku i na końcu każdego wiersza (w przeciwnym razie pasują one tylko na początku / końcu całego ciągu).

Pattern.DOTALLlub (?s)mówi Javie, aby zezwoliła również kropce na dopasowanie znaków nowej linii.

Po drugie, w twoim przypadku wyrażenie regularne zawodzi, ponieważ używasz matches()metody, która oczekuje, że wyrażenie regularne będzie pasować do całego ciągu - co oczywiście nie działa, ponieważ po (\\W)*(\\S)*dopasowaniu pozostało kilka znaków .

Więc jeśli szukasz po prostu ciągu zaczynającego się od User Comments:, użyj wyrażenia regularnego

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

z Pattern.DOTALLopcją:

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

ResultString będzie zawierać tekst po User Comments:


Próbuję znaleźć wzorzec, który pasowałby do dowolnego ciągu zaczynającego się od „Komentarze użytkownika:”. Po tym „Komentarze użytkownika:” to coś, co użytkownik wprowadza w obszarze tekstowym i dlatego może zawierać wszystko - nawet nowe wiersze. Wygląda na to, że muszę się dużo nauczyć w wyrażeniach regularnych ...
Nivas,

2
To działa (dzięki!) Wypróbowałem wzór (?s)User Comments:\s*(.*). Z odpowiedzi @Amarghosh mam wzór User Comments: [\\s\\S]*. Czy jest wśród nich lepszy lub zalecany sposób, czy też są to tylko dwa różne sposoby robienia tego samego?
Nivas,

3
Obie mają to samo na myśli; [\s\S]jest nieco bardziej wyraźny („dopasuj dowolny znak, który jest albo białą, albo nie-białą spacją”), .jest łatwiejszy do odczytania, ale musisz poszukać modyfikatora (?s)lub DOTALL, aby dowiedzieć się, czy nowe linie są uwzględnione, czy nie. Wolałbym .z Pattern.DOTALLustawioną flagą (jest to łatwiejsze do odczytania i zapamiętania niż (?s)moim zdaniem. Powinieneś używać tego, z czym czujesz się najbardziej komfortowo.
Tim Pietzcker

.*ze DOTALLjest bardziej czytelny. Użyłem drugiego, aby pokazać, że problem tkwi w różnicach między str.matches i matcher.find, a nie w flagach. +1
Amarghosh,

Wolę .*z Pattern.DOTALL, ale będę musiał iść z (?), Ponieważ muszę używać String.matches.
Nivas

42

Nie ma to nic wspólnego z flagą MULTILINE; to, co widzisz, to różnica między metodami find()i matches(). find()powiedzie się, jeśli dopasowanie można znaleźć w dowolnym miejscu w ciągu docelowym , podczas gdy matches()oczekuje, że wyrażenie regularne będzie pasować do całego ciągu .

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

Co więcej, MULTILINEnie oznacza tego, co myślisz, że robi. Wiele osób wydaje się dochodzić do wniosku, że musisz użyć tej flagi, jeśli twój docelowy ciąg zawiera znaki nowej linii - to znaczy, jeśli zawiera wiele linii logicznych. Widziałem tutaj kilka odpowiedzi na SO w tym celu, ale w rzeczywistości wszystko, co robi ta flaga, to zmienia zachowanie kotwic, ^i $.

Zwykle ^dopasowuje sam początek ciągu docelowego i $dopasowuje sam koniec (lub przed nową linią na końcu, ale na razie zostawimy to na boku). Ale jeśli ciąg zawiera znaki nowej linii, możesz wybrać opcję ^i $dopasować na początku i na końcu dowolnej linii logicznej, a nie tylko na początku i na końcu całego ciągu, ustawiając flagę MULTILINE.

Więc zapomnij o tym, co MULTILINE oznacza i po prostu pamiętaj, co robi : zmienia zachowanie kotwic ^i $. DOTALLtryb początkowo był nazywany „jednoliniowym” (i nadal występuje w niektórych wersjach, w tym w Perlu i .NET) i zawsze powodował podobne zamieszanie. Na szczęście twórcy Javy wybrali w tym przypadku bardziej opisową nazwę, ale nie było rozsądnej alternatywy dla trybu „wielowierszowego”.

W Perlu, gdzie całe to szaleństwo się zaczęło, przyznali się do błędu i pozbyli się trybu „wielowierszowego” i „pojedynczej linii” w wyrażeniach regularnych Perl 6. Za dwadzieścia lat może reszta świata pójdzie w ich ślady.


5
Trudno uwierzyć, że użyli nazwy metody „#matches” na oznaczenie „pasuje do wszystkich” yikes
rogerdpack

@ alan-moore Przepraszam, że to prawda, mimo że jest poprawne [potrzebuję więcej snu :)]
Raymond Naseef

22

str.matches(regex) zachowuje się tak, jakby Pattern.matches(regex, str) próbował dopasować całą sekwencję wejściową do wzorca i zwraca

truejeśli i tylko wtedy, gdy cała sekwencja wejściowa pasuje do wzorca tego dopasowania

Natomiast matcher.find() próbuje znaleźć następny podciąg sekwencji wejściowej, który pasuje do wzorca i zwraca

truejeśli i tylko wtedy, gdy podciąg sekwencji wejściowej pasuje do wzorca tego dopasowania

Tak więc problem dotyczy wyrażenia regularnego. Spróbuj wykonać następujące czynności.

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

Krótko mówiąc, (\\W)*(\\S)*część w twoim pierwszym wyrażeniu regularnym dopasowuje pusty ciąg, ponieważ *oznacza zero lub więcej wystąpień, a prawdziwy dopasowany ciąg to, User Comments:a nie cały ciąg, jak można się spodziewać. Drugi zawodzi, ponieważ próbuje dopasować cały ciąg, ale nie może \\Wdopasować znaku innego niż słowo, tj. [^a-zA-Z0-9_]A pierwszy znak to Tznak słowa.


Chcę dopasować dowolny ciąg zaczynający się od „Komentarze użytkownika”, a ciąg może również zawierać znaki nowej linii. Więc użyłem wzoru User Comments: [\\s\\S]*i to zadziałało. (dzięki!) Z odpowiedzi @Tim mam wzorzec User Comments:(.*), to również jest w porządku. Czy jest wśród nich zalecany lub lepszy sposób, czy są to tylko dwa sposoby na zrobienie tego samego?
Nivas

@Nivas Nie sądzę, żeby była jakaś różnica jeśli chodzi o wydajność; ale myślę, że (.*)wraz z DOTALLflagą jest bardziej oczywiste / czytelne niż([\\s\\S]*)
Amarghosh

To jest najlepsza odpowiedź ... zapewnia dostęp zarówno do kodu Java, jak i do opcji Pattern String dla funkcji MultiLine.
GoldBishop

0

Flaga multilinii mówi regex, aby dopasował wzorzec do każdej linii, a nie do całego ciągu. Do twoich celów wystarczy wieloznaczna karta.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.