Analizar la entrada de CSV con un RegEx en Java

Lo sé, ahora tengo dos problemas. ¡Pero me estoy divirtiendo!

Empecé con este consejo para no intentar dividir, sino para hacer coincidir lo que es un campo aceptable, y expandir desde allí a esta expresión.

final Pattern pattern = Pattern.compile("\"([^\"]*)\"|(?<=,|^)([^,]*)(?=,|$)"); 

La expresión se ve así sin las molestas comillas escapadas:

 "([^"]*)"|(?<=,|^)([^,]*)(?=,|$) 

Esto funciona bien para mí, ya sea que coincida con “dos comillas y lo que esté entre ellos”, o “algo entre el comienzo de la línea o una coma y el final de la línea o una coma”. Al iterar a través de los partidos obtengo todos los campos, incluso si están vacíos. Por ejemplo,

 the quick, "brown, fox jumps", over, "the",,"lazy dog" 

se descompone en

 the quick "brown, fox jumps" over "the" "lazy dog" 

¡Estupendo! Ahora quiero quitar las comillas, así que agregué el lookahead y miré detrás de los grupos que no capturaban, como lo hacía con las comas.

 final Pattern pattern = Pattern.compile("(?<=\")([^\"]*)(?=\")|(?<=,|^)([^,]*)(?=,|$)"); 

de nuevo la expresión es:

 (?<=")([^"]*)(?=")|(?<=,|^)([^,]*)(?=,|$) 

En lugar del resultado deseado

 the quick brown, fox jumps over the lazy dog 

ahora entiendo este desglose

 the quick "brown fox jumps" ,over, "the" ,, "lazy dog" 

¿Qué me estoy perdiendo?

Precedencia del operador Básicamente no hay ninguno. Todo es de izquierda a derecha. Entonces el o (|) se está aplicando a la cita de cierre anticipada y a la coma anticipada

Tratar:

 (?:(?< =")([^"]*)(?="))|(?<=,|^)([^,]*)(?=,|$) 
 (?:^|,)\s*(?:(?:(?=")"([^"].*?)")|(?:(?!")(.*?)))(?=,|$) 

Esto debería hacer lo que quieras.

Explicación:

 (?:^|,)\s* 

El patrón debe comenzar con a, o al principio de la cadena. Además, ignore todos los espacios en blanco al comienzo.

Mire hacia adelante y vea si el rest comienza con una cita

 (?:(?=")"([^"].*?)") 

Si lo hace, coincida sin codicia hasta la próxima cita.

 (?:(?!")(.*?)) 

Si no comienza con una cita, entonces haga coincidir no codiciosamente hasta la próxima coma o el final de la cadena.

 (?=,|$) 

El patrón debe terminar con una coma o un final de cadena.

Cuando comencé a entender lo que había hecho mal, también comencé a entender cuán enrevesados ​​estaban los planteamientos al respecto. Finalmente me di cuenta de que no quería todo el texto coincidente, quería grupos específicos dentro de él. Terminé usando algo muy similar a mi RegEx original, excepto que no hice una búsqueda anticipada de la coma de cierre, que creo que debería ser un poco más eficiente. Aquí está mi código final.

 package regex.parser; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; public class CSVParser { /* * This Pattern will match on either quoted text or text between commas, including * whitespace, and accounting for beginning and end of line. */ private final Pattern csvPattern = Pattern.compile("\"([^\"]*)\"|(?< =,|^)([^,]*)(?:,|$)"); private ArrayList allMatches = null; private Matcher matcher = null; private String match = null; private int size; public CSVParser() { allMatches = new ArrayList(); matcher = null; match = null; } public String[] parse(String csvLine) { matcher = csvPattern.matcher(csvLine); allMatches.clear(); String match; while (matcher.find()) { match = matcher.group(1); if (match!=null) { allMatches.add(match); } else { allMatches.add(matcher.group(2)); } } size = allMatches.size(); if (size > 0) { return allMatches.toArray(new String[size]); } else { return new String[0]; } } public static void main(String[] args) { String lineinput = "the quick,\"brown, fox jumps\",over,\"the\",,\"lazy dog\""; CSVParser myCSV = new CSVParser(); System.out.println("Testing CSVParser with: \n " + lineinput); for (String s : myCSV.parse(lineinput)) { System.out.println(s); } } } 

Sé que esto no es lo que quiere el OP, pero para otros lectores, uno de los métodos de reemplazo de cadena podría usarse para quitar las comillas de cada elemento en la matriz de resultados de la expresión regular actual de OP.