Как разделить разделенную запятыми строку при игнорировании пропущенных запятых?
Мне нужно написать расширенную версию функции StringUtils.commaDelimitedListToStringArray, которая получает дополнительный параметр: escape char.
поэтому вызываем my:
commaDelimitedListToStringArray("test,test\\,test\\,test,test", "\\")
должен вернуться:
["test", "test,test,test", "test"]
Моя текущая попытка состоит в том, чтобы использовать String.split() для разделения строки с использованием регулярных выражений:
String[] array = str.split("[^\\\\],");
Но возвращаемый массив:
["tes", "test\,test\,tes", "test"]
Любые идеи?
Ответы
Ответ 1
Регулярное выражение
[^\\],
означает "соответствие символу, который не является обратным слэшем, за которым следует запятая" - вот почему шаблоны, такие как t,
, сопоставляются, потому что t
- это символ, который не является обратным слэшем.
Я думаю, вам нужно использовать какой-то negative lookbehind, чтобы захватить ,
, которому не предшествует \
без захват предыдущего символа, что-то вроде
(?<!\\),
(Кстати, обратите внимание, что я целенаправленно не избежал двойных обратных косых черт, чтобы сделать это более читаемым)
Ответ 2
Try:
String array[] = str.split("(?<!\\\\),");
В основном это говорит о разложении по запятой, за исключением случаев, когда этой запятой предшествуют две обратные косые черты. Это называется отрицательным поиском нулевой ширины.
Ответ 3
Не изобретайте колесо.
Ответ 4
В будущем, вот полный метод, с которым я закончил:
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;
}
Ответ 5
Как говорит матовый 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;
}
Описание для шаблона (неэкранированный):
(?<=(^|,))
forward - начало строки или ,
([^\\,]|\\,|\\\\)*
элемент, состоящий из \,
, \\
или символов, которые не являются ни \
, ни ,
(?=(,|$))
Позади конец строки или ,
Шаблон может быть упрощен.
Даже при 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;
}