Abbina il testo su più righe usando l'espressione regolare


174

Sto cercando di abbinare un testo a più righe usando Java. Quando uso la Patternclasse con il Pattern.MULTILINEmodificatore, sono in grado di abbinare, ma non riesco a farlo(?m).

Lo stesso modello con (?m)e usando String.matchesnon sembra funzionare.

Sono sicuro che mi manca qualcosa, ma non ho idea di cosa. Non sono molto bravo con le espressioni regolari.

Questo è quello che ho provato

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?

Risposte:


298

Innanzitutto, stai utilizzando i modificatori in base a un'ipotesi errata.

Pattern.MULTILINEoppure (?m)dice a Java di accettare le ancore ^e di $far combaciare all'inizio e alla fine di ogni riga (altrimenti corrispondono solo all'inizio / alla fine dell'intera stringa).

Pattern.DOTALLo (?s)dice a Java di consentire al punto di abbinare anche i caratteri di nuova riga.

In secondo luogo, nel tuo caso, la regex fallisce perché stai usando il matches()metodo che prevede che la regex corrisponda all'intera stringa - che ovviamente non funziona poiché ci sono alcuni caratteri rimasti dopo che (\\W)*(\\S)*sono stati abbinati.

Quindi, se stai semplicemente cercando una stringa che inizia con User Comments:, usa regex

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

con l' Pattern.DOTALLopzione:

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

ResultString conterrà quindi il testo dopo User Comments:


Sto cercando di trovare un modello che corrisponda a qualsiasi stringa che inizia con "Commenti utente:". Dopo questo "Commenti utente:" è qualcosa che un utente inserisce in un'area di testo e quindi può contenere qualsiasi cosa , anche nuove righe. Sembra che debba imparare molto in regex ...
Nivas,

2
Questo funziona (grazie!) Ho provato lo schema (?s)User Comments:\s*(.*). Dalla risposta di @Amarghosh ho ottenuto lo schema User Comments: [\\s\\S]*. Tra questi c'è un modo migliore o raccomandato o sono solo due modi diversi di fare lo stesso?
Nivas,

3
Entrambi significano lo stesso; [\s\S]è un po 'più esplicito ("corrisponde a qualsiasi carattere che sia uno spazio bianco o non-spazio"), .è più facile da leggere, ma è necessario cercare il modificatore (?s)o DOTALLper scoprire se sono incluse o meno nuove righe. Preferirei .con il Pattern.DOTALLset di bandiere (questo è più facile da leggere e ricordare che (?s)secondo me. Dovresti usare ciò con cui ti senti più a tuo agio.
Tim Pietzcker

.*con DOTALLè più leggibile. Ho usato l'altro per dimostrare che il problema è nelle differenze tra str.matches e matcher.find e non le bandiere. +1
Amarghosh,

Preferisco .*con Pattern.DOTALL, ma dovrò andare con (? S) perché devo usare String.matches.
Nivas,

42

Questo non ha nulla a che fare con la bandiera MULTILINE; quello che vedi è la differenza tra i metodi find()e matches(). find()ha esito positivo se è possibile trovare una corrispondenza in qualsiasi punto della stringa di destinazione , mentre si matches()aspetta che la regex corrisponda all'intera stringa .

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

Inoltre, MULTILINEnon significa quello che pensi che faccia. Molte persone sembrano saltare alla conclusione che è necessario utilizzare quel flag se la stringa di destinazione contiene nuove righe, ovvero se contiene più righe logiche. Ho visto diverse risposte qui su SO in tal senso, ma in realtà, tutto ciò che fa bandiera è cambiare il comportamento delle ancore, ^e $.

Normalmente ^corrisponde all'inizio della stringa di destinazione e $corrisponde alla fine (o prima di una nuova riga alla fine, ma per ora lo lasceremo da parte). Ma se la stringa contiene nuove righe, puoi scegliere ^e $far corrispondere all'inizio e alla fine di qualsiasi riga logica, non solo all'inizio e alla fine dell'intera stringa, impostando il flag MULTILINE.

Quindi dimentica cosa MULTILINE significa e ricorda cosa fa : cambia il comportamento di ^e delle $ancore. DOTALLla modalità era originariamente chiamata "single-line" (ed è ancora in alcuni gusti, tra cui Perl e .NET), e ha sempre causato confusione simile. Siamo fortunati che gli sviluppatori Java abbiano scelto il nome più descrittivo in quel caso, ma non c'erano alternative ragionevoli per la modalità "multilinea".

In Perl, dove è iniziata tutta questa follia, hanno ammesso il loro errore e si sono sbarazzati delle modalità "multilinea" e "a linea singola" nelle regex di Perl 6. Tra altri venti anni, forse il resto del mondo avrà seguito l'esempio.


5
Difficile credere che abbiano usato il nome del metodo "#matches" per indicare "corrisponde a tutti" yikes
rogerdpack

@ alan-moore
Scusatemi,

22

str.matches(regex) si comporta come il Pattern.matches(regex, str) tentativo di far corrispondere l'intera sequenza di input al modello e ritorna

trueif, e solo if, l' intera sequenza di input corrisponde al modello di questo matcher

Considerando che matcher.find() tenta di trovare la successiva sottosequenza della sequenza di input che corrisponde allo schema e ritorna

trueif, e solo if, una sottosequenza della sequenza di input corrisponde al modello di questo matcher

Quindi il problema è con la regex. Prova quanto segue.

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

Quindi, in breve, la (\\W)*(\\S)*parte nel tuo primo regex corrisponde a una stringa vuota come *zero o più occorrenze e la stringa reale corrispondente è User Comments:e non l'intera stringa come ti aspetteresti. Il secondo fallisce mentre cerca di far corrispondere l'intera stringa ma non può \\Wcorrispondere a un carattere non di parole, ovvero [^a-zA-Z0-9_]il primo carattere è Tun carattere di parola.


Voglio abbinare qualsiasi stringa che inizia con "Commenti utente" e la stringa può contenere anche nuove righe. Quindi ho usato il modello User Comments: [\\s\\S]*e questo ha funzionato. (grazie!) Dalla risposta di @Tim ho ottenuto lo schema User Comments:(.*), anche questo va bene Ora, c'è un modo consigliato o migliore tra questi, o sono solo due modi di fare lo stesso?
Nivas,

@Nivas Non credo che ci sarebbe alcuna differenza in termini di prestazioni; ma penso che (.*)insieme alla DOTALLbandiera sia più ovvio / leggibile di([\\s\\S]*)
Amarghosh,

Questa è la risposta migliore .... fornisce sia l'accesso al codice Java che le opzioni Pattern String, per la funzionalità MultiLine.
GoldBishop

0

Il flag multilinea dice a regex di far corrispondere il modello a ciascuna linea invece dell'intera stringa per i tuoi scopi sarà sufficiente un jolly.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.