EN · DE · RU · FR · ES

#1876: CSVParser.java

projectforge-common/src/main/java/org/projectforge/common/CSVParser.java Вспомогательный класс — пакет org.projectforge.common, projectforge-common/src/main/java/org/projectforge/common/CSVParser.java 342 строки · 248 кода · 57 комментариев · 37 пустых
Пользовательский парсер CSV (значения, разделённые запятыми) с архитектурой лексера/парсера, написанной вручную. Читает данные из java.io.Reader и токенизирует CSV-данные посимвольно, поддерживая поля в кавычках, встроенные символы новой строки внутри ячеек в кавычках, экранированные двойные кавычки (соглашение ""), настраиваемые разделители полей, сопоставление имён столбцов заголовка и обнаружение UTF-8 BOM. Написан Каем Райнхардом и Х. Шпивоком (2005), этот код предшествует внешним библиотекам CSV и избегает их использования.

Архитектура

Импорты

Архитектура парсера — лексер, написанный вручную

Вместо использования регулярных выражений или генератора парсеров, CSVParser реализует посимвольный лексер с буфером возврата. Такая конструкция отдаёт приоритет контролю над обработкой ошибок и производительности для конкретного подмножества форматирования CSV, используемого в ProjectForge.

Основные компоненты

Перечисление Type

enum Type { EOF, EOL, CHAR }

Типы токенов: конец файла, конец строки или символьные данные. Это управляет конечным автоматом парсера.

Управление символьным потоком

Обработка UTF-8 BOM

skipBOM() вызывается во время конструирования для обнаружения и пропуска метки порядка байтов UTF-8 (\uFEFF) в начале файла. Если BOM отсутствует, первый символ помещается обратно (unread). Это обеспечивает корректный разбор CSV-файлов, экспортированных из Microsoft Excel, который добавляет BOM для файлов UTF-8.

Разбор ячеек (parseCell)

Основная логика разбора CSV обрабатывает следующие случаи:

СлучайПоведение
Ячейка без кавычекСимволы накапливаются до разделителя или конца строки
Ячейка в кавычках ("...")Символы внутри кавычек накапливаются; кавычки должны быть правильно закрыты
Экранированная кавычка ("")Две последовательные двойные кавычки внутри ячейки в кавычках представляют один литеральный символ кавычки
Встроенный символ новой строкиСимволы новой строки внутри ячеек в кавычках сохраняются (многострочные значения ячеек)
Замыкающие пробелыПробелы после закрывающей кавычки пропускаются; ожидается разделитель или конец строки
Незакрытая кавычкаВыбрасывает RuntimeException с информативным сообщением об ошибке, включающим номер строки/столбца

Разбор строк (parseLine)

Читает ячейки до конца строки или конца файла, собирая их в List<String>. Возвращает null в конце файла (не пустой список — вызывающие могут отличить конец файла от пустых строк).

Поддержка столбцов заголовка (parseHeadCols / getCell)

Для CSV-файлов со строкой заголовка parseHeadCols() читает первую строку и строит colMap: Map<String, Integer>, сопоставляя имена столбцов с их позиционными индексами. Последующие вызовы getCell(List<String>, colname) получают значения по имени столбца, а не по позиции. Это обеспечивает доступ к именованным столбцам, как в Excel.

Сообщения об ошибках

Три различных константы ошибок обеспечивают конкретную диагностику:

ERROR_UNEXPECTED_QUOTATIONMARK = "Неожиданный символ кавычки \" (допускается только в ячейках в кавычках)."
ERROR_QUOTATIONMARK_MISSED_AT_END_OF_CELL = "Пропущена кавычка \" в конце ячейки."
ERROR_DELIMITER_OR_NEW_LINE_EXPECTED_AFTER_QUOTATION_MARK = "После кавычки ожидается разделитель или новая строка."
ERROR_UNEXPECTED_CHARACTER_AFTER_QUOTATION_MARK = "Неожиданный символ после кавычки."

Каждое сообщение дополняется номером строки и столбца через createMessage().

Интеграция с CSVWriter

CSVParser использует CSVWriter.DEFAULT_CSV_SEPARATOR_CHAR (';' — точка с запятой) в качестве разделителя по умолчанию. Это европейское соглашение для CSV (Microsoft Excel в немецкой локали использует CSV с разделителем-точкой с запятой). Разделитель можно настроить через setCsvSeparatorChar().

Ограничения дизайна

Эта пользовательская реализация была написана в 2005 году, задолго до того, как Apache Commons CSV (выпущен в 2014) или OpenCSV стали широко доступны. В то время в JDK не было встроенной поддержки CSV. Код поддерживался с постепенными улучшениями: обработка BOM (коммит 2024 года), поддержка многострочных полей в кавычках и исправление опечаток с помощью codespell.

История Git

868d6abb7 2025 -> 2026
161d71602 WIP: CSVParser: символы BOM.
dfb2378df WIP: CSVParser: многострочные поля и т.д.
63081666f Заголовки исходных файлов: 2024 -> 2025.
a73905c14 Исправление опечаток в каталогах projectforge*/ Найдено с помощью codespell
a72903e36 *.java, *.kt: StringBuffer -> StringBuilder.
b6092df09 Авторские права 2023 -> 2024
ab45d51fa Авторские права 2001-2022 -> 2001-2023.