Dividir en comas fuera de las citas

Mi progtwig lee una línea de un archivo. Esta línea contiene texto separado por comas como:

123,test,444,"don't split, this",more test,1 

Me gustaría que el resultado de una división sea este:

 123 test 444 "don't split, this" more test 1 

Si uso String.split(",") , obtendría esto:

 123 test 444 "don't split this" more test 1 

En otras palabras: la coma en la subcadena "don't split, this" no es un separador. Como lidiar con esto?

Gracias de antemano .. Jakob

Puedes probar esta expresión regular:

 str.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); 

Esto divide la cadena , que es seguida por un número par de comillas dobles. En otras palabras, se divide en coma fuera de las comillas dobles. Esto funcionará siempre que tenga comillas equilibradas en su cadena.

Explicación:

 , // Split on comma (?= // Followed by (?: // Start a non-capture group [^"]* // 0 or more non-quote characters " // 1 quote [^"]* // 0 or more non-quote characters " // 1 quote )* // 0 or more repetition of non-capture group (multiple of 2 quotes will be even) [^"]* // Finally 0 or more non-quotes $ // Till the end (This is necessary, else every comma will satisfy the condition) ) 

Incluso puede escribir así en su código, usando el modificador (?x) con su expresión regular. El modificador ignora cualquier espacio en blanco en tu expresión regular, por lo que es más fácil leer una expresión regular dividida en varias líneas como esta:

 String[] arr = str.split("(?x) " + ", " + // Split on comma "(?= " + // Followed by " (?: " + // Start a non-capture group " [^\"]* " + // 0 or more non-quote characters " \" " + // 1 quote " [^\"]* " + // 0 or more non-quote characters " \" " + // 1 quote " )* " + // 0 or more repetition of non-capture group (multiple of 2 quotes will be even) " [^\"]* " + // Finally 0 or more non-quotes " $ " + // Till the end (This is necessary, else every comma will satisfy the condition) ") " // End look-ahead ); 

¿Por qué dividir cuando puedes unir?

Resucitando esta pregunta porque por alguna razón, la solución fácil no fue mencionada. Aquí está nuestra expresión regular bellamente compacta:

 "[^"]*"|[^,]+ 

Esto coincidirá con todos los fragmentos deseados ( ver demostración ).

Explicación

  • Con "[^"]*" , coincidimos con "double-quoted strings"
  • o |
  • emparejamos [^,]+ cualquier caracter que no sea una coma.

Un posible refinamiento es mejorar el lado de la cadena de la alternancia para permitir que las cadenas entre comillas incluyan citas escapadas.

Puedes hacer esto muy fácilmente sin una expresión regular compleja:

  1. Dividir en el personaje " . Obtienes una lista de cadenas
  2. Procese cada cadena en la lista: divida cada cadena que esté en una posición pareja en la Lista (iniciando la indexación con cero) en “,” (obtiene una lista dentro de una lista), deje cada secuencia impar posicionada sola (directamente colocándola en una lista dentro de la lista).
  3. Únase a la lista de listas, para que solo obtenga una lista.

Si desea manejar las comillas de ” ”, debe adaptar el algoritmo un poco (unir algunas partes, dividir incorrectamente o cambiar la división a expresiones regulares simples), pero la estructura básica permanece.

Entonces, básicamente, es algo como esto:

 public class SplitTest { public static void main(String[] args) { final String splitMe="123,test,444,\"don't split, this\",more test,1"; final String[] splitByQuote=splitMe.split("\""); final String[][] splitByComma=new String[splitByQuote.length][]; for(int i=0;i 

¡Esto será mucho más limpio con lambdas, prometido!

Por favor, mira el siguiente fragmento de código. Este código solo considera un flujo feliz. Cambie según su requisito

 public static String[] splitWithEscape(final String str, char split, char escapeCharacter) { final List list = new LinkedList(); char[] cArr = str.toCharArray(); boolean isEscape = false; StringBuilder sb = new StringBuilder(); for (char c : cArr) { if (isEscape && c != escapeCharacter) { sb.append(c); } else if (c != split && c != escapeCharacter) { sb.append(c); } else if (c == escapeCharacter) { if (!isEscape) { isEscape = true; if (sb.length() > 0) { list.add(sb.toString()); sb = new StringBuilder(); } } else { isEscape = false; } } else if (c == split) { list.add(sb.toString()); sb = new StringBuilder(); } } if (sb.length() > 0) { list.add(sb.toString()); } String[] strArr = new String[list.size()]; return list.toArray(strArr); } 

Basándome en la respuesta de @ zx81 , porque la idea de coincidencia es muy buena, agregué la llamada de results de Java 9, que devuelve un Stream . Como OP quería usar split , he recogido a String[] , como lo hace split .

Tenga cuidado si tiene espacios después de sus separadores de coma ( a, b, "c,d" ). Entonces necesitas cambiar el patrón.

Demostración de Jshell

 $ jshell -> String so = "123,test,444,\"don't split, this\",more test,1"; | Added variable so of type String with initial value "123,test,444,"don't split, this",more test,1" -> Pattern.compile("\"[^\"]*\"|[^,]+").matcher(so).results(); | Expression value is: java.util.stream.ReferencePipeline$Head@2038ae61 | assigned to temporary variable $68 of type java.util.stream.Stream -> $68.map(MatchResult::group).toArray(String[]::new); | Expression value is: [Ljava.lang.String;@6b09bb57 | assigned to temporary variable $69 of type String[] -> Arrays.stream($69).forEach(System.out::println); 123 test 444 "don't split, this" more test 1 

Código

 String so = "123,test,444,\"don't split, this\",more test,1"; Pattern.compile("\"[^\"]*\"|[^,]+") .matcher(so) .results() .map(MatchResult::group) .toArray(String[]::new); 

Explicación

  1. Regex [^"] coincide: una cita, cualquier cosa menos una cita, una cita.
  2. Regex [^"]* coincide con: una cita, cualquier cosa menos una cita 0 (o más) veces, una cita.
  3. Esa expresión regular debe ir primero para “ganar”, de lo contrario igualaría cualquier cosa que no fuera una coma 1 o más veces , es decir: [^,]+ – “ganaría”.
  4. results() requiere Java 9 o superior.
  5. Devuelve Stream , que mapeo usando group() call y collect para array of Strings. La toArray() Parameterless toArray() devolverá Object[] .