EN · DE · RU · FR · ES

#1876: CSVParser.java

projectforge-common/src/main/java/org/projectforge/common/CSVParser.java Clase de utilidad — paquete org.projectforge.common, projectforge-common/src/main/java/org/projectforge/common/CSVParser.java 342 líneas · 248 código · 57 comentarios · 37 en blanco
Analizador CSV (valores separados por comas) personalizado con una arquitectura de analizador léxico/parser escrita a mano. Lee desde un java.io.Reader y tokeniza datos CSV carácter por carácter, admitiendo campos entrecomillados, saltos de línea incrustados dentro de celdas entrecomilladas, comillas dobles escapadas (convención ""), separadores de campo configurables, mapeo de nombres de columnas de cabecera y detección de BOM UTF-8. Escrito por Kai Reinhard y H. Spiewok (2005), es anterior y evita dependencias externas de bibliotecas CSV.

Arquitectura

Importaciones

Arquitectura del analizador — Analizador léxico escrito a mano

En lugar de usar expresiones regulares o un generador de analizadores, CSVParser implementa un analizador léxico carácter por carácter con un búfer de retroceso. Este diseño prioriza el control sobre el manejo de errores y el rendimiento para el subconjunto específico de formato CSV utilizado por ProjectForge.

Componentes principales

Enumeración de tipos

enum Type { EOF, EOL, CHAR }

Tipos de token: Fin de archivo, Fin de línea o datos de carácter. Esto impulsa la máquina de estados del analizador.

Gestión del flujo de caracteres

Manejo de BOM UTF-8

skipBOM() se llama durante la construcción para detectar y saltar una Marca de orden de bytes UTF-8 (\uFEFF) al inicio del archivo. Si no hay BOM, el primer carácter se empuja hacia atrás (unread). Esto permite el análisis correcto de archivos CSV exportados desde Microsoft Excel, que incluye un BOM para archivos UTF-8.

Análisis de celdas (parseCell)

La lógica central de análisis CSV maneja estos casos:

CasoComportamiento
Celda sin comillasLos caracteres se acumulan hasta el separador o EOL
Celda entrecomillada ("...")Los caracteres dentro de las comillas se acumulan; las comillas deben cerrarse correctamente
Comilla escapada ("")Dos comillas dobles consecutivas dentro de una celda entrecomillada representan un carácter de comilla literal
Salto de línea incrustadoLos saltos de línea dentro de celdas entrecomilladas se conservan (valores de celda multilínea)
Espacio en blanco finalEl espacio en blanco después de la comilla de cierre se omite; espera un separador o EOL a continuación
Comilla no terminadaLanza una RuntimeException con un mensaje de error descriptivo que incluye el número de línea/columna

Análisis de líneas (parseLine)

Lee celdas hasta EOL o EOF, recogiéndolas en una List<String>. Devuelve null en EOF (no una lista vacía — los llamadores pueden distinguir el fin de archivo de las líneas vacías).

Soporte de columnas de cabecera (parseHeadCols / getCell)

Para archivos CSV con una fila de cabecera, parseHeadCols() lee la primera línea y construye un colMap: Map<String, Integer> que asigna nombres de columna a su índice posicional. Las llamadas posteriores a getCell(List<String>, nombrecolumna) recuperan valores por nombre de columna en lugar de por posición. Esto permite el acceso a columnas con nombre similar a Excel.

Mensajes de error

Tres constantes de error distintas proporcionan diagnósticos específicos:

ERROR_UNEXPECTED_QUOTATIONMARK = "Comilla inesperada \" (solo permitida en celdas entrecomilladas)."
ERROR_QUOTATIONMARK_MISSED_AT_END_OF_CELL = "Comilla \" faltante al final de la celda."
ERROR_DELIMITER_OR_NEW_LINE_EXPECTED_AFTER_QUOTATION_MARK = "Se esperaba un delimitador o nueva línea después de la comilla."
ERROR_UNEXPECTED_CHARACTER_AFTER_QUOTATION_MARK = "Carácter inesperado después de la comilla."

Cada mensaje se aumenta con números de línea y columna a través de createMessage().

Integración con CSVWriter

CSVParser utiliza CSVWriter.DEFAULT_CSV_SEPARATOR_CHAR (';' — punto y coma) como su separador predeterminado. Esta es la convención CSV europea (Microsoft Excel en configuraciones regionales alemanas utiliza CSV delimitado por punto y coma). El separador es configurable a través de setCsvSeparatorChar().

Limitaciones de diseño

Esta implementación personalizada fue escrita en 2005, mucho antes de que Apache Commons CSV (lanzado en 2014) u OpenCSV estuvieran ampliamente disponibles. En ese momento, el JDK no tenía soporte CSV incorporado. El código se ha mantenido con mejoras incrementales: manejo de BOM (commit de 2024), soporte de campos entrecomillados multilínea y correcciones tipográficas mediante codespell.

Historial de Git

868d6abb7 2025 -> 2026
161d71602 WIP: CSVParser: caracteres BOM.
dfb2378df WIP: CSVParser: multilíneas etc.
63081666f Encabezados de archivos fuente: 2024 -> 2025.
a73905c14 Corregir errores tipográficos en directorios projectforge*/ Encontrados mediante codespell
a72903e36 *.java, *.kt: StringBuffer -> StringBuilder.
b6092df09 Copyright 2023 -> 2024
ab45d51fa Copyright 2001-2022 -> 2001-2023.