Парсер Java CSV с разделителем строк (многосимвольный)

Существует ли какая-либо библиотека Java с открытым исходным кодом, которая поддерживает многосимвольные (т. е. строки длиной > 1) разделители (разделители) для CSV?

По определению, CSV = данные, разделенные запятыми, с одним символом (',') в качестве разделителя. Однако существует множество других односимвольных альтернатив (например, вкладка), в результате чего CSV обозначает данные «Значения, разделенные символами» (по сути, DSV: данные, разделенные разделителями).

Основные библиотеки Java с открытым исходным кодом для CSV (например, OpenCSV) поддерживают практически любой символ в качестве разделителя, но не строку ( многосимвольные) разделители. Итак, для данных, разделенных строками типа "|||" нет другого варианта, кроме предварительной обработки ввода, чтобы преобразовать строку в односимвольный разделитель. С этого момента данные можно анализировать как значения, разделенные одним символом.

Поэтому было бы неплохо, если бы существовала библиотека, изначально поддерживающая разделители строк, чтобы не было необходимости в предварительной обработке. Это будет означать, что CSV теперь означает данные «CharSequence-Separated Values». :-)


person PNS    schedule 28.12.2011    source источник
comment
Вы можете написать свою собственную библиотеку. В этом нет ничего сложного. Прочитайте каждую строку из файла и разделите ее регулярным выражением или разделителями.   -  person juergen d    schedule 28.12.2011
comment
Не все так просто, потому что CSV может содержать поля в кавычках, многострочные записи и т. д. Кроме того, существует бесчисленное множество вариантов кавычек, escape-символов и т. д. Взгляните на secretgeek.net/csv_trouble.asp для забавного обзора проблем, с которыми вы можете столкнуться.   -  person PNS    schedule 28.12.2011
comment
вы проверяли FlatPack? Я спрашиваю, потому что согласно моим прошлым исследованиям эта библиотека имеет гораздо более богатый API, чем OpenCSV.   -  person gnat    schedule 28.12.2011
comment
Если вам нужно разрешить разделителю быть частью данных, либо заключив в кавычки 007,"My name is Bond, James Bond", либо экранировав 007,My name is Bond\, James Bond, тогда это становится намного сложнее (большинство в первом случае). Однако Asker не уточняет, нужно ли это.   -  person SJuan76    schedule 28.12.2011
comment
Это действительно было бы необходимо, поэтому (среди многих других причин) зрелая библиотека предпочтительнее, но все те, с которыми я играл, похоже, поддерживают односимвольные разделители.   -  person PNS    schedule 28.12.2011
comment
@gnat FlatPack, похоже, также поддерживает только односимвольные разделители.   -  person PNS    schedule 28.12.2011
comment
Понимаю. Тогда вы рассматривали двухэтапный подход? Я имею в виду 1) замените свою многосимвольную последовательность одним символом по вашему выбору, затем 2) передайте результат в openCSV или любую другую библиотеку   -  person gnat    schedule 28.12.2011
comment
@gnat Как я сказал в вопросе, Итак, для данных, разделенных строками, такими как ||| нет другого варианта, кроме предварительной обработки ввода для преобразования строки в односимвольный разделитель. :-)   -  person PNS    schedule 28.12.2011


Ответы (3)


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

Вот несколько предлагаемых обходных путей (примеры в Groovy можно преобразовать в java).

Игнорировать неявные промежуточные поля

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

    CSVParser csv = new CSVParser((char)'|')

    String[] result = csv.parseLine('J||Project report||"F, G, I"||1')

    assert result[0] == "J"
    assert result[2] == "Project report"
    assert result[4] == "F, G, I"
    assert result[6] == "1"

or

    CSVParser csv = new CSVParser((char)'|')

    String[] result = csv.parseLine('J|||Project report|||"F, G, I"|||1')

    assert result[0] == "J"
    assert result[3] == "Project report"
    assert result[6] == "F, G, I"
    assert result[9] == "1"

Сверните свой собственный

Используйте метод tokenizer Java String.

    def result = 'J|||Project report|||"F, G, I"|||1'.tokenize('|||')

    assert result[0] == "J"
    assert result[1] == "Project report"
    assert result[2] == "\"F, G, I\""
    assert result[3] == "1"

Недостатком этого подхода является то, что вы теряете возможность игнорировать символы кавычек или экранировать разделители.

