View Full Version : [JAVA - regex]
DeltaDirac
10-07-2011, 16:02
Ciao a tutti i Java-Guru
Sto cercando di controllare il formato di due file di testo con la libreria java.util.regex, al momento senza grosse soddisfazioni :(
Il primo file contiene una serie di dati nel seguente formato:
Amico1 Amico2 distanza12 Amico3 distanza13 Amico4 distanza14
Amico3 Amico5 distanza35
Amico5 Amico6 distanza56 Amico7 distanza57
esempio:
Anton.Luca Bianca 150 Claudio 14 Ciro 280
Claudio Francesco 45
Francesco Giovanni 100 Luca 50 Piero 200
Non ho molta confidenza con le espressioni regolari ed ho qualche difficoltà a concatenare i controlli sui set di caratteri di input.
Ho tentato di costruire una classe ( controlloPattern ) che riuscisse a verificare se il file di input aderisce allo standard oppure o no, ma l'unica cosa che son riuscito ad ottenere è una "verifica per parti" ovvero son riuscito a leggere o solo i nomi o solo le distanze ma non OK se la verifica è positiva:
public class controlloPattern {
String regex = "[A-Za-z.]+[a-zA-Z0-9]{2,3}";
String input = "Anton.Luca Bianca 150 Claudio 14 Ciro 280 Claudio Francesco 45
Francesco Giovanni 100 Luca 50 Piero 200";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
while (matcher.find())
System.out.println(matcher.group());
}
Ottengo in uscita:
Anton.Luca
Bianca
Claudio
Ciro
Claudio
Francesco
Francesco
Giovanni
Luca
Piero
L'espressione regolare voleva catturare anche nomi dati nel formato Nome.Nome, ecco perché ho inserito il .
Con una regex simile riesco ad ottenere le distanze a cui si trovano gli amici:
String regex = "[0-9]{2,3}";
IL PROBLEMA E' CHE NON RIESCO A VERIFICARE se il file di input rispetta complessivamente il formato dato :muro:
Idee?
Il secondo file è simile ma contiene anche un altro dato (affinità) introdotto subito dopo la distanza e separato dal carattere pipe (|). Me ne occuperò dopo..
wingman87
11-07-2011, 09:10
Ho provato a scrivere una regex ma non so se funziona:
(([A-Za-z\.]+\s){2}\d+(\s[A-Za-z\.]+\s\d+)*[\n\r]*)+
Te la spiego a pezzi;
([A-Za-z\.]+\s){2}\d+
Questo serve a matchare i primi due nomi più il numero: [A-Za-z\.] matcha le lettere più il punto, con il + ripeti il match, con \s matchi lo spazio e con {2} ripeti il match precedente (quello tra parentesi tonde), con \d matchi i digit, cioè i numeri;
(\s[A-Za-z\.]+\s\d+)*
Questo matcha le coppie nome-numero successive, con * il match può avvenire 0 o più volte;
[\n\r]*
Per matchare il fine riga (non sapendo se è presente solo \n o anche \r ho usato la classe, con * e non + perché non so se il file termina con un ritorno a capo);
(tutto)+
Matcha una o più righe, puoi sostituire il + con un * se il file può anche essere vuoto.
Non avendola testata non sono sicuro che sia corretta, comunque credo che ti serva una cosa del genere.
DeltaDirac
11-07-2011, 18:46
Così come è scritta non riesco a farla funzionare per sintassi gobba (illegal escape caracter, 6 volte) ; duplicando tutti i caratteri \ come segue:
"(([A-Za-z\\.]+\\s){2}\\d+(\\s[A-Za-z\\.]+\\s\\d+)*[\n\r]*)+"
ma non riesco a fare la validazione.
wingman87
11-07-2011, 19:40
In tal caso credo che tu debba raddoppiare anche i backslash di n ed r. Comunque non ho capito se ha funzionato
DeltaDirac
12-07-2011, 06:46
Ad es.
String regex = "(([A-Za-z\\.]+\\s){2}\\d+(\\s[A-Za-z\\.]+\\s\\d+)*[\\n\\r]*)+";
String input = "Anton.Luca Bianca 150 Claudio 14 Ciro 280 Claudio Francesco 45 Francesco Giovanni 100 Luca 50 Piero 200 2 2 2";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
while (matcher.find())
System.out.println(matcher.group());
Dovrebbe fallire perché mancano sostanzialmente tutti i caratteri ( : ) che delimitano i due campi e c'è una ripetizione di numeri alla fine della linea.
Una stringa di input valida sarebbe invece "Franco Luca 10:12 Giovanni 6:3 Matteo 19:199"
Questa stringa contiene anche il secondo problema di cui accennavo all'inizio: vorrei validare ogni linea del file solo se aderisce a quel formato (nome1 nome2 numero1:numero2 nome3 numero4:numero5 ... ).
Invece non riesco proprio ad uscirne :muro
wingman87
12-07-2011, 08:27
Scusa ma mi stai un po' confondendo... in quest'ultimo post hai usato una sintassi diversa da quella del primo post. Cosa devi validare esattamente?
banryu79
12-07-2011, 12:40
Volevo proporre una soluzione non basata sull'uso delle regex.
Definisco una classe astratta (TextValidator) che rappresenta l'algoritmo generico di validazione di una sorgente di testo; in particolare astrae la semantica di validazione e la semantica di scomposizione in token di una data linea nella sorgente:
package stringsplitting.textvalidator;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.StringTokenizer;
/**
* Abstract class to perform validation of a text source.
*/
public abstract class TextValidator
{
/**
* Line separator in text file/stream
*/
public static final String NEWLINE = System.getProperty("line.separator");
/**
* Main algorithm to validate a given text source
* @param source a stream of text
* @return true if the text source is valid
*/
public boolean isValid(InputStream source) {
String txt = getText(source);
if (txt == null) return false;
int num = 1;
for (String line : getAllLines(txt)) {
String[] tokens = getTokens(line, num);
if (! isValid(tokens, num)) return false;
num++;
}
return true;
}
/* utility method to extract all text from the source */
private String getText(InputStream source) {
Scanner scan = null;
try {
scan = new Scanner(source);
return scan.useDelimiter("\\z").next();
} finally {
if (scan != null) scan.close();
}
}
/* utility method to extract all lines from the text */
private String[] getAllLines(String txt) {
StringTokenizer st = new StringTokenizer(txt, NEWLINE);
List<String> lines = new ArrayList<String>(st.countTokens());
while (st.hasMoreTokens()) lines.add(st.nextToken());
final int N = lines.size();
return lines.toArray(new String[N]);
}
/**
* Implementation-specific semantic for validating a given line
* @param tokens the tokens taht form the given line
* @param number the number of the line in the text
* @return true if the given line is valid
*/
abstract protected boolean isValid(String[] tokens, int number);
/**
* Implementation-specific semantic for tokenizing a given line
* @param line the line to tokenize
* @param number the number of the line in the text
* @return the given line decomposed into an array of tokens
*/
abstract protected String[] getTokens(String line, int number);
}
Quindi, a seconda del formato, definisco un'implementazione apposita.
Nell'esempio qui sotto, due implemantazioni di TextValidator per due formati che ho scelto traendoli dalle descrizioni dell'utente.
Tra il primo e il secondo formato (quello con i versetti presenti) c'è della logica condivisa il che mi permette di definire il secondo "validatore" come estensione del primo (sovvrascrivendo le operazioni differenti, e aggiungendo quelle mancanti):
package stringsplitting.textvalidator;
import java.io.InputStream;
/**
* Check the format of a given text file.<br>
*/
public class ValidatorExample
{
public static void main(String[] args) {
InputStream in = ValidatorExample.class.getResourceAsStream("input2.txt");
MyTextValidator2 check = new MyTextValidator2();
if (check.isValid(in))
System.out.println("File is valid.");
else
System.out.println("File is malformed.");
}
/**
* Custom implementation of a TextValidator
* format is:<br>
* nameOrDot [name value]
* <p>
* nameOrDot -> letters.letters | letters<br>
* name -> letters<br>
* value -> integer<br>
*/
static class MyTextValidator1 extends TextValidator
{
@Override
protected boolean isValid(String[] tokens, int number) {
String tok = tokens[0];
if (! isNameOrDot(tok)) return false;
if (tokens.length > 1) {
if (tokens.length % 2 == 0) return false;
for (int i=1; i<tokens.length-1; i+=2) {
String s1 = tokens[i];
if (! isName(s1)) return false;
String s2 = tokens[i+1];
if (! isValue(s2)) return false;
}
}
return true;
}
@Override
protected String[] getTokens(String line, int number) {
return line.split(" ");
}
// @impl
protected boolean isName(String str) {
for (char c : str.toCharArray())
if (! Character.isLetter(c)) return false;
return true;
}
// @impl
protected boolean isNameOrDot(String str) {
for (char c : str.toCharArray())
if ((! Character.isLetter(c)) && c != '.') return false;
return true;
}
// @impl
protected boolean isValue(String str) {
try {
Integer.parseInt(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}
/**
* Custom implementation of another TextValidator
* format is:<br>
* nameOrDot [name verse]
* <p>
* nameOrDot -> letters.letters | letters<br>
* name -> letters<br>
* verse -> integer:integer
* <p>
* Reuse functionality already implemented in MyTextValidator1
*/
static class MyTextValidator2 extends MyTextValidator1
{
@Override
protected boolean isValid(String[] tokens, int number) {
if (tokens.length < 3) return false;
if (tokens.length % 2 == 0) return false;
String tok = tokens[0];
if (! isNameOrDot(tok)) return false;
for (int i=1; i<tokens.length-1; i+=2) {
String s1 = tokens[i];
if (! isName(s1)) return false;
String s2 = tokens[i+1];
if (! isVerse(s2)) return false;
}
return true;
}
// @impl
private boolean isVerse(String str) {
int index = str.indexOf(':');
if (index == -1) return false;
String s1 = str.substring(0, index);
if (! isValue(s1)) return false;
String s2 = str.substring(index+1);
if (! isValue(s2)) return false;
return true;
}
}
}
Il primo validatore l'ho provato con questo input:
Anton.Luca Bianca 150 Claudio 14 Ciro 280
Claudio Francesco 45
Francesco Giovanni 100 Luca 50 Piero 200
e il secondo con questo:
Franco Luca 10:12 Giovanni 6:3
Carlo Luca 10:12
Luisa Giovanni 6:3 Matteo 19:199
Antonio.Rossi Matteo 19:199
Mattia Luca 10:12 Giovanni 6:3 Matteo 19:199
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.