Come dividere una stringa separata da virgola ignorando le virgole con escape?

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

  •  03-07-2019
  •  | 
  •  

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?

È stato utile?

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é \ ,

(? = (, | $)) 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;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top