EN · DE · RU · FR · ES

#1877: CSVWriter.java

projectforge-common/src/main/java/org/projectforge/common/CSVWriter.java Вспомогательный класс — пакет org.projectforge.common, projectforge-common/src/main/java/org/projectforge/common/CSVWriter.java 176 строк · 100 кода · 54 комментария · 22 пустых
Писатель CSV-вывода, который форматирует данные в текстовые файлы с разделителем-точкой с запятой. Компаньон для CSVParser — предоставляет плавный (fluent) API построителя для записи строк CSV с правильным экранированием. Поддерживает типизированные перегрузки write() для строк (с экранированием двойных кавычек), целых чисел long, дат (формат ISO 8601) и общих объектов. Настраиваемый символ-разделитель, окончание строки и формат даты. Используется для экспорта данных ProjectForge (списки адресов, экспорт табелей, финансовые отчёты).

Архитектура

Импорты

Конфигурация

НастройкаПо умолчаниюСеттер
Разделитель CSV';' (точка с запятой — европейская конвенция)setCsvSeparator(char)
Окончание строки"\n" (перевод строки Unix)setCr(String)
Формат даты"yyyy-MM-dd HH:mm:ss.SSS"setDateFormat(DateFormat)
Часовой пояс датыUTCЖёстко задан в конструкторе

Плавный (Fluent) API построителя

Все методы write() возвращают this (экземпляр CSVWriter), что позволяет выстраивать цепочки вызовов:

csvWriter.write("Имя").write("Возраст").write(42L).writeEndOfLine();

Типизированные перегрузки Write

МетодФорматированиеЭкранирование
write(String s)Исходная строка с экранированием " в "" (удвоение)Всегда обёрнуто в "..." — следует правилам экранирования RFC 4180
write(long value)Прямой числовой вывод через PrintWriter.print()Нет — числа без кавычек
write(Date value)Форматируется через dateFormat.format() в UTCОбёрнуто в кавычки — даты могут содержать пробелы или спецсимволы
write(Object value)Запасной вариант: String.valueOf(value)Нет — общие объекты без кавычек (рискованно для объектов с запятыми/кавычками)

Алгоритм экранирования (запись строк)

Метод write(String) реализует правильное экранирование CSV согласно RFC 4180:

  1. Выводит разделитель, если это не первая запись в строке (управляется writeSeparator() с флагом firstEntry)
  2. Выводит открывающую двойную кавычку (")
  3. Перебирает каждый символ строки
  4. Если символ — двойная кавычка ("), выводит её дважды ("") — стандартная escape-последовательность CSV
  5. Выводит все остальные символы как есть
  6. Выводит закрывающую двойную кавычку (")

Запись строк

Логика разделителя (writeSeparator)

Булев флаг firstEntry отслеживает, находимся ли мы в начале строки:

Это классический паттерн отслеживания состояния для вывода с разделителями.

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

CSVWriter определяет DEFAULT_CSV_SEPARATOR_CHAR = ';' как публичную константу, на которую ссылается CSVParser для обеспечения согласованного разделителя по умолчанию между читателем и писателем.

Сравнение с современными CSV-библиотеками

Эта реализация намеренно минималистична по сравнению с Apache Commons CSV или OpenCSV:

Простота отражает его происхождение как специализированного инструмента для экспорта данных ProjectForge, а не универсальной CSV-библиотеки.

Метод write(Object) НЕ экранирует вывод — он вызывает String.valueOf(value) и записывает напрямую. Если toString() объекта возвращает строку, содержащую запятые, точки с запятой или переводы строк, результирующий CSV будет повреждён. Это приемлемо, поскольку ожидается, что вызывающие будут использовать типизированные перегрузки.
Вывод дат использует часовой пояс UTC — даты нормализуются к UTC перед форматированием. Это осознанный выбор для обмена данными: он устраняет неоднозначность часовых поясов при экспорте. Формат "yyyy-MM-dd HH:mm:ss.SSS" похож на ISO 8601, но использует пробел в качестве разделителя вместо 'T'.

История Git

868d6abb7 2025 -> 2026
63081666f Заголовки исходных файлов: 2024 -> 2025.
a73905c14 Исправление опечаток в директориях projectforge*/ Найдено через codespell
b6092df09 Авторские права 2023 -> 2024
ab45d51fa Авторские права 2001-2022 -> 2001-2023.
5f7ef41b8 Авторские права 2021 -> 2022
ceb63e8a1 Заголовок исходного кода: (C) 2001-2021.
7c79f1922 Авторские права в заголовке исходника -> 2020.