Convertidor de CSV a HTML

Sep 08 2020

He escrito un pequeño programa que convierte un archivo CSV en una tabla HTML. Funciona para mis propósitos. Pero, ¿hay partes de mi código que se puedan escribir de forma más limpia? ¿Puedes mejorar quizás el rendimiento? ¿Quizás hay algún error? Busqué errores y afortunadamente no encontré ninguno.

Posdata

Tal vez debería haber proporcionado alguna información de fondo: estoy trabajando en una documentación de base de datos que estoy escribiendo como un documento HTML, porque no me gustan los documentos de Word. Sin embargo, crear una descripción tabular de las columnas con decenas de etiquetas es complicado. Es por eso que escribí este script: ahora solo tengo que exportar la información de la tabla como CSV y puedo convertirla directamente sin tener que ingresar muchas etiquetas yo mismo. Esta es la razón por la que no hay etiquetas HTML ni body: las tablas creadas no deben ser documentos HTML separados, sino partes de un único documento HTML grande.

CsvToHtmlTable.java

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.FileWriter;
import java.util.List;
import java.util.ArrayList;

public class CsvToHtmlTable {
    public static void main(String[] args) {
        // print info and show user how to call the program if needed
        System.out.println("This program is tested only for UTF-8 files.");
        if (args[0].equalsIgnoreCase("help") || args[0].equalsIgnoreCase("-help") || args.length != 2) {
            System.out.println("java CsvToHtmlTable <input file> <output file>");
            System.out.println("Example: java CsvToHtmlTable nice.csv nice.html");
            System.exit(0);
        }
        
        String csvFile = args[0];
        String outputFile = args[1];
        
        // read lines of csv to a string array list
        List<String> lines = new ArrayList<String>();
        try (BufferedReader reader = new BufferedReader(new FileReader(csvFile))) {
            String currentLine;
            while ((currentLine = reader.readLine()) != null) {
                lines.add(currentLine);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        //embrace <td> and <tr> for lines and columns
        for (int i = 0; i < lines.size(); i++) {
            lines.set(i, "<tr><td>" + lines.get(i) + "</td></tr>");
            lines.set(i, lines.get(i).replaceAll(",", "</td><td>"));
        }
        
        // embrace <table> and </table>
        lines.set(0, "<table border>" + lines.get(0));
        lines.set(lines.size() - 1, lines.get(lines.size() - 1) + "</table>"); 
        
        // output result
        try (FileWriter writer = new FileWriter(outputFile)) {
            for (String line : lines) {
                writer.write(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Cómo llamar al programa:

java CsvToHtmlTable ExampleInput.csv ExampleOutput.html

ExampleInput.csv

Name,Vorname,Alter
Ulbrecht,Klaus Dieter,12
Meier,Bertha,102

ExampleOutput.html

<table border><tr><td>Name</td><td>Vorname</td><td>Alter</td></tr>
<tr><td>Ulbrecht</td><td>Klaus Dieter</td><td>12</td></tr>
<tr><td>Meier</td><td>Bertha</td><td>102</td></tr></table>

Respuestas

4 Marc Sep 09 2020 at 03:25

Buena implementación, encuentra mis sugerencias en línea.


se puede escribir más limpio?

  • La clase java.nio.file.Filestiene un par de métodos útiles que puede usar:
lines = Files.readAllLines(Paths.get(csvFile), StandardCharsets.UTF_8);
//..
Files.write(Paths.get(outputFile), lines);
  • Considere la posibilidad de crear una constante para el carácter delimitador. Algunos archivos CSV están delimitados por ;para manejar valores que contienen comas, etc .:
public static final String DELIMITER_CHAR=",";
  • Proporcione un mensaje de usuario y salga en caso de excepción de E / S, por ejemplo:
} catch (IOException e) {
    System.out.println("Error reading input file: "+e.getMessage());
    System.exit(1);
}
  • Encapsule la lógica para convertir las líneas en un método, de modo que sea más fácil de probar y reutilizar. Por ejemplo:
public class CsvToHtmlTable{
    public static List<String> convert(List<String> lines){/**/}
    public static void main(String[] args){/**/}
}

¿Puedes mejorar quizás el rendimiento?

El límite de esta implementación es la restricción de memoria. Si el archivo de entrada es grande, es posible que todo el archivo no quepa en la memoria disponible.

Puede que no sea su caso, pero si necesita manejar archivos grandes, considere leer y escribir el archivo línea por línea.


¿Quizás hay algún error?

  • Compruebe si el archivo de entrada está vacío, de lo contrario lines.set(0,..)falla
  • Como mencionó @ Doi9t, la lógica de validación de entrada debe considerar cuando no hay argumentos, uno, dos o más.
5 AJNeufeld Sep 09 2020 at 03:02

CSVReader

Leer un archivo CSV puede ser una tarea compleja. Si bien muchos archivos CSV son solo valores separados por comas, si un valor contiene una coma, estaría rodeado de comillas dobles, y si el valor contiene comillas dobles, las comillas dobles en sí se duplican.

Para manejar estos más que solo archivos CSV básicos, realmente debería usar una biblioteca CSV, como OpenCSV (com.opencsv: opencsv: 5.0) o Apache Commons CSV (org.apache.commons: commons-csv: 1.7).

HTML

HTML válido

Su código esencialmente solo escribe <table>...table data...</table>. Este no es HTML adecuado. Faltan <html>...</html>etiquetas en todo el documento y en <body>...</body>todo el contenido. Probablemente también debería tener un <head>...</head>, quizás con un bonito <title>...</title>.

Escapando

Si sus datos CSV contienen caracteres especiales, como <, >y &, realmente debe escapar de ellos en la tabla HTML generada.

Encabezados de tabla

Parece que la primera línea de su tabla contiene encabezados, no datos. La primera fila de la tabla probablemente debería estar formateada con <th>...</th>etiquetas en lugar de <td>...</td>etiquetas.

Procesamiento línea por línea

Está leyendo el archivo CSV completo en la memoria, y solo cuando se ha cargado en su totalidad, lo vuelve a escribir como HTML. Esto requiere mucha memoria, especialmente si el archivo CSV es enorme.

En cambio, podrías:

  • abre el CSV
  • abre el archivo HTML
  • escribe el prólogo HTML
  • para cada línea leída del archivo CSV:
    • formatear y escribir una línea en un archivo HTML
  • escribir epílogo HTML

Sin probar, codificando desde la cadera, sin manejar las citas en CSV o escapar de las entidades HTML en la salida:

        try (BufferedReader reader = new BufferedReader(new FileReader(csvFile));
             FileWriter writer = new FileWriter(outputFile)) {

            writer.write("<html><body><table border>\n");

            String currentLine;
            while ((currentLine = reader.readLine()) != null) {
                writer.write("<tr>");

                for(String field: currentLine.split(","))
                    writer.write("<td>" + field + "</td>");

                writer.write("</tr>\n");
            }

            writer.write("</table></body></html>\n");

        } catch (IOException e) {
            e.printStackTrace();
        }

XML y XSLT

Es posible que desee considerar la posibilidad de crear un traductor de CSV a XML.

Su salida XML podría verse así:

<data input-file='ExampleInput.csv'>
  <person>
    <Name>Ulbrecht</Name>
    <Vorname>Klaus Dieter</Vorname>
    <Alter>12</Alter>
  </person>
  <person>
    <Name>Meier</Name>
    <Vorname>Bertha</Vorname>
    <Alter>102</Alter>
  </person>
</data>

Y luego podría usar una hoja de estilo XSLT para traducir el XML a HTML, posiblemente en un navegador sin tener que escribir el HTML en un archivo.

3 Noname Sep 10 2020 at 23:23

Posible NullPointerExceptionen líneaif (args[0].equals...

Verifique el argstamaño de entrada y nullifyingantes de proceder a acceder a cualquier índice / elemento.
Sí, eso argspodría ser null, como lo llama otra clase cargada. O vacío, si el usuario se olvida de configurar el args.

Archivo de almacenamiento en búfer

Creo que almacenar en caché todo el archivo en la memoria y luego procesarlo es una buena idea para su caso, ya que cada línea que lee, simplemente puede procesarlo, escribirlo y continuar con la siguiente línea. (como el procesamiento línea por línea mencionado por AJNeufeld)

División de columna rota (delimitador)

Básicamente, dividir las columnas que usan los datos lines.get(i).replaceAll(",",...)está roto, ya que los datos en sí tendrían ,como contenido.

Teniendo en cuenta una línea Porsche,"991,991.2,992",70donde su código (e incluso el proporcionado por AJNeufeld) fallará, ya que 991,991.2,992es un valor, y esos ""están ahí para decirle al analizador que los datos escapados están por delante.

Así que personalmente, sugiero optar por un proceso de análisis de carácter por carácter, que le permita omitir cualquiera ,como delimitador cuando llegue a una apertura "hasta su "par final .

Caracteres inesperados

También considere afirmar, omitir o convertir cualquier carácter inesperado. Por ejemplo, convertir un carácter NULL( \0) en 0x00o mostrar la advertencia y omitirla.

Olvidar archivos

Le sugiero que no bloquee su aplicación para que solo lea archivos y guarde en ellos. Muchas veces STDIN, y STDOUTson formas más bienvenidas.

Sugiero apoyar tanto desde un archivo como STDIN. Por ejemplo, use have para establecer -finuna entrada de archivo en un argumento, o -stdinpara informar la lectura de la herramienta STDIN.

Comprobación de archivos

Verificar archivos (tanto de entrada como de salida) antes de procesar el archivo también será genial. Para asegurarse de que sean accesibles.

Afirmación

Limpiar (o indicar / configurar) el resultado, mientras hay una excepción de E / S inesperada durante el proceso, también podría ser bueno.

Espero eso ayude.