CSV para conversor de HTML

Sep 08 2020

Eu escrevi um pequeno programa que converte um arquivo CSV em uma tabela HTML. Funciona para meus propósitos. Mas há partes no meu código que podem ser escritas de forma mais limpa? Você pode melhorar talvez o desempenho? Existe talvez algum bug? Procurei bugs e felizmente não encontrei.

PostScript

Talvez eu devesse ter fornecido algumas informações básicas: Estou trabalhando em uma documentação de banco de dados que estou escrevendo como um documento HTML, porque não gosto de documentos do Word. No entanto, criar uma descrição tabular das colunas com dezenas de tags é doloroso. É por isso que escrevi este script: Agora, só preciso exportar as informações da tabela como CSV e posso convertê-las diretamente, sem precisar inserir muitas tags. É por isso que não existem tags HTML e body: As tabelas criadas não devem ser documentos HTML separados, mas partes de um ú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();
        }
    }
}

Como chamar o 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>

Respostas

4 Marc Sep 09 2020 at 03:25

Boa implementação, encontre minhas sugestões inline.


pode ser escrito mais limpo?

  • A classe java.nio.file.Filestem alguns métodos úteis que você pode usar:
lines = Files.readAllLines(Paths.get(csvFile), StandardCharsets.UTF_8);
//..
Files.write(Paths.get(outputFile), lines);
  • Considere criar uma constante para o caractere delimitador. Alguns arquivos CSV são delimitados por ;para lidar com valores que contêm vírgulas, etc:
public static final String DELIMITER_CHAR=",";
  • Forneça uma mensagem ao usuário e saia em caso de Exceção de E / S, por exemplo:
} catch (IOException e) {
    System.out.println("Error reading input file: "+e.getMessage());
    System.exit(1);
}
  • Encapsule a lógica para converter as linhas em um método, para que seja mais fácil de testar e reutilizar. Por exemplo:
public class CsvToHtmlTable{
    public static List<String> convert(List<String> lines){/**/}
    public static void main(String[] args){/**/}
}

Você pode melhorar talvez o desempenho?

O limite dessa implementação é a restrição de memória. Se o arquivo de entrada for grande, o arquivo inteiro pode não caber na memória disponível.

Pode não ser o seu caso, mas se você precisar lidar com arquivos grandes, considere ler e escrever o arquivo linha por linha.


Existe talvez algum bug?

  • Verifique se o arquivo de entrada está vazio, caso contrário lines.set(0,..)falhará
  • Como @ Doi9t mencionou, a lógica de validação de entrada precisa ser considerada quando não há argumentos, um, dois ou mais.
5 AJNeufeld Sep 09 2020 at 03:02

CSVReader

Ler um arquivo CSV pode ser uma tarefa complexa. Embora muitos arquivos CSV sejam apenas valores separados por vírgula, se um valor contiver uma vírgula, ele será colocado entre aspas duplas e, se o valor contiver aspas duplas, as próprias aspas duplas.

Para lidar com isso mais do que apenas arquivos CSV básicos, você realmente deve usar uma biblioteca CSV, como OpenCSV (com.opencsv: opencsv: 5.0) ou Apache Commons CSV (org.apache.commons: commons-csv: 1.7).

HTML

HTML válido

Seu código essencialmente apenas escreve <table>...table data...</table>. Este não é um HTML adequado. Estão faltando <html>...</html>tags em todo o documento e em <body>...</body>todo o conteúdo. Você provavelmente também deve ter um <head>...</head>, talvez com um bom <title>...</title>.

Escapando

Se os seus dados CSV contém quaisquer caracteres especiais, como <, >, e &, você realmente deve escapar-los na tabela HTML gerado.

Títulos de Tabela

Parece que a primeira linha da sua tabela contém títulos, não dados. A primeira linha da tabela provavelmente deve ser formatada com <th>...</th>tags em vez de <td>...</td>tags.

Processamento linha a linha

Você está lendo todo o arquivo CSV na memória e somente quando ele é totalmente carregado é que você o grava de volta como HTML. Isso consome muita memória, especialmente se o arquivo CSV for muito grande!

Em vez disso, você poderia:

  • abra o CSV
  • abra o arquivo HTML
  • escreva o prólogo HTML
  • para cada linha lida do arquivo CSV:
    • formatar e escrever a linha no arquivo HTML
  • escrever epílogo HTML

Não testado, codificação desde o início, sem lidar com citações em CSV ou escapar de quaisquer entidades HTML na saída:

        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

Você pode querer considerar a criação de um tradutor CSV para XML.

Sua saída XML pode ser semelhante 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 então você pode usar uma folha de estilo XSLT para traduzir o XML para HTML, possivelmente em um navegador sem nunca gravar o HTML em um arquivo.

3 Noname Sep 10 2020 at 23:23

Possível NullPointerExceptionna linhaif (args[0].equals...

Verifique o argstamanho da entrada e nullifyingantes de prosseguir para acessar qualquer índice / item.
Sim, argspode ser null, conforme chamado por outra classe carregada. Ou vazio, se o usuário esquecer de definir o args.

Arquivo de buffer

Acho que armazenar todo o arquivo em cache na memória e, em seguida, processá-lo é uma boa ideia para o seu caso, pois a cada linha que você lê, você pode simplesmente processar, escrever e prosseguir para a próxima linha. (como Processamento Linha a Linha mencionado por AJNeufeld)

Divisão de coluna quebrada (delimitador)

Basicamente, a divisão dos dados das colunas usando lines.get(i).replaceAll(",",...)é quebrada, já que os próprios dados teriam ,como conteúdo.

Considerando uma linha Porsche,"991,991.2,992",70onde seu código (e mesmo aquele fornecido por AJNeufeld) irá falhar, já que 991,991.2,992é um valor, e esses ""estão lá para dizer ao analisador, os dados de escape estão à frente.

Então, pessoalmente, sugiro ir para um processo de análise caractere por caractere, que permite que você ignore qualquer ,delimitador quando chegar a uma abertura "para seu "par final .

Caracteres inesperados

Também considerando declarar, pular ou converter quaisquer caracteres inesperados. Por exemplo, converter um NULL( \0) char em 0x00ou mostrar o aviso e ignorá-lo.

Esqueça Arquivos

Eu sugiro, não bloquear seu aplicativo para apenas ler arquivos e salvar neles. Muitas vezes STDIN, e STDOUTsão formas mais bem-vindas.

Eu sugiro apoiar de um arquivo e STDIN. Por exemplo, use have to set -finpara uma entrada de arquivo no argumento ou -stdinpara informar a ferramenta de leitura STDIN.

Verificando Arquivos

Verificar os arquivos (tanto de entrada quanto de saída) antes de processá-los também será ótimo. Para se certificar de que estão acessíveis.

Afirmação

Limpar (ou solicitar / definir) o resultado, embora haja uma exceção de E / S inesperada durante o processo, também pode ser útil.

Espero que ajude.