Как разделить запятую String, игнорируя экранированные запятые? - PullRequest
26 голосов
/ 04 мая 2009

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

так называется мой:

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

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

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



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

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

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

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

Есть идеи?

Ответы [ 4 ]

32 голосов
/ 04 мая 2009

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

[^\\],

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

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

(?<!\\),

(Кстати, обратите внимание, что я намеренно не избежал двойного слэша, чтобы сделать его более читабельным)

30 голосов
/ 04 мая 2009

Попробуйте:

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

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

6 голосов
/ 04 мая 2009

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

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 /657413/kak-razdelit-zapyatuy-string-ignoriruya-ekranirovannye-zapyatye
    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;
}
2 голосов
/ 12 марта 2013

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

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

Как сказал drvdijk, (?<!\\), будет неверно истолковывать сбежавшие обратные слеши.

"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"]

drvdijk предложил (?<=(?<!\\\\)(\\\\\\\\){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) этот метод кажется более быстрым, чем предложенный drvdijk. Его по-прежнему можно оптимизировать, написав определенный синтаксический анализатор.

Кроме того, зачем иметь 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...