Как разделить строку, разделенную запятыми, игнорируя экранированные запятые?

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

  •  03-07-2019
  •  | 
  •  

Вопрос

Мне нужно написать расширенную версию функции StringUtils.commaDelimitedListToStringArray, которая получает дополнительный параметр:побег символ.

так что звоню моему:

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

должен вернуться:

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



Моя текущая попытка — использовать String.split() для разделения строки с помощью регулярных выражений:

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

Но возвращаемый массив:

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

Есть идеи?

Это было полезно?

Решение

Регулярное выражение

[^\\],

означает «соответствие символу, который не является обратной косой чертой, за которой следует запятая» — вот почему такие шаблоны, как t, совпадают, потому что t это символ, который не является обратной косой чертой.

Я думаю, вам нужно использовать что-то вроде негативный взгляд назад, чтобы захватить , которому не предшествует \ без захвата предыдущего символа, что-то вроде

(?<!\\),

(Кстати, обратите внимание, что я намеренно не экранировал обратную косую черту дважды, чтобы сделать это более читабельным)

Другие советы

Пытаться:

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

По сути, это означает разделение по запятой, за исключением случаев, когда этой запятой предшествуют две обратные косые черты.Это называется утверждение отрицательного просмотра назад нулевой ширины.

Для дальнейшего использования вот полный метод, который у меня получился:

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

Как сказал Мэтт Би: [^\\], будет интерпретировать символ, предшествующий запятой, как часть разделителя.

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

Как сказал Дрвдейк: (?<!\\), будет неправильно интерпретировать экранированную обратную косую черту.

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

Я ожидаю, что смогу избежать и обратной косой черты...

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

Дрвдейк предложил (?<=(?<!\\\\)(\\\\\\\\){0,100}), что хорошо работает для списков, элементы которых заканчиваются 100 обратными косыми чертами.Это достаточно далеко...но почему предел?Есть ли более эффективный способ (не жадный ли взгляд назад)?А как насчет недопустимых строк?

Некоторое время я искал общее решение, а затем написал это сам...Идея состоит в том, чтобы разделить список по шаблону, который соответствует элементам списка (вместо разделителя).

Мой ответ не принимает escape-символ в качестве параметра.

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

Описание шаблона (неэкранированное):

(?<=(^|,)) вперед — это начало строки или ,

([^\\,]|\\,|\\\\)* элемент, состоящий из \,, \\ или персонажи, которые не являются ни тем, ни другим \ ни ,

(?=(,|$)) позади находится конец веревки или ,

Схема может быть упрощена.

Даже с 3-мя разборами(matches + find + replaceAll), этот метод кажется более быстрым, чем тот, который предложил Дрвдейк.Его еще можно оптимизировать, написав специальный парсер.

Кроме того, какая необходимость в escape-символе, если только один символ является особенным, его можно просто удвоить...

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;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top