ven 19 aprile 2024 - Lo Sviluppatore  anno VI

Java: Le espressioni regolari

regex
Condividi

Cosa sono le espressioni regolari?

Una espressione regolare (in inglese regular expression, in forma abbreviata, regexp, regex o RE) definisce una funzione che prende in ingresso una stringa, e restituisce in uscita un valore del tipo sì/no, a seconda che la stringa segua o meno un certo  pattern. Ad esempio, tutti gli  indirizzi e-mail devono essere costituiti nel seguente modo:   cominciare  con una sequenza di caratteri alfanumerici,  seguiti dal simbolo chiocciola, seguiti da altri caratteri alfanumerici, seguiti dal punto, seguiti da due o tre lettere.
Questa regola informale diventerebbe una  regex qualora fosse codificata secondo   una   sintassi   ben   precisa   e   riconosciuta   da   un   programma   in   grado   di analizzare le stringhe.

Le espressioni regolari  sono uno strumento molto potente per lavorare sulle stringhe. Sono costituite da una sequenza di caratteri e possono essere utilizzate, sia per convalidare i dati, sia per effettuare ricerche all’interno di un testo. Per creare un’espressione regolare è necessario conoscere una sintassi specifica. La sintassi di questo pseudo-linguaggio è molto flessibile e consente di creare espressioni in base alle proprie esigenze.

La sintassi nei vari linguaggi di programmazione è più o meno simile e quindi una volta imparato questo pseudo-linguaggio l’uso con un linguaggio anzichè un’altro è pressochè uguale. In questo articolo in partcolare vedremo l’uso con Java.

La sintassi delle Regexp

Prima di vedere come lavorare con le regexp con il linguaggio java vediamo un pò di capire la sintassi del linguaggio per definire una regexp.

Per definire un’espressione regolare è necessario conoscere alcune regole base:

  • […] Insieme di caratteri validi;
  • [^…] Insieme negato di caratteri validi;
  • Intervallo;
  • && Intersezione;
  • . Qualunque carattere;
  • + Concatenazione;
  • ^regex il carattere ^ indica che il match deve essere all’inizio della linea
  • regex$ il carattere $ indica che il match deve essere alla fine della linea
  • regex* Cardinalità multipla (0 o più occorrenze dell’espressione regex);
  • regex{n} Cardinalità multipla (esattamente n occorrenze dell’espressione regex);
  • regex{n,} Cardinalità multipla (almeno n occorrenze dell’espressione regex);
  • regex{n,m} Cardinalità multipla (almeno n occorrenze dell’espressione regex, ma non più di m).

Facciamo alcuni esempi semplici per chiarire meglio le regole:

  • [abc]  Espressione che contiene un solo carattere tra a, b, o c;
  • [^abc]  Espressione che contiene un solo carattere, tutti sono validi, ad esclusione di a, b, o c;
  • [a-z]  Espressione che contiene un solo carattere tra quelli del range a-z;
  • [a-d[m-p]]  Espressione che contiene un solo carattere tra quelli del range a-d e del range m-p (unione);
  • [a-z&&[def]]  Espressione che contiene un solo carattere tra quelli comuni del range a-z e dell’insieme (d, e, f) (intersezione);
  • [a-z&&[^bc]]  Espressione che contiene un solo carattere tra quelli comuni del range a-z e dell’insieme negato (b, f) (intersezione);
  • [a-z&&[^m-p]]  Espressione che contiene un solo carattere tra quelli comuni del range a-z e del range negato (m-p) (intersezione).
  • [x|y]  Espressione che contiene un solo carattere che può essere x oppure y

Esistono anche forme abbreviate per rappresentare un insieme di caratteri. Ad esempio:

  • \d Carattere numerico. Corrisponde all’insieme [0-9];
  • \D Carattere diverso da un numero. Corrisponde all’insieme [^0-9];
  • \s Carattere spazio;
  • \S Carattere diverso dallo spazio. Corrisponde all’insieme [^\s];
  • \w carattere alfanumerico. Corrisponde all’insieme [a-zA-Z_0-9];
  • \W carattere costituito da caratteri speciali. Corrisponde all’insieme [^\w]

