// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.visualization.datasource.render;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
import com.google.visualization.datasource.DataSourceRequest;
import com.google.visualization.datasource.base.ResponseStatus;
import com.google.visualization.datasource.datatable.ColumnDescription;
import com.google.visualization.datasource.datatable.DataTable;
import com.google.visualization.datasource.datatable.TableCell;
import com.google.visualization.datasource.datatable.TableRow;
import com.google.visualization.datasource.datatable.ValueFormatter;
import com.google.visualization.datasource.datatable.value.ValueType;
/**
* Takes a data table and returns a csv string.
*
* @author Nimrod T.
*/
public class CsvRenderer extends AbstractRenderer {
private static final String DEFAULT_SEPARATOR = ",";
/**
* The separator string used to delimit row values.
*/
private String separator;
public CsvRenderer() {
this(DEFAULT_SEPARATOR);
}
public CsvRenderer(String separator) {
this.separator = separator;
}
@Override
public String getMimetype() {
return "text/csv";
}
@Override
public void setHeaders(DataSourceRequest request, HttpServletResponse response) {
super.setHeaders(request, response);
final String filename = request.getDataSourceParameters().getFilename();
if(StringUtils.isNotBlank(filename)) {
response.setHeader("Content-Disposition", String.format("attachment; filename=%s", filename));
}
}
/**
* Generates a CSV string representation of a data table.
*/
@Override
public CharSequence render(final DataSourceRequest request, final DataTable dataTable) {
// Deal with empty data table.
if(dataTable.getColumnDescriptions().isEmpty()) {
return StringUtils.EMPTY;
}
// Deal with non-empty data table.
StringBuilder sb = new StringBuilder();
List<ColumnDescription> columns = dataTable.getColumnDescriptions();
// Append column labels
for(ColumnDescription column : columns) {
sb.append(escapeString(column.getLabel())).append(separator);
}
Map<ValueType, ValueFormatter> formatters = ValueFormatter.createDefaultFormatters(request.getUserLocale());
// Remove last comma.
int length = sb.length();
sb.replace(length - 1, length, "\n");
// Append the data cells.
List<TableRow> rows = dataTable.getRows();
for(TableRow row : rows) {
List<TableCell> cells = row.getCells();
for(TableCell cell : cells) {
if(cell.isNull()) {
sb.append("null");
}
else {
String value = cell.getValue().toString();
if(value == null) {
value = formatters.get(cell.getType()).format(cell.getValue());
}
ValueType type = cell.getType();
// Escape the string with quotes if its a text value or if it contains a comma.
if(value.indexOf(',') > -1 || type.equals(ValueType.TEXT)) {
sb.append(escapeString(value));
}
else {
sb.append(value);
}
}
sb.append(separator);
}
// Remove last comma.
length = sb.length();
sb.replace(length - 1, length, "\n");
}
return sb.toString();
}
/**
* Escapes a string that is written to a csv file. The escaping is as follows:
* 1) surround with ".
* 2) double each internal ".
*
* @param input The input string.
* @return An escaped string.
*/
private String escapeString(String input) {
StringBuilder sb = new StringBuilder();
sb.append("\"");
sb.append(StringUtils.replace(input, "\"", "\"\""));
sb.append("\"");
return sb.toString();
}
public String error(DataSourceRequest request, final ResponseStatus status) {
StringBuilder sb = new StringBuilder();
sb.append("Error: ").append(status.getReasonType().getMessageForReasonType(null));
sb.append(". ").append(status.getDescription());
return this.escapeString(sb.toString());
}
}