Konwerter CSV na HTML

Sep 08 2020

Napisałem mały program, który konwertuje plik CSV na tabelę HTML. Działa na moje potrzeby. Ale czy są części w moim kodzie, które można napisać bardziej przejrzystym? Czy możesz poprawić wydajność? Czy są jakieś błędy? Szukałem błędów i na szczęście nie znalazłem.

Postscriptum

Może powinienem był podać kilka podstawowych informacji: pracuję nad dokumentacją bazy danych, którą piszę jako dokument HTML, ponieważ nie lubię dokumentów Worda. Jednak tworzenie tabelarycznego opisu kolumn z dziesiątkami tagów jest bolesne. Dlatego napisałem ten skrypt: Teraz muszę tylko wyeksportować informacje z tabeli jako CSV i mogę je bezpośrednio przekonwertować bez konieczności samodzielnego wprowadzania wielu tagów. Dlatego nie ma znaczników HTML i body: utworzone tabele nie powinny być oddzielnymi dokumentami HTML, ale częściami pojedynczego, dużego dokumentu HTML.

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

Jak wywołać program:

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>

Odpowiedzi

4 Marc Sep 09 2020 at 03:25

Niezłe wdrożenie, moje sugestie są w tekście.


można napisać bardziej przejrzyście?

  • Klasa java.nio.file.Filesma kilka przydatnych metod, których możesz użyć:
lines = Files.readAllLines(Paths.get(csvFile), StandardCharsets.UTF_8);
//..
Files.write(Paths.get(outputFile), lines);
  • Rozważ utworzenie stałej dla znaku ogranicznika. Niektóre pliki CSV są rozdzielane znakami, ;aby obsługiwać wartości zawierające przecinki itp .:
public static final String DELIMITER_CHAR=",";
  • Podaj komunikat użytkownika i zakończ w przypadku wyjątku we / wy, na przykład:
} catch (IOException e) {
    System.out.println("Error reading input file: "+e.getMessage());
    System.exit(1);
}
  • Hermetyzuj logikę, aby przekonwertować linie na metodę, aby łatwiej było testować i ponownie używać. Na przykład:
public class CsvToHtmlTable{
    public static List<String> convert(List<String> lines){/**/}
    public static void main(String[] args){/**/}
}

Czy możesz poprawić wydajność?

Ograniczeniem tej implementacji jest ograniczenie pamięci. Jeśli plik wejściowy jest duży, cały plik może nie zmieścić się w dostępnej pamięci.

Może nie w Twoim przypadku, ale jeśli potrzebujesz obsługiwać duże pliki, rozważ odczyt i zapis pliku wiersz po wierszu.


Czy są jakieś błędy?

  • Sprawdź, czy plik wejściowy jest pusty, w przeciwnym razie lines.set(0,..)nie powiedzie się
  • Jak wspomniał @ Doi9t, logika walidacji danych wejściowych musi zostać rozważona, gdy nie ma żadnych argumentów, jeden, dwa lub więcej.
5 AJNeufeld Sep 09 2020 at 03:02

CSVReader

Czytanie pliku CSV może być złożonym zadaniem. Chociaż wiele plików CSV to tylko wartości rozdzielane przecinkami, jeśli wartość zawiera przecinek, zostanie ujęta w podwójne cudzysłowy, a jeśli wartość zawiera cudzysłowy, same cudzysłowy są podwajane.

Aby poradzić sobie z tym czymś więcej niż zwykłymi plikami CSV, naprawdę powinieneś użyć biblioteki CSV, takiej jak OpenCSV (com.opencsv: opencsv: 5.0) lub Apache Commons CSV (org.apache.commons: commons-csv: 1.7).

HTML

Prawidłowy HTML

Twój kod po prostu pisze <table>...table data...</table>. To nie jest poprawny HTML. Brakuje <html>...</html>tagów w całym dokumencie i <body>...</body>wokół treści. Prawdopodobnie powinieneś również mieć <head>...</head>, być może z ładnym <title>...</title>.

Ucieczka