Gli esempi visti finora sono per stringhe di lunghezza 1 ma nei casi reali si ha normalmente a che fare con stringhe di lunghezza maggiore. Per questa esigenza esisistono quelli che si chiamano quantificatori che permettono di definire il numero di occorenze di un carattere

a? il carattere “a” occorre zero o una volta
a* il carattere “a” occorre zero o più volte
a+ il carattere “a” occorre una o più volte
a{n} il carattere “a” occorre esattamente n volte
a{n,} il carattere “a” occorre n o più volte
a{n,m} il carattere “a” occorre almeno n volte ma non più di m volte

 

Regexp in Java

Java fornisce la Java Regex API all’interno del package java.util.regex che contiene le tre classi: Pattern, Matcher e PatternSyntaxException.

Pattern: E’ la rappresentazione compilata dell’espressione regolare. Un’espressione regolare che è specificata come stringa, deve essere compilata in un’istanza della classe Pattern. Il modello creato può essere utilizzato per creare un oggetto Matcher.

Matcher: Un oggetto di tipo Matcher viene creato da un Pattern mediante l’invocazione del metodo matcher.

PatternSyntaxException: Eccezione lanciata quando viene compilata un espressione regolare con un sintassi errata.

Ma vediamo i modi di utilizzare queste classi:

Uso della classe Pattern

split

// matcha con stringhe formate da 1 o più cifre numeriche
Pattern pattern = Pattern.compile("\\d+");
String[] st = pattern.split("20 patate, 10 pomodori, 5 cipolle");
for(int i = 1; i < st.length; i++) {
   System.out.println("ingrediente " + i + " : " + st[i]);
}

split() divide la stringa di input basandosi sullo specifica Pattern. Nell’esempio precedente, il metodo split cercherà qualsiasi carattere numerico che appare una o più volte nella stringa di input.

output:
ingrediente 1 : patate,
ingrediente 2 : pomodori,
ingrediente 3 : cipolle

flags

Un Pattern può essere creato associando un flag che per mette di modificare il comportamento del match. Per esempio la seguente definizione di Pattern abilita il match case insensitive:

Pattern pattern = Pattern.compile("abc", Pattern.CASE_INSENSITIVE);

matches

La classe Pattern ha dei metodi matches che prendono in input una espressione regolare e una stringa da analizzare e restutise true o false a secondo se la la stringa in input matcha con l’epressione regolare o meno.

System.out.println("Matches: " + pattern.matches(".*", "abcd654xyz00")); // true

Nota:  Il backslash \ è un carattere di escape in una stringa Java, è pertanto necessario utilizzare il doppio backslash \\ per definire una singola barra rovesciata. Se si desidera definire \ w, allora è necessario utilizzare \\ w nella tua regex. Se si desidera utilizzare il backslash come un letterale, è necessario digitare \\\\ in quanto \ è anche un carattere di escape nelle espressioni regolari.

Raggrupamenti e back reference

Uno strumento molto utile delle regexp è la possibilità di raggruppare parti dell’espressione regolare. Per fare questo si usa la notazione (<parte dell’espressione regolare>) in pratica si racchiude il gruppo tra parentesi tonde. Ogni gruppo crea in contemporanea quello che si chiama una back reference, dove viene “catturata” una parte della regexp che si può utilizzare ad esempio per fare delle sostituzioni in una stringa. Un gruppo viene riferito tramite la notazione $, $1 rappresenta il primo gruppo, $2 il secondo e così via.  Ma vediamo qualche esempio per capire meglio la cosa.

Quì rimuoviamo gli spazi tra una parola che termina con . o , facendo il matching col primo e il terzo gruppo del pattern

String str = "rosso, verde. Giallo";
String pattern = "(\\w)(\\s+)([\\.,])";
System.out.println(str.replaceAll(pattern, "$1$3"));

Output: rosso,verde.Giallo

In questo esempio estraiamo il testo contenuto nel tag html <title> (secondo gruppo)

