EN · DE · RU · FR · ES

#1876: CSVParser.java

projectforge-common/src/main/java/org/projectforge/common/CSVParser.java Hilfsklasse — Paket org.projectforge.common, projectforge-common/src/main/java/org/projectforge/common/CSVParser.java 342 Zeilen · 248 Code · 57 Kommentare · 37 Leer
Eigener CSV-Parser (Komma-getrennte Werte) mit einer handgeschriebenen Lexer/Parser-Architektur. Liest von einem java.io.Reader und tokenisiert CSV-Daten Zeichen für Zeichen. Unterstützt quotierte Felder, eingebettete Zeilenumbrüche innerhalb quotierter Zellen, maskierte doppelte Anführungszeichen (""-Konvention), konfigurierbare Feldtrennzeichen, Spaltennamen-Mapping für Kopfzeilen und UTF-8-BOM-Erkennung. Geschrieben von Kai Reinhard und H. Spiewok (2005), älter als und ohne externe CSV-Bibliotheksabhängigkeiten.

Architektur

Importe

Parser-Architektur — Handgeschriebener Lexer

Anstatt Regex oder einen Parser-Generator zu verwenden, implementiert CSVParser einen zeichenweisen Lexer mit einem Pushback-Puffer. Dieses Design priorisiert Kontrolle über Fehlerbehandlung und Leistung für die spezifische Teilmenge der CSV-Formatierung, die von ProjectForge verwendet wird.

Kernkomponenten

Typ-Enum

enum Type { EOF, EOL, CHAR }

Token-Typen: Dateiende (End-Of-File), Zeilenende (End-Of-Line) oder Zeichendaten. Dies steuert die Zustandsmaschine des Parsers.

Zeichenstrom-Verwaltung

UTF-8-BOM-Behandlung

skipBOM() wird während der Konstruktion aufgerufen, um ein UTF-8-Byte-Order-Mark (\uFEFF) am Dateianfang zu erkennen und zu überspringen. Wenn keine BOM vorhanden ist, wird das erste Zeichen zurückgelegt (unread). Dies ermöglicht das korrekte Parsen von CSV-Dateien, die aus Microsoft Excel exportiert wurden, da diese für UTF-8-Dateien eine BOM enthalten.

Zellen-Parsing (parseCell)

Die Kern-CSV-Parsing-Logik behandelt diese Fälle:

FallVerhalten
Unquotierte ZelleZeichen werden bis zum Trennzeichen oder Zeilenende gesammelt
Quotierte Zelle ("...")Zeichen innerhalb der Anführungszeichen werden gesammelt; Anführungszeichen müssen korrekt geschlossen sein
Maskiertes Anführungszeichen ("")Zwei aufeinanderfolgende doppelte Anführungszeichen innerhalb einer quotierten Zelle repräsentieren ein einziges literales Anführungszeichen
Eingebetteter ZeilenumbruchZeilenumbrüche innerhalb quotierter Zellen werden beibehalten (mehrzeilige Zellenwerte)
Nachgestelltes LeerzeichenLeerzeichen nach dem schließenden Anführungszeichen werden übersprungen; erwartet als nächstes Trennzeichen oder Zeilenende
Nicht geschlossenes AnführungszeichenLöst eine RuntimeException mit einer beschreibenden Fehlermeldung aus, die Zeilen-/Spaltennummer enthält

Zeilen-Parsing (parseLine)

Liest Zellen bis zum Zeilenende oder Dateiende und sammelt sie in einer List<String>. Gibt bei Dateiende null zurück (keine leere Liste — Aufrufer können Dateiende von leeren Zeilen unterscheiden).

Spaltenkopf-Unterstützung (parseHeadCols / getCell)

Für CSV-Dateien mit einer Kopfzeile liest parseHeadCols() die erste Zeile und erstellt eine colMap: Map<String, Integer>, die Spaltennamen ihren Positionsindizes zuordnet. Nachfolgende Aufrufe von getCell(List<String>, spaltenname) rufen Werte nach Spaltenname statt nach Position ab. Dies ermöglicht einen Excel-ähnlichen Zugriff auf benannte Spalten.

Fehlermeldungen

Drei verschiedene Fehlerkonstanten bieten spezifische Diagnosen:

ERROR_UNEXPECTED_QUOTATIONMARK = "Unerwartetes Anführungszeichen \" (nur in quotierten Zellen erlaubt)."
ERROR_QUOTATIONMARK_MISSED_AT_END_OF_CELL = "Anführungszeichen \" am Ende der Zelle fehlt."
ERROR_DELIMITER_OR_NEW_LINE_EXPECTED_AFTER_QUOTATION_MARK = "Trennzeichen oder Zeilenumbruch nach Anführungszeichen erwartet."
ERROR_UNEXPECTED_CHARACTER_AFTER_QUOTATION_MARK = "Unerwartetes Zeichen nach Anführungszeichen."

Jede Meldung wird über createMessage() um Zeilen- und Spaltennummern ergänzt.

Integration mit CSVWriter

CSVParser verwendet CSVWriter.DEFAULT_CSV_SEPARATOR_CHAR (';' — Semikolon) als Standardtrennzeichen. Dies ist die europäische CSV-Konvention (Microsoft Excel in deutschen Gebietsschemata verwendet semikolongetrennte CSV). Das Trennzeichen ist über setCsvSeparatorChar() konfigurierbar.

Design-Einschränkungen

Diese benutzerdefinierte Implementierung wurde 2005 geschrieben, lange bevor Apache Commons CSV (veröffentlicht 2014) oder OpenCSV weit verbreitet waren. Zu dieser Zeit hatte das JDK keine integrierte CSV-Unterstützung. Der Code wurde mit inkrementellen Verbesserungen gewartet: BOM-Behandlung (Commit 2024), Unterstützung für mehrzeilige quotierte Felder und Tippfehlerkorrekturen mittels codespell.

Git-Verlauf

868d6abb7 2025 -> 2026
161d71602 WIP: CSVParser: BOM-Zeichen.
dfb2378df WIP: CSVParser: Mehrzeiler usw.
63081666f Quellcode-Dateiköpfe: 2024 -> 2025.
a73905c14 Korrigiere Tippfehler in Verzeichnissen projectforge*/ Gefunden mittels codespell
a72903e36 *.java, *.kt: StringBuffer -> StringBuilder.
b6092df09 Copyright 2023 -> 2024
ab45d51fa Copyright 2001-2022 -> 2001-2023.