tokens - split java
¿Cómo dividir una cadena separada por comas mientras se ignoran las comillas escapadas? (5)
Necesito escribir una versión extendida de la función StringUtils.commaDelimitedListToStringArray que obtiene un parámetro adicional: el escape char.
así que llamando a mi:
commaDelimitedListToStringArray("test,test//,test//,test,test", "//")
debería regresar:
["test", "test,test,test", "test"]
Mi intento actual es usar String.split () para dividir el String usando expresiones regulares:
String[] array = str.split("[^////],");
Pero la matriz devuelta es:
["tes", "test/,test/,tes", "test"]
¿Algunas ideas?
Como matt b dijo, [^//],
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 escapadas.
"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 las barras diagonales inversas ...
"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 se ve detrás codicioso)? ¿Qué pasa con las cadenas inválidas?
Busqué durante un tiempo una solución genérica, luego escribí la cosa yo mismo ... La idea es dividir siguiendo un patrón que coincida con los elementos de la lista (en lugar de hacer coincidir el delimitador).
Mi respuesta no toma el carácter de escape como un 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 relieve):
(?<=(^|,))
forward es inicio de cadena o a ,
([^//,]|//,|////)*
el elemento compuesto por /,
//
o caracteres que no son ni /
ni ,
(?=(,|$))
detrás es el final de la cadena o a ,
El patrón puede ser simplificado.
Incluso con los 3 análisis ( matches
+ find
+ replaceAll
), este método parece más rápido que el sugerido por drvdijk. Todavía se puede optimizar escribiendo 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;
}
La expresión regular
[^//],
significa "hacer coincidir un carácter que no sea una barra invertida seguida de una coma"; por eso, patrones como t,
se corresponden, porque t
es un carácter que no es una barra diagonal inversa.
Creo que necesitas usar algún tipo de lookbehind negativo , para capturar a ,
que no esté precedido por un /
sin capturar el carácter anterior, algo así como
(?<!//),
(Por cierto, tenga en cuenta que no he escapado a propósito de las barras diagonales inversas a propósito para hacer esto más legible)
No reinventar la rueda.
Para referencia futura, aquí está el método completo con el que terminé:
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://.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;
}
Tratar:
String array[] = str.split("(?<!////),");
Básicamente, esto significa dividir en una coma, excepto cuando esa coma está precedida por dos barras diagonales inversas. Esto se denomina aserción negativa detrás de la aserción de ancho cero .