String str = <title>Info</title>
pattern = "(?i)(<title.*?>)(.+?)(</title>)";
String updated = str.replaceAll(pattern, "$2");

Output: Info

Positive/Negative look ahead

Positive look ahead (notazione: (?=pattern))  serve a matchare un gruppo dopo un’espressione principale senza includerlo nel risultato. Specularmente abbiamo il negative look ahead  (notazione : (?!pattern)) che definisce un gruppo dopo l’espressione principale che la stringa NON deve matchare .

Esempio Positive look ahead:

\d(?=px)

applicato alla stringa: 2px 3pt 1em 4px restituisce : 2 4

Esempio Negative look ahead:

\d(?!px)
applicato alla stringa: 2px 3pt 1em 4px restituisce : 3 1

Espressioni regolari di uso comune

linea vuota
^$

indirizzo email
[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}

data in formato gg/mm/aaaa
(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d

ora in formato 12 ore
(1[012]|[1-9]):[0-5][0-9](\\s)?(?i)(am|pm)

ora in formato 24 ore
([01]?[0-9]|2[0-3]):[0-5][0-9]

url http
http\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?

codice fiscale
[a-zA-Z]{6}\d\d[a-zA-Z]\d\d[a-zA-Z]\d\d\d[a-zA-Z]

nome utente
^[a-z0-9_-]{3,15}$

(nome utente formato da soli caratteri alfanumerici più  _ e – di lungezza min 3 e max 15)

password
((?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{8,20})

(password che deve contenere un numero, un carattere minuscolo, uno maiuscolo e un carattere speciale tra @#$% e deve avere lunghezza min 8 e max 20)

tag HTML
<([A-Z][A-Z0-9]*)\b[^>]*>(.*?)

uno specifico tag HTML
<TAG\b[^>]*>(.*?)

codice esadecimale colore
^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$

estenzione di un file immagine
([^\s]+(\.(?i)(jpg|png|gif|bmp))$)

Indirizzo IP
^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.
([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$

Referenze e link utili

Regular Expressions

Regular-Expressions.info

Regexr.com

7 thoughts on “Java: Le espressioni regolari

  1. Articolo molto esplicativo sulle espressioni regolari ma ho una domanda: se io volessi verificare solo alcune delle condizioni dell’espressione come faccio? Ad esempio per la password, l’espressione regolare che suggerisci è: ((?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{8,20}), che è soddisfatta se sono verificate tutte le condizioni richieste. Ma se mi basta che siano soddisfatte ad esempio 2 condizioni sulle 4 indicate affinché la password sia valida?
    Ho letto che il . prima della condizione sulla lunghezza indica che devono essere soddisfatte tutte le condizioni precedenti, esiste un modo per dire che basta che siano soddisfatte almeno 2?

    1. Ciao, non so se ho ben capito la tua domanda, ma provo a risponderti comunque. Un operatore del genere non esiste nelle RE, un modo per fare una cosa del genere è quello di fare una RE per ogni possibile requisito che la stringa in input deve soddisfare e infine contare i match positivi.

      G.

  2. Vorrei comporre un Pattern che mi consenta di inserire la data nel formato aa/mm o aa/mm/aaaa
    Signolarmente non è difficile ma si puo fare un or di 2 Pattern? Come?

    1. Potresti fare:
      Pattern.matches(“(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d”, “la tua stringa data”) || Pattern.matches(“(0[1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d”, “la tua stringa data”);

  3. Si vuole contare il numero di studenti maggiorenni all’interno di una scuola superiore. Illustrare le istruzioni da eseguire per il conteggio sapendo che il livello di competenza di ogni classe è superiore alla media.

    Vorrei creare un’espressione regolare con questo problema, come devo fare?? Grazie

    1. Ciao, dai pochi dettagli per poter tentare una soluzione. Le espressioni regolari servono a riconoscere pattern all’interno di stringhe, non so come potrebbero aiutarti nel problema che proponi.
      Fornisci magari maggiori dettagli, ad esempio sulla struttura su cui vuoi operare.

Rispondi a Mauro Annulla risposta

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.

Top