6
\$\begingroup\$

I have written a little program that converts a CSV-File to an HTML-Table. It works for my purposes. But are there parts in my code that can be written more clean? Can you improve maybe the performance? Are there maybe any bugs? I searched for bugs and fortunately I did not find some.

Postscript

Maybe I should have provided some background information: I am working on a database documentation that I am writing as an HTML document, because I dont like Word-documents. However, creating a tabular description of the columns with dozens of tags is painful. That is why I wrote this script: Now I only have to export the table information as CSV and can convert it directly without having to enter many tags myself. This is why there are no HTML and body tags: The tables created should not be separate HTML documents, but parts of a single, large HTML document.

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

How to call the 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>

\$\endgroup\$
  • 2
    \$\begingroup\$ You can have an exception when there are no arguments (ArrayIndexOutOfBoundsException), since you validate the position on an empty array; you can add another validation in case the array is empty. \$\endgroup\$ – Doi9t Sep 8 at 15:46
4
\$\begingroup\$

Nice implementation, find my suggestions inline.


can be written more clean?

  • The class java.nio.file.Files has a couple of handy methods that you can use:
lines = Files.readAllLines(Paths.get(csvFile), StandardCharsets.UTF_8);
//..
Files.write(Paths.get(outputFile), lines);
  • Consider to create a constant for the delimiter character. Some CSV files are delimited by ; to handle values containing commas, etc.:
public static final String DELIMITER_CHAR=",";
  • Provide a user message and exit in case of I/O Exception, for example:
} catch (IOException e) {
    System.out.println("Error reading input file: "+e.getMessage());
    System.exit(1);
}
  • Encapsulate the logic to convert the lines into a method, so that it's easier to test and reuse. For example:
public class CsvToHtmlTable{
    public static List<String> convert(List<String> lines){/**/}
    public static void main(String[] args){/**/}
}

Can you improve maybe the performance?

The limit of this implementation is the memory constraint. If the input file is large, the whole file might not fit in the available memory.

Might not be your case, but if you need to handle large files, consider reading and writing the file line by line.


Are there maybe any bugs?

  • Check if the input file is empty, otherwise lines.set(0,..) fails
  • As @Doi9t mentioned, the input validation logic needs to consider when there are no arguments, one, two or more.
| improve this answer | |
\$\endgroup\$
5
\$\begingroup\$

CSVReader

Reading a CSV file can be a complex task. While many CSV files are just comma-separated-values, if a value contains a comma, it would be surrounded by double quotes, and if the value contains double quotes, the double quotes themselves are doubled.

To handle these more that just a basic CSV files, you really should use a CSV library, such as OpenCSV (com.opencsv:opencsv:5.0) or Apache Commons CSV (org.apache.commons:commons-csv:1.7).

HTML

Valid HTML

Your code essentially just writes <table>...table data...</table>. This isn't proper HTML. You're missing <html>...</html> tags around the entire document, and <body>...</body> around the content. You should probably also have a <head>...</head>, perhaps with a nice <title>...</title>.

Escaping

If your CSV data contains any special characters, like <, >, and &, you really must escape them in the generated HTML table.

Table Headings

It looks like the first line of your table contains headings, not data. The first table row should probably be formatted with <th>...</th> tags instead of <td>...</td> tags.

Line by Line processing

You are reading the entire CSV file into memory, and only when it has been loaded in its entirety are you writing it back out as HTML. This is very memory intensive, especially if the CSV file is huge!

Instead, you could:

  • open the CSV
  • open the HTML file
  • write the HTML prolog
  • for each line read from CSV file:
    • format and write line to HTML file
  • write HTML epilog

Untested, coding from the hip, without handling quoting in CSV or escaping any HTML entities in 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 & XSLT

You may want to consider creating a CSV to XML translator.

Your XML output might look like:

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

And then you could use an XSLT Stylesheet to translate the XML to HTML, possibly in a browser without ever writing the HTML to a file.

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ @DexterThorn As suggested by AJNeufeld better rely on a csv library especially because of problem of quotes inside quotes that is quite common with excel files converted to csv format. \$\endgroup\$ – dariosicily Sep 9 at 15:13
3
\$\begingroup\$

Possible NullPointerException at line if (args[0].equals...

Please check the input args size and nullifying before proceed to access any index/item.
Yes, that args could be null, as called by another loaded class. Or empty, if the user forgets to set the args.

Buffering File

I think caching the whole file in memory, and then processing it is a good idea for your case, as each line you read, you could simply process, and write it, and proceed for next line. (as Line By Line Processing mentioned by AJNeufeld)

Broken Column(delimiter) Splitting

Basically, splitting the columns data using lines.get(i).replaceAll(",",...) is broken, since the data itself would have , as content.

Considering a line as Porsche,"991,991.2,992",70 where your code (and even the one provided by AJNeufeld) will fail, since 991,991.2,992 is one value, and those "" are there to tell the parser, escaped data are ahead.

So personally, I suggest go for a char-by-char parsing process, that allow you skip any , as delimiter when you reach an opening " til its ending " pair.

Unexpected Chars

Also considering to either assert, skip, or convert any unexpected characters. For example converting a NULL (\0) char into 0x00 or show the warning and skip it.

Forget Files

I suggest, to not lock your app to just read from files, and save to them. Many times STDIN, and STDOUT are more welcome ways.

I suggest to support from both a file and STDIN. For example, use have to set -fin for a file-input in argument, or -stdin to inform tool read from STDIN.

Checking Files

Checking files(both in/out) before processing the file will be great too. To make sure they are accessible.

Assertion

Cleaning up(or prompt/setable) the result, while there is an unexpected IO exception during process could be good too.

Hope it helps.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.