Don't use plain HTML tables with an application/vnd.ms-excel
content type. You're then basically fooling Excel with a wrong content type which would cause failure and/or warnings in the latest Excel versions. It will also messup the original HTML source when you edit and save it in Excel. Just don't do that.
CSV in turn is a standard format which enjoys default support from Excel without any problems and is in fact easy and memory-efficient to generate. Although there are libraries out, you can in fact also easily write one in less than 20 lines (funny for ones who can't resist). You just have to adhere the RFC 4180 spec which basically contains only 3 rules:
- Fields are separated by a comma.
- If a comma occurs within a field, then the field has to be surrounded by double quotes.
- If a double quote occurs within a field, then the field has to be surrounded by double quotes and the double quote within the field has to be escaped by another double quote.
Here's a kickoff example:
public static <T> void writeCsv (List<List<T>> csv, char separator, OutputStream output)
throws IOException
{
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new OutputStreamWriter(output, "UTF-8"));
for (List<T> row : csv) {
StringBuilder line = new StringBuilder();
for (Iterator<T> iter = row.iterator(); iter.hasNext();) {
String field = String.valueOf(iter.next()).replace("\"", "\"\"");
if (field.indexOf(separator) > -1 || field.indexOf('"') > -1) {
field = '"' + field + '"';
}
line.append(field);
if (iter.hasNext()) {
line.append(separator);
}
}
writer.write(line.toString());
writer.newLine();
}
} finally {
if (writer != null) try { writer.close(); } catch (IOException ignore) {}
}
}
Here's an example how you could use it:
public static void main(String[] args) throws IOException {
List<List<String>> csv = new ArrayList<List<String>>();
csv.add(Arrays.asList("field1", "field2", "field3"));
csv.add(Arrays.asList("field1,", "field2", "fie\"ld3"));
csv.add(Arrays.asList("\"field1\"", ",field2,", ",\",\",\""));
writeCsv(csv, ',', System.out);
}
And inside a Servlet (yes, Servlet, don't use JSP for this!) you can basically do:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String filename = request.getPathInfo();
List<List<Object>> csv = someDAO().list();
response.setHeader("content-type", "application/csv");
response.setHeader("content-disposition", "attachment;filename=\"" + filename + "\"");
writeCsv(csv, ';', response.getOutputStream());
}
Map this servlet on something like /csv/*
and invoke it as something like http://example.com/context/csv/filename.csv
. That's all.
Note that I added the possiblity to specify the separator character separately, because it may depend on the locale used whether Excel would accept a comma ,
or semicolon ;
as CSV field separator. Note that I also added the filename to the URL pathinfo, because a certain webbrowser developed by a team in Redmond otherwise wouldn't save the download with the proper filename.