Comment diviser une chaîne séparée par des virgules en ignorant les virgules?

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

  •  03-07-2019
  •  | 
  •  

Question

Je dois écrire une version étendue de la fonction StringUtils.commaDelimitedListToStringArray qui obtient un paramètre supplémentaire: le caractère d'échappement.

donc appeler mon:

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

devrait renvoyer:

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



Ma tentative actuelle consiste à utiliser String.split () pour fractionner 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?

Était-ce utile?

La solution

L'expression régulière

[^\\],

signifie "faire correspondre un caractère qui n'est pas une barre oblique inversée suivie d'une virgule" - c’est la raison pour laquelle des motifs 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 , pour capturer un < code>, qui n'est pas précédé d'un \ sans capturer le caractère précédent, comme

(?<!\\),

(Au fait, notez que j’ai délibérément évité les doubles barres obliques inversées pour le rendre plus lisible)

Autres conseils

Essayez:

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

Il s’agit essentiellement de scinder sur une virgule, sauf lorsque cette virgule est précédée de deux barres obliques inverses. Cette opération s'appelle une assertion de regard négatif derrière l'assertion de largeur nulle .

Pour référence future, voici la méthode complète avec laquelle j'ai abouti:

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 Drvdijk l’a dit, (? <! \\), interprétera de manière erronée les barres obliques inverses échappées.

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

Je m'attendrais à pouvoir échapper aussi 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 éléments se terminant avec jusqu'à 100 barres obliques inverses. C'est assez loin ... mais pourquoi une limite? Existe-t-il un moyen plus efficace (ne cherche-t-il pas les gourmands)? Qu'en est-il des chaînes non valides?

J'ai longtemps cherché une solution générique, puis j'ai écrit la chose moi-même ... L'idée est de scinder 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 en tant que 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 (non échappé):

(? < = (^ |,)) est le début de la chaîne ou un ,

([^ \\,] | \\, | \\\\) * l'élément composé de \, , \\ ou des caractères qui ne sont ni \ ni ,

(? = (, | $)) est derrière la fin de la chaîne ou un ,

Le motif peut être simplifié.

Même avec les 3 analyses ( correspond + 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.

De plus, quel est le besoin d'avoir un caractère d'échappement si un seul caractère 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;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top