#java #csv #parsing #apache-commons-csv
#java #csv #синтаксический анализ #apache-commons-csv
Вопрос:
Я пытаюсь проанализировать почти действительный файл CSV, содержащий данные, которые на 99,9% верны и действительны. Однако на полпути есть пара недопустимых записей (слишком много кавычек), например
a,b,"c",d
a,b,""c""",d
Мой код
try (Reader reader = new BufferedReader(new FileReader(file), BUFFERED_READER_SIZE);
CSVParser csvParser = new CSVParser(reader, CSVFormat.EXCEL)
) {
Iterator<CSVRecord> iterator = csvParser.iterator();
CSVRecord record;
while (iterator.hasNext()) {
try {
record = iterator.next();
} catch (IllegalStateException e) {
}
}
} catch (IOException e) {
}
Как мне проанализировать CSV, чтобы при обнаружении недопустимой строки / записи он просто пропускал ее и переходил к следующей строке?
Ответ №1:
Я не думаю, что вы можете многое сделать, чтобы обойти это. CSVParser
является конечным классом и не позволяет контролировать способ, которым он анализирует базовый поток. Однако это можно обойти, используя пользовательский итератор, который справится с этой задачей.
public final class Csv {
private Csv() {
}
public interface ICsvParserFactory {
@Nonnull
CSVParser createCsvParser(@Nonnull Reader lineReader);
}
public static Stream<CSVRecord> tryParseLinesLeniently(final BufferedReader bufferedReader, final ICsvParserFactory csvParserFactory) {
return bufferedReader.lines()
.map(line -> {
try {
return csvParserFactory.createCsvParser(new StringReader(line))
.iterator()
.next();
} catch ( final IllegalStateException ex ) {
return null;
}
})
.filter(Objects::nonNull)
.onClose(() -> {
try {
bufferedReader.close();
} catch ( final IOException ex ) {
throw new RuntimeException(ex);
}
});
}
}
Однако я не думаю, что это хорошая идея в любом случае:
- Он не может вернуть
CSVParser
экземпляр. - Он может возвращать
Iterator<CSVRecord>
вместоStream<CSVRecord>
(и сохранятьfilter
операцию), но я просто нахожу потоки более простыми в реализации. - Он создает новый анализатор CSV для каждой строки, поэтому этот метод создает много объектов для документа CSV, который содержит «слишком много» строк. Средство чтения строк, вероятно, можно использовать повторно.
- Вся идея метода заключается в том, что он, не являясь анализатором CSV, предполагает, что каждая строка содержит только одну строку (я действительно не помню, разрешают ли CSV / TSV многострочные записи), поэтому он нарушает правила синтаксического анализа CSV просто по замыслу. Он пока не поддерживает заголовки (но может быть легко улучшен).
final Csv.ICsvParserFactory csvParserFactory = lineReader -> {
try {
return new CSVParser(lineReader, CSVFormat.EXCEL);
} catch ( final IOException ex ) {
throw new RuntimeException(ex);
}
};
try ( final Stream<CSVRecord> csvRecords = Csv.tryParseLinesLeniently(new BufferedReader(reader), csvParserFactory) ) {
csvRecords.forEachOrdered(System.out::println);
}
Если возможно, пожалуйста, позвольте вашему анализатору CSV использовать действительные документы CSV, не используя никаких обходных путей, подобных этому.
Редактировать 1
В приведенном выше коде есть ошибка реализации: ВСЕ записи, возвращаемые из потока, теперь имеют recordNumber
значение 1
.
Теперь я действительно считаю, что запрос не может быть исправлен с помощью синтаксического анализатора Apache Commons CSV, поскольку единственный CSVRecord
конструктор также является закрытым для пакета и не может быть создан вне этого пакета, если не использовать отражение или не вторгаться в его объявляющий пакет.
Извините, вам нужно либо исправить свои документы CSV, либо использовать другой анализатор, который может анализировать «более мягко».
Комментарии:
1. CSV (большинство диалектов) допускает поля, содержащие новую строку, если поле заключено в кавычки. Но соблюдение этого сделало бы невозможным выполнение запроса «игнорировать строки со слишком большим количеством кавычек» (поскольку вы не можете точно определить, где находится конец строки), поэтому необходимо предположить, что в этом файле нет многострочных строк.
2. @rici Правильно, фрагментация ввода построчно нарушает грамматику CSV и не учитывает номер записи, который я только что нашел в
CSVRecord
классе.3. Вместо того, чтобы передавать ввод по строке за раз, вы можете попытаться вставить кавычку в конце строк с нечетным числом кавычек. Вставка цитаты может быть немного медленной, но она должна быть нечастой.
Ответ №2:
Я использую Apache CSV commons версии 1.9.0, и я могу продолжить извлечение строк после недопустимых строк, просто «поглощая» исключение и просто продолжая. Имейте в виду, что hasNext()
метод фактически предварительно извлекает следующую строку, поэтому он может выдавать IllegalStateException
как next()
метод, так и .
Если вы обработаете исключение, следующим CSVRecord
извлеченным будет искаженная версия недопустимой строки, поэтому вы захотите ее пропустить. Я не могу опубликовать свой код, поскольку это IP-адрес моего работодателя, но, надеюсь, это поможет.