Convertitore da CSV a HTML

Sep 08 2020

Ho scritto un programmino che converte un file CSV in una tabella HTML. Funziona per i miei scopi. Ma ci sono parti nel mio codice che possono essere scritte più pulite? Puoi migliorare forse le prestazioni? Ci sono forse bug? Ho cercato bug e fortunatamente non ne ho trovati.

Post scriptum

Forse avrei dovuto fornire alcune informazioni di base: sto lavorando su una documentazione di database che sto scrivendo come documento HTML, perché non mi piacciono i documenti di Word. Tuttavia, la creazione di una descrizione tabulare delle colonne con dozzine di tag è dolorosa. Ecco perché ho scritto questo script: ora devo solo esportare le informazioni della tabella come CSV e posso convertirle direttamente senza dover inserire molti tag da solo. Questo è il motivo per cui non sono presenti tag HTML e body: le tabelle create non devono essere documenti HTML separati, ma parti di un unico documento HTML di grandi dimensioni.

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();
        }
    }
}

Come chiamare il programma:

java CsvToHtmlTable ExampleInput.csv ExampleOutput.html

EsempioInput.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>

Risposte

4 Marc Sep 09 2020 at 03:25

Bella implementazione, trova i miei suggerimenti in linea.


si può scrivere più pulito?

  • La classe java.nio.file.Filesha un paio di metodi utili che puoi usare:
lines = Files.readAllLines(Paths.get(csvFile), StandardCharsets.UTF_8);
//..
Files.write(Paths.get(outputFile), lines);
  • Considera l'idea di creare una costante per il carattere delimitatore. Alcuni file CSV sono delimitati da ;per gestire i valori contenenti virgole, ecc .:
public static final String DELIMITER_CHAR=",";
  • Fornisci un messaggio utente ed esci in caso di eccezione I / O, ad esempio:
} catch (IOException e) {
    System.out.println("Error reading input file: "+e.getMessage());
    System.exit(1);
}
  • Incapsula la logica per convertire le linee in un metodo, in modo che sia più facile da testare e riutilizzare. Per esempio:
public class CsvToHtmlTable{
    public static List<String> convert(List<String> lines){/**/}
    public static void main(String[] args){/**/}
}

Puoi migliorare forse le prestazioni?

Il limite di questa implementazione è il vincolo di memoria. Se il file di input è di grandi dimensioni, l'intero file potrebbe non rientrare nella memoria disponibile.

Potrebbe non essere il tuo caso, ma se hai bisogno di gestire file di grandi dimensioni, valuta la possibilità di leggere e scrivere il file riga per riga.


Ci sono forse bug?

  • Controlla se il file di input è vuoto, altrimenti lines.set(0,..)fallisce
  • Come menzionato da @ Doi9t, la logica di convalida dell'input deve essere considerata quando non ci sono argomenti, uno, due o più.
5 AJNeufeld Sep 09 2020 at 03:02

CSVReader

La lettura di un file CSV può essere un'attività complessa. Sebbene molti file CSV siano solo valori separati da virgole, se un valore contiene una virgola, sarebbe racchiuso tra virgolette doppie e se il valore contiene virgolette doppie, le virgolette stesse vengono raddoppiate.

Per gestirli più di un semplice file CSV, dovresti davvero usare una libreria CSV, come OpenCSV (com.opencsv: opencsv: 5.0) o Apache Commons CSV (org.apache.commons: commons-csv: 1.7).

HTML

HTML valido

Il tuo codice essenzialmente scrive solo <table>...table data...</table>. Questo non è HTML corretto. Ti mancano i <html>...</html>tag in tutto il documento e <body>...</body>intorno al contenuto. Probabilmente dovresti anche avere un <head>...</head>, forse con un bel <title>...</title>.

Fuggire

Se i dati CSV contiene caratteri speciali, come <, >e &, si deve davvero li fuggire nella tabella HTML generato.

Intestazioni tabella

Sembra che la prima riga della tabella contenga intestazioni, non dati. La prima riga della tabella dovrebbe probabilmente essere formattata con <th>...</th>tag invece che con <td>...</td>tag.

Elaborazione riga per riga

Stai leggendo l'intero file CSV in memoria e solo quando è stato caricato nella sua interezza lo riscrivi come HTML. Questo richiede molta memoria, soprattutto se il file CSV è enorme!

Invece, potresti:

  • apri il CSV
  • apri il file HTML
  • scrivi il prologo HTML
  • per ogni riga letta dal file CSV:
    • formattare e scrivere la riga nel file HTML
  • scrivi epilogo HTML

Non testato, codifica dall'anca, senza gestire le citazioni in CSV o l'escape di qualsiasi entità HTML nell'output:

        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 e XSLT

Potresti prendere in considerazione la creazione di un traduttore da CSV a XML.

Il tuo output XML potrebbe essere simile a:

<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>

E poi potresti usare un foglio di stile XSLT per tradurre l'XML in HTML, possibilmente in un browser senza mai scrivere l'HTML su un file.

3 Noname Sep 10 2020 at 23:23

Possibile NullPointerExceptionin lineaif (args[0].equals...

Si prega di controllare la argsdimensione di input e nullifyingprima di procedere per accedere a qualsiasi indice / elemento.
Sì, argspotrebbe essere null, come chiamato da un'altra classe caricata. O vuoto, se l'utente dimentica di impostare il file args.

File di buffering

Penso che memorizzare nella cache l'intero file in memoria e poi elaborarlo sia una buona idea per il tuo caso, poiché ogni riga che leggi, potresti semplicemente elaborarla e scriverla e procedere con la riga successiva. (come Elaborazione riga per riga menzionata da AJNeufeld)

Divisione colonna spezzata (delimitatore)

Fondamentalmente, la divisione dei dati delle colonne utilizzando lines.get(i).replaceAll(",",...)è interrotta, poiché i dati stessi avrebbero ,come contenuto.

Considerando una riga come Porsche,"991,991.2,992",70dove il tuo codice (e anche quello fornito da AJNeufeld) fallirà, poiché 991,991.2,992è un valore, e quelli ""sono lì per dire al parser, i dati di escape sono avanti.

Quindi, personalmente, suggerisco di utilizzare un processo di analisi carattere per carattere, che ti consente di saltare qualsiasi ,delimitatore quando raggiungi un'apertura "fino alla sua "coppia finale .

Chars inaspettati

Considerando anche di affermare, saltare o convertire qualsiasi carattere imprevisto. Ad esempio, convertire un carattere NULL( \0) in 0x00o mostrare l'avviso e saltarlo.

Dimentica i file

Suggerisco di non bloccare la tua app per leggere solo dai file e salvarli. Molte volte STDINe STDOUTsono modi più graditi.

Suggerisco di supportare sia da un file che da STDIN. Ad esempio, utilizzare deve impostare -finper un file-input in argomento, o -stdinper informare lo strumento letto da STDIN.

Controllo dei file

Anche il controllo dei file (sia in entrata che in uscita) prima di elaborare il file sarà fantastico. Per assicurarti che siano accessibili.

Asserzione

Pulire (o richiedere / impostare) il risultato, anche se c'è un'eccezione IO imprevista durante il processo potrebbe essere buono.

Spero che sia d'aiuto.