Разделение на запятую вне цитат

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

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" не является разделителем. Как справиться с этим?

Спасибо заранее. Jakob

Ответы

Ответ 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

Почему Split, если вы можете сопоставить?

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

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

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

Объяснение

  • С "[^"]*" мы сопоставим полный "double-quoted strings"
  • или |
  • мы сопоставляем [^,]+ любые символы, которые не являются запятой.

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

Ответ 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);
            }
        }
    }
}

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

Ответ 4

См. ниже фрагмент кода. Этот код учитывает только счастливый поток. Измените в соответствии с вашим требованием

public static String[] splitWithEscape(final String str, char split,
        char escapeCharacter) {
    final List<String> list = new LinkedList<String>();

    char[] cArr = str.toCharArray();

    boolean isEscape = false;
    StringBuilder sb = new StringBuilder();

    for (char c : cArr) {
        if (isEscape && c != escapeCharacter) {
            sb.append(c);
        } else if (c != split && c != escapeCharacter) {
            sb.append(c);
        } else if (c == escapeCharacter) {
            if (!isEscape) {
                isEscape = true;
                if (sb.length() > 0) {
                    list.add(sb.toString());
                    sb = new StringBuilder();
                }
            } else {
                isEscape = false;
            }

        } else if (c == split) {
            list.add(sb.toString());
            sb = new StringBuilder();
        }
    }

    if (sb.length() > 0) {
        list.add(sb.toString());
    }

    String[] strArr = new String[list.size()];

    return list.toArray(strArr);
}