¿Cómo dividir una cadena separada por comas mientras se ignoran las comas escapadas?

StackOverflow https://stackoverflow.com/questions/820172

  •  03-07-2019
  •  | 
  •  

Pregunta

Necesito escribir una versión extendida de la función StringUtils.commaDelimitedListToStringArray que obtiene un parámetro adicional: el carácter de escape.

así que llama a mi:

commaDelimitedListToStringArray("test,test\\,test\\,test,test", "\\")

debería devolver:

["test", "test,test,test", "test"]



Mi intento actual es usar String.split () para dividir la cadena usando expresiones regulares:

String[] array = str.split("[^\\\\],");

Pero la matriz devuelta es:

["tes", "test\,test\,tes", "test"]

¿Alguna idea?

¿Fue útil?

Solución

La expresión regular

[^\\],

significa " coincide con un carácter que no es una barra invertida seguida de una coma " - esta es la razón por la que los patrones como t, se corresponden, porque t es un carácter que no es una barra invertida.

Creo que necesitas usar algún tipo de lookbehind negativo , para capturar un < código>, que no está precedido por un \ sin capturar el carácter anterior, algo como

(?<!\\),

(Por cierto, tenga en cuenta que a propósito no he escapado doblemente las barras invertidas para hacer esto más legible)

Otros consejos

Prueba:

String array[] = str.split("(?<!\\\\),");

Básicamente, esto significa división en una coma, excepto cuando esa coma va precedida por dos barras invertidas. Esto se denomina aspecto negativo detrás de la afirmación de ancho cero .

Para referencias futuras, aquí está el método completo que terminé con:

public static String[] commaDelimitedListToStringArray(String str, String escapeChar) {
    // these characters need to be escaped in a regular expression
    String regularExpressionSpecialChars = "/.*+?|()[]{}\\";

    String escapedEscapeChar = escapeChar;

    // if the escape char for our comma separated list needs to be escaped 
    // for the regular expression, escape it using the \ char
    if(regularExpressionSpecialChars.indexOf(escapeChar) != -1) 
        escapedEscapeChar = "\\" + escapeChar;

    // see http://stackoverflow.com/questions/820172/how-to-split-a-comma-separated-string-while-ignoring-escaped-commas
    String[] temp = str.split("(?<!" + escapedEscapeChar + "),", -1);

    // remove the escapeChar for the end result
    String[] result = new String[temp.length];
    for(int i=0; i<temp.length; i++) {
        result[i] = temp[i].replaceAll(escapedEscapeChar + ",", ",");
    }

    return result;
}

Como dijo matt b, [^ \\], interpretará el carácter que precede a la coma como parte del delimitador.

"test\\\\\\,test\\\\,test\\,test,test"
  -(split)->
["test\\\\\\,test\\\\,test\\,tes" , "test"]

Como dijo drvdijk, (? <! \\), malinterpretará las barras invertidas que se escaparon.

"test\\\\\\,test\\\\,test\\,test,test"
  -(split)->
["test\\\\\\,test\\\\,test\\,test" , "test"]
  -(unescape commas)->
["test\\\\,test\\,test,test" , "test"]

Esperaría poder escapar también de barras invertidas ...

"test\\\\\\,test\\\\,test\\,test,test"
  -(split)->
["test\\\\\\,test\\\\" , "test\\,test" , "test"]
  -(unescape commas and backslashes)->
["test\\,test\\" , "test,test" , "test"]

drvdijk sugirió (? < = (? <! \\\\) (\\\\\\\\) {0,100}), que funciona bien para listas con elementos que terminan Con hasta 100 barras invertidas. Esto es suficiente ... pero ¿por qué un límite? ¿Hay una manera más eficiente (no es mirar codicioso)? ¿Qué pasa con las cadenas no válidas?

Busqué por un tiempo una solución genérica, luego escribí lo mismo ... La idea es dividir siguiendo un patrón que coincida con los elementos de la lista (en lugar de hacerlo con el delimitador).

Mi respuesta no toma el carácter de escape como parámetro.

public static List<String> commaDelimitedListStringToStringList(String list) {
    // Check the validity of the list
    // ex: "te\\st" is not valid, backslash should be escaped
    if (!list.matches("^(([^\\\\,]|\\\\,|\\\\\\\\)*(,|$))+")) {
        // Could also raise an exception
        return null;
    }
    // Matcher for the list elements
    Matcher matcher = Pattern
            .compile("(?<=(^|,))([^\\\\,]|\\\\,|\\\\\\\\)*(?=(,|$))")
            .matcher(list);
    ArrayList<String> result = new ArrayList<String>();
    while (matcher.find()) {
        // Unescape the list element
        result.add(matcher.group().replaceAll("\\\\([\\\\,])", "$1"));
    }
    return result;
}

Descripción del patrón (sin escape):

(? < = (^ |,)) forward es el comienzo de la cadena o un ,

([^ \\,] | \\, | \\\\) * el elemento compuesto por \, , \\ o los caracteres que no son \ ni ,

(? = (, | $)) behind es el final de la cadena o un ,

El patrón puede estar simplificado.

Incluso con los 3 análisis ( coincidencias + find + replaceAll ), este método parece más rápido que el sugerido por drvdijk. Todavía se puede optimizar al escribir un analizador específico.

Además, ¿cuál es la necesidad de tener un personaje de escape si solo un personaje es especial, simplemente podría duplicarse ...

public static List<String> commaDelimitedListStringToStringList2(String list) {
    if (!list.matches("^(([^,]|,,)*(,|$))+")) {
        return null;
    }
    Matcher matcher = Pattern.compile("(?<=(^|,))([^,]|,,)*(?=(,|$))")
                    .matcher(list);
    ArrayList<String> result = new ArrayList<String>();
    while (matcher.find()) {
        result.add(matcher.group().replaceAll(",,", ","));
    }
    return result;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top