J'ai besoin d'écrire une version étendue de la fonction StringUtils.commaDelimitedListToStringArray qui obtient un paramètre supplémentaire: le caractère d'échappement.
appelant ainsi mon:
commaDelimitedListToStringArray("test,test\\,test\\,test,test", "\\")
devrait retourner:
["test", "test,test,test", "test"]
Ma tentative actuelle consiste à utiliser String.split () pour diviser la chaîne à l'aide d'expressions régulières:
String[] array = str.split("[^\\\\],");
Mais le tableau retourné est:
["tes", "test\,test\,tes", "test"]
Des idées?
L'expression régulière
[^\\],
signifie "faire correspondre un caractère qui n'est pas une barre oblique inverse suivie d'une virgule" - c'est pourquoi des modèles tels que t,
correspondent, car t
est un caractère qui n'est pas une barre oblique inverse.
Je pense que vous devez utiliser une sorte de lookbehind négatif , pour capturer un ,
qui n'est pas précédé d'un \
sans capturer le caractère précédent, quelque chose comme
(?<!\\),
(BTW, notez que je n'ai pas délibérément échappé doublement aux barres obliques inverses pour le rendre plus lisible)
Essayer:
String array[] = str.split("(?<!\\\\),");
Fondamentalement, cela signifie fractionner sur une virgule, sauf lorsque cette virgule est précédée de deux barres obliques inverses. C'est ce qu'on appelle un lookbehind négatif assertion de largeur nulle .
Pour référence future, voici la méthode complète avec laquelle je me suis retrouvé:
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;
}
Comme Matt B l'a dit, [^\\],
interprétera le caractère précédant la virgule comme faisant partie du délimiteur.
"test\\\\\\,test\\\\,test\\,test,test"
-(split)->
["test\\\\\\,test\\\\,test\\,tes" , "test"]
Comme l'a dit drvdijk, (?<!\\),
interprètera mal les barres obliques inversées.
"test\\\\\\,test\\\\,test\\,test,test"
-(split)->
["test\\\\\\,test\\\\,test\\,test" , "test"]
-(unescape commas)->
["test\\\\,test\\,test,test" , "test"]
Je m'attendrais à pouvoir également échapper aux barres obliques inverses ...
"test\\\\\\,test\\\\,test\\,test,test"
-(split)->
["test\\\\\\,test\\\\" , "test\\,test" , "test"]
-(unescape commas and backslashes)->
["test\\,test\\" , "test,test" , "test"]
drvdijk a suggéré (?<=(?<!\\\\)(\\\\\\\\){0,100}),
qui fonctionne bien pour les listes avec des éléments se terminant par jusqu'à 100 barres obliques inverses. C'est assez loin ... mais pourquoi une limite? Existe-t-il un moyen plus efficace (ne regarde pas derrière gourmand)? Qu'en est-il des chaînes invalides?
J'ai cherché pendant un certain temps une solution générique, puis j'ai écrit la chose moi-même ... L'idée est de diviser en suivant un modèle qui correspond aux éléments de la liste (au lieu de faire correspondre le délimiteur).
Ma réponse ne prend pas le caractère d'échappement comme paramètre.
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;
}
Description du motif (sans échappement):
(?<=(^|,))
forward est le début d'une chaîne ou un ,
([^\\,]|\\,|\\\\)*
l'élément composé de \,
, \\
ou des caractères qui ne sont ni \
ni ,
(?=(,|$))
derrière est la fin d'une chaîne ou un ,
Le modèle peut être simplifié.
Même avec les 3 analyses (matches
+ find
+ replaceAll
), cette méthode semble plus rapide que celle suggérée par drvdijk. Il peut toujours être optimisé en écrivant un analyseur spécifique.
Aussi, quel est le besoin d'avoir un personnage d'échappement si un seul personnage est spécial, il pourrait simplement être doublé ...
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;
}