Jeśli dane CSV zawiera żadnych znaków specjalnych, takich jak <, >i &, naprawdę musi uciec je w wygenerowanym tabeli HTML.

Nagłówki tabeli

Wygląda na to, że pierwsza linia tabeli zawiera nagłówki, a nie dane. Pierwszy wiersz tabeli powinien być prawdopodobnie sformatowany za pomocą <th>...</th>tagów zamiast <td>...</td>tagów.

Przetwarzanie linia po linii

Wczytujesz cały plik CSV do pamięci i dopiero po załadowaniu go w całości zapisujesz go z powrotem jako HTML. Zajmuje to bardzo dużo pamięci, zwłaszcza jeśli plik CSV jest ogromny!

Zamiast tego możesz:

  • otwórz plik CSV
  • otwórz plik HTML
  • napisz prolog HTML
  • dla każdego wiersza odczytanego z pliku CSV:
    • sformatuj i zapisz wiersz do pliku HTML
  • napisz epilog HTML

Nietestowane, kodowanie z biodra, bez obsługi cytowania w CSV lub ucieczki jakichkolwiek encji HTML w wyniku:

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

Możesz rozważyć utworzenie translatora CSV na XML.

Twoje wyjście XML może wyglądać następująco:

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

Następnie możesz użyć arkusza stylów XSLT, aby przetłumaczyć XML na HTML, prawdopodobnie w przeglądarce bez zapisywania kodu HTML do pliku.

3 Noname Sep 10 2020 at 23:23

Możliwe NullPointerExceptionna liniiif (args[0].equals...

Sprawdź argsrozmiar wejściowy i nullifyingzanim przejdziesz do jakiegokolwiek indeksu / pozycji.
Tak, to argsmoże być nullwywołane przez inną załadowaną klasę. Lub pusty, jeśli użytkownik zapomni ustawić args.

Buforowanie pliku

Myślę, że buforowanie całego pliku w pamięci, a następnie przetwarzanie go jest dobrym pomysłem dla twojego przypadku, ponieważ każda przeczytana linia może po prostu przetworzyć, zapisać i przejść do następnej linii. (zgodnie z przetwarzaniem Line By Line wspomnianym przez AJNeufeld)

Podział łamanej kolumny (separator)

Zasadniczo dzielenie kolumn za pomocą danych lines.get(i).replaceAll(",",...)jest zepsute, ponieważ same dane miałyby ,zawartość.

Biorąc pod uwagę wiersz jako miejsce, w Porsche,"991,991.2,992",70którym twój kod (a nawet ten dostarczony przez AJNeufeld) zawiedzie, ponieważ 991,991.2,992ma jedną wartość, a te ""są po to, aby powiedzieć parserowi, dane ucieczki są przed nami.

Osobiście radzę więc zastosować proces parsowania znak po znaku, który pozwala pominąć dowolny ,separator, gdy dojdziesz do otwarcia "aż do "pary końcowej .

Nieoczekiwane znaki

Rozważ także potwierdzenie, pominięcie lub przekonwertowanie nieoczekiwanych znaków. Na przykład konwersja znaku NULL( \0) na 0x00lub wyświetlenie ostrzeżenia i pominięcie go.

Zapomnij o plikach

Sugeruję, aby nie blokować aplikacji, aby po prostu czytać z plików i zapisywać w nich. Wiele razy STDINi STDOUTsą bardziej pożądane sposoby.

Sugeruję wsparcie zarówno z pliku, jak i STDIN. Na przykład, użyj muszą ustawić -finplik wejściowy w argumencie lub -stdinpoinformować narzędzie, z którego czyta STDIN.

Sprawdzanie plików

Sprawdzanie plików (zarówno do wewnątrz, jak i na zewnątrz) przed przetworzeniem pliku również będzie świetne. Aby upewnić się, że są dostępne.

Twierdzenie

Oczyszczanie (lub monitowanie / ustawianie) wyniku, podczas gdy występuje nieoczekiwany wyjątek we / wy podczas procesu, również może być dobre.

Mam nadzieję, że to pomoże.