Вопрос-ответ

Splitting on comma outside quotes

Разделение по запятой вне кавычек

Моя программа считывает строку из файла. Эта строка содержит текст, разделенный запятыми, например:

123,test,444,"don't split, this",more test,1

Я бы хотел, чтобы результат разделения был таким:

123
test
444
"don't split, this"
more test
1

Если я использую String.split(","), я бы получил это:

123
test
444
"don't split
this"

more test
1

Другими словами: запятая в подстроке "don't split, this" не является разделителем. Как с этим бороться?

Переведено автоматически
Ответ 1

Вы можете попробовать это регулярное выражение:

str.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");

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

Объяснение:

,           // Split on comma
(?= // Followed by
(?: // Start a non-capture group
[^"]* // 0 or more non-quote characters
"
// 1 quote
[^"]* // 0 or more non-quote characters
"
// 1 quote
)* // 0 or more repetition of non-capture group (multiple of 2 quotes will be even)
[^"]* // Finally 0 or more non-quotes
$ // Till the end (This is necessary, else every comma will satisfy the condition)
)

Вы даже можете ввести подобное в свой код, используя (?x) модификатор с вашим регулярным выражением. Модификатор игнорирует любые пробелы в вашем регулярном выражении, поэтому становится проще читать регулярное выражение, разбитое на несколько строк, вот так:

String[] arr = str.split("(?x)   " + 
", " + // Split on comma
"(?= " + // Followed by
" (?: " + // Start a non-capture group
" [^\"]* " + // 0 or more non-quote characters
" \" " + // 1 quote
" [^\"]* " + // 0 or more non-quote characters
" \" " + // 1 quote
" )* " + // 0 or more repetition of non-capture group (multiple of 2 quotes will be even)
" [^\"]* " + // Finally 0 or more non-quotes
" $ " + // Till the end (This is necessary, else every comma will satisfy the condition)
") " // End look-ahead
);
Ответ 2

Зачем разделять, когда можно сопоставить?

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

"[^"]*"|[^,]+

Это позволит сопоставить все нужные фрагменты (смотрите демонстрацию).

Объяснение


  • С помощью "[^"]*" мы полностью сопоставляем "double-quoted strings"

  • или |

  • мы сопоставляем [^,]+ любые символы, которые не являются запятой.

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

Ответ 3

Основываясь на ответе @zx81, поскольку идея сопоставления действительно хороша, я добавил вызов Java 9 results, который возвращает Stream. Поскольку OP хотел использовать split, я собрал to String[], как это split и делается.

Внимание, если после ваших разделителей-запятых стоят пробелы (a, b, "c,d"). Тогда вам нужно изменить шаблон.

Демонстрация Jshell

$ jshell
-> String so = "123,test,444,\"don't split, this\",more test,1";
| Added variable so of type String with initial value "123,test,444,"don't split, this",more test,1"

-> Pattern.compile("\"[^\"]*\"|[^,]+").matcher(so).results();
| Expression value is: java.util.stream.ReferencePipeline$Head@2038ae61
| assigned to temporary variable $68 of type java.util.stream.Stream<MatchResult>

-> $68.map(MatchResult::group).toArray(String[]::new);
| Expression value is: [Ljava.lang.String;@6b09bb57
| assigned to temporary variable $69 of type String[]

-> Arrays.stream($69).forEach(System.out::println);
123
test
444
"don'
t split, this"
more test
1

Код

String so = "123,test,444,\"don't split, this\",more test,1";
Pattern.compile("\"[^\"]*\"|[^,]+")
.matcher(so)
.results()
.map(MatchResult::group)
.toArray(String[]::new);

Объяснение


  1. Регулярные выражения [^"] совпадения: цитата, что угодно, кроме цитаты, цитата.

  2. Регулярное выражение [^"]* соответствует: цитате, чему угодно, но не цитате 0 (или более) раз, цитате.

  3. Это регулярное выражение должно быть первым, чтобы "выиграть", в противном случае совпадение с чем угодно, кроме запятой 1 или более раз, то есть: [^,]+, "выиграет".

  4. results() требуется Java 9 или выше.

  5. Он возвращает Stream<MatchResult>, который я сопоставляю с помощью group() вызова и собираю в массив строк. Вернет toArray() вызов без параметровObject[].

Ответ 4

Вы можете сделать это очень легко без сложного регулярного выражения:


  1. Разделение по символу ". Вы получите список строк

  2. Обработайте каждую строку в списке: разделите каждую строку, которая находится на четной позиции в списке (начиная индексацию с нуля) на "," (вы получите список внутри списка), каждую строку с нечетным расположением оставьте отдельно (непосредственно помещая ее в список внутри списка).

  3. Присоединяйтесь к списку списков, чтобы получить только список.

Если вы хотите обрабатывать кавычки '"', вам придется немного адаптировать алгоритм (соединить некоторые части, которые вы неправильно разделили, или заменить разделение на простое регулярное выражение), но базовая структура останется.

Итак, в основном это что-то вроде этого:

public class SplitTest {
public static void main(String[] args) {
final String splitMe="123,test,444,\"don't split, this\",more test,1";
final String[] splitByQuote=splitMe.split("\"");
final String[][] splitByComma=new String[splitByQuote.length][];
for(int i=0;i<splitByQuote.length;i++) {
String part=splitByQuote[i];
if (i % 2 == 0){
splitByComma[i]=part.split(",");
}else{
splitByComma[i]=new String[1];
splitByComma[i][0]=part;
}
}
for (String parts[] : splitByComma) {
for (String part : parts) {
System.out.println(part);
}
}
}
}

Обещано, что с лямбдами это будет намного чище!

java regex string