Como dividir uma corda separada por vírgula enquanto ignora vírgulas escapadas?

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

  •  03-07-2019
  •  | 
  •  

Pergunta

Preciso escrever uma versão estendida da função StringUtils.CommadeLimitedListTToStringArray, que obtém um parâmetro adicional: o char Escape.

Então, chamando meu:

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

Deve voltar:

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



Minha tentativa atual é usar string.split () para dividir a string usando expressões regulares:

String[] array = str.split("[^\\\\],");

Mas a matriz devolvida é:

["tes", "test\,test\,tes", "test"]

Alguma ideia?

Foi útil?

Solução

A expressão regular

[^\\],

significa "corresponder a um personagem que não é uma barra de barriga seguida por uma vírgula" - é por isso que padrões como t, são correspondentes, porque t é um personagem que não é uma barra de barriga.

Eu acho que você precisa usar algum tipo de LookBehind negativo, para capturar um , que não é precedido por um \ sem capturar o personagem anterior, algo como

(?<!\\),

(Btw, observe que eu propositalmente não escapou duplamente as barras de barriga para tornar isso mais legível)

Outras dicas

Tentar:

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

Basicamente, isso está dizendo dividido em uma vírgula, exceto onde essa vírgula é precedida por duas barras de barriga. Isso é chamado de Afirmação negativa da largura da largura.

Para referência futura, aqui está o método completo com o qual acabei com:

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;
}

Como Matt B disse, [^\\], interpretará o caráter que precede a vírgula como parte do delimitador.

"test\\\\\\,test\\\\,test\\,test,test"
  -(split)->
["test\\\\\\,test\\\\,test\\,tes" , "test"]

Como Drvdijk disse, (?<!\\), irá interpretar mal as barras de barriga escapar.

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

Eu esperaria poder escapar de barras de barriga também ...

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

drvdijk sugeriu (?<=(?<!\\\\)(\\\\\\\\){0,100}), O que funciona bem para listas com elementos terminando com até 100 barragens. Isso é suficiente ... mas por que um limite? Existe uma maneira mais eficiente (não parece gananciosa)? E as cordas inválidas?

Eu procurei um tempo para uma solução genérica, então escrevi a coisa ... A idéia é dividir -se de seguir um padrão que corresponde aos elementos da lista (em vez de combinar o delimitador).

Minha resposta não toma o personagem de fuga como um 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;
}

Descrição para o padrão (inquilibrado):

(?<=(^|,)) a frente é o início da string ou um ,

([^\\,]|\\,|\\\\)* o elemento composto de \,, \\ ou personagens que não são nenhum \ nem ,

(?=(,|$)) atrás está o final da corda ou um ,

O padrão pode ser simplificado.

Mesmo com as 3 analingas (matches + find + replaceAll), esse método parece mais rápido que o sugerido por drvdijk. Ainda pode ser otimizado escrevendo um analisador específico.

Além disso, qual é a necessidade de ter um personagem de fuga se apenas um personagem for especial, pode simplesmente ser dobrado ...

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;
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top