Come dividere una stringa separata da virgola ignorando le virgole con escape?
Domanda
Devo scrivere una versione estesa della funzione StringUtils.commaDelimitedListToStringArray che ottiene un parametro aggiuntivo: il carattere escape.
quindi chiamando my:
commaDelimitedListToStringArray("test,test\\,test\\,test,test", "\\")
dovrebbe restituire:
["test", "test,test,test", "test"]
Il mio attuale tentativo è di usare String.split () per dividere la stringa usando espressioni regolari:
String[] array = str.split("[^\\\\],");
Ma l'array restituito è:
["tes", "test\,test\,tes", "test"]
Qualche idea?
Soluzione
L'espressione regolare
[^\\],
significa " corrisponde a un carattere che non è una barra rovesciata seguito da una virgola " - questo è il motivo per cui modelli come t,
corrispondono, poiché t
è un carattere che non è una barra rovesciata.
Penso che sia necessario utilizzare una sorta di lookbehind negativo , per catturare un < code>, che non è preceduto da un \
senza catturare il carattere precedente, qualcosa come
(?<!\\),
(A proposito, nota che non ho volutamente evitato doppiamente le barre rovesciate per renderlo più leggibile)
Altri suggerimenti
Prova:
String array[] = str.split("(?<!\\\\),");
Fondamentalmente questo sta dicendo diviso su una virgola, tranne dove quella virgola è preceduta da due barre rovesciate. Questo si chiama sguardo negativo dietro asserzione di larghezza zero .
Per riferimento futuro, ecco il metodo completo con cui sono finito:
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;
}
Come diceva matt b, [^ \\],
interpreterà il carattere che precede la virgola come parte del delimitatore.
"test\\\\\\,test\\\\,test\\,test,test"
-(split)->
["test\\\\\\,test\\\\,test\\,tes" , "test"]
Come diceva drvdijk, (? <! \\),
interpreterà erroneamente le barre rovesciate sfuggite.
"test\\\\\\,test\\\\,test\\,test,test"
-(split)->
["test\\\\\\,test\\\\,test\\,test" , "test"]
-(unescape commas)->
["test\\\\,test\\,test,test" , "test"]
Mi aspetterei di poter sfuggire anche alle barre rovesciate ...
"test\\\\\\,test\\\\,test\\,test,test"
-(split)->
["test\\\\\\,test\\\\" , "test\\,test" , "test"]
-(unescape commas and backslashes)->
["test\\,test\\" , "test,test" , "test"]
drvdijk ha suggerito (? < = (? <! \\\\) (\\\\\\\\) {0,100}),
che funziona bene per elenchi con elementi che terminano con fino a 100 barre rovesciate. Questo è abbastanza lontano ... ma perché un limite? Esiste un modo più efficiente (non è avido dietro)? Che dire stringhe non valide?
Ho cercato per un po 'una soluzione generica, poi ho scritto la cosa da solo ... L'idea è quella di dividere seguendo uno schema che corrisponda agli elementi dell'elenco (invece di abbinare il delimitatore).
La mia risposta non accetta il carattere di escape come parametro.
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;
}
Descrizione per il modello (senza caratteri di escape):
(? < = (^ |,))
forward è l'inizio della stringa o un ,
([^ \\,] | \\, | \\\\) *
l'elemento composto da \,
, \\
o caratteri che non sono né \
né ,
(? = (, | $))
dietro è la fine della stringa o un ,
Il modello può essere semplificato.
Anche con le 3 analisi ( corrisponde
+ trova
+ sostituisci tutti
), questo metodo sembra più veloce di quello suggerito da drvdijk. Può ancora essere ottimizzato scrivendo un parser specifico.
Inoltre, qual è la necessità di avere un personaggio di escape se solo un personaggio è speciale, potrebbe semplicemente essere raddoppiato ...
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;
}