Dividir por comas fuera de comillas

Resuelto Jakob Mathiasen asked hace 11 años • 5 respuestas

Mi programa 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 fuera este:

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

Si uso el 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?

Jakob Mathiasen avatar Sep 19 '13 18:09 Jakob Mathiasen
Aceptado

Puedes probar esta expresión regular:

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

Esto divide la cadena ,seguida de un número par de comillas dobles. En otras palabras, se divide por 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 puedes escribir así en tu código, usando (?x)el modificador con tu expresión regular. El modificador ignora los espacios en blanco en su expresión regular, por lo que resulta 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
                         );
Rohit Jain avatar Sep 19 '2013 11:09 Rohit Jain

¿Por qué dividir cuando puedes igualar?

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

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

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

Explicación

  • Con "[^"]*", emparejamos completo"double-quoted strings"
  • o|
  • hacemos coincidir [^,]+cualquier carácter 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 comillas de escape.

zx81 avatar Jun 27 '2014 10:06 zx81

Sobre la base de la respuesta de @ zx81 , porque la idea de hacer coincidir es realmente buena, agregué resultsuna llamada a Java 9, que devuelve un archivo Stream. Como OP quería usar split, lo recopilé String[], al igual splitque lo hace.

Tenga cuidado si tiene espacios después de los 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<MatchResult>

-> $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. Coincidencias de expresiones regulares [^"]: una cita, cualquier cosa menos una cita, una cita.
  2. Coincidencias de expresiones regulares [^"]*: una cotización, cualquier cosa menos una cotización 0 (o más) veces, una cotización.
  3. Esa expresión regular debe ir primero para "ganar"; de lo contrario, coincidirá con cualquier cosa que no sea una coma 1 o más veces , es decir: [^,]+- "ganaría".
  4. results()requiere Java 9 o superior.
  5. Devuelve Stream<MatchResult>, que asigno mediante group()llamada y recopilación a una matriz de cadenas. La llamada sin parámetros toArray()regresaría Object[].
LAFK says reinstate Monica avatar Mar 05 '2018 06:03 LAFK says reinstate Monica