Обновлять

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

  1. Используйте «свернуть свой собственный», чтобы сначала проверить данные. Разделите каждую строку и докажите, что она содержит необходимое количество полей.
  2. Используйте подход «игнорирование поля» для анализа проверенных данных, уверенный в том, что указано правильное количество полей.

Не очень эффективно, но, возможно, проще, чем писать собственный парсер CSV :-)

person Mark O'Connor    schedule 31.12.2011
comment
Марк, подход с игнорированием поля умный, но он не будет работать для строк, состоящих более чем из 1 разных символов. Я также подумал об использовании первого (или последнего) символа разделителя строк в качестве разделителя, а затем удалить оставшуюся часть разделителя, которая будет отображаться в начале каждого поля. Тем не менее, это не сработает, если этот символ является обычным, то есть он встречается в большем количестве мест, чем количество разделителей. Прокатить собственный вариант не так просто, как кажется на первый взгляд. Проверьте secretgeek.net/csv_trouble.asp по некоторым веским причинам. - person PNS; 02.01.2012
comment
Я понимаю ограничения обоих решений. Как уже говорилось, подход с игнорированием поля действительно хорош только для анализа данных с хорошим поведением. Как вы заметили, если кто-то использует неправильное количество разделительных символов, это нарушает ваши предположения о данных. Прокрутка вашего собственного варианта - это действительно доказательство того, что это можно сделать, я бы никогда не стал беспокоиться, если только данные не будут невероятно хорошо себя вести. По моему опыту данные CSV редко... - person Mark O'Connor; 02.01.2012
comment
Ты прав. Мой опыт также подтверждает, что данные CSV часто имеют неправильный формат. +1 - person PNS; 05.01.2012
comment
FWIW, вот мои 0,02 евро: создайте средство чтения с предварительной обработкой, которое преобразует любую последовательность строк в символ, и передайте это средство чтения в openCSV. - person Luis Muñiz; 05.12.2012
comment
Apache Commons CSV, похоже, тоже не имеет этой функции. Согласно withRecordSeparator, синтаксический анализ в настоящее время работает только для входных данных с '\n', '\r' и \r\n. - person Mark Teese; 05.11.2018

Ни одно из этих решений не сработало для меня, потому что все они предполагали, что вы можете хранить весь CSV-файл в памяти, что позволяет выполнять простые действия типа replaceAll.

Я знаю, что это медленно, но я выбрал Scanner. Он имеет удивительное количество функций и позволяет создавать собственную простую программу чтения CSV с любой строкой, которую вы хотите использовать в качестве разделителя записи. Он также позволяет анализировать очень большие файлы CSV (раньше я делал отдельные файлы размером 10 ГБ), поскольку вы можете читать записи по одной за раз.

Scanner s = new Scanner(inputStream, "UTF-8").useDelimiter(">|\n");

Я бы предпочел более быстрое решение, но ни одна библиотека, которую я нашел, не поддерживает его. FasterXML имеет открытый билет на добавление этой функциональности с начала 2017 года: https://github.com/FasterXML/jackson-dataformats-text/issues/14

person Peter    schedule 12.10.2018

Попробуйте opencsv.

Он делает все, что вам нужно, включая (и особенно) обработку встроенных разделителей внутри значений в кавычках (например, "a,b", "c" анализирует как ["a,b", "c"])

Я успешно использовал его, и он мне понравился.

Отредактировано:

Поскольку opencsv обрабатывает только односимвольные разделители, вы можете обойти это следующим образом:

String input;
char someCharNotInInput = '|';
String delimiter = "abc"; // or whatever
input.replaceAll(delimiter, someCharNotInInput);
new CSVReader(input, someCharNotInInput); // etc
// Put it back into each value read
value.replaceAll(someCharNotInInput, delimiter); // in case it's inside delimiters
person Bohemian♦    schedule 28.12.2011
comment
OpenCSV — отличная библиотека, но она поддерживает только односимвольные разделители, а не многосимвольные. - person PNS; 28.12.2011
comment
Проблема заключается не в обработке любых форм односимвольных разделителей (включая встроенные), а в обработке многосимвольных разделителей. :-) - person PNS; 28.12.2011
comment
Да, это этап предварительной обработки, о котором я говорил в вопросе, спасибо. - person PNS; 28.12.2011
comment
Но такая замена не будет делать различий между разделителями внутри или вне кавычек. - person Bart Kiers; 28.12.2011
comment
Это не будет, но восстановление исходного значения исправляет это. В общем, предварительная обработка выполнима, но не оптимальна, поэтому я и разместил вопрос в первую очередь. - person PNS; 28.12.2011