#java #excel #grails #groovy #grails-2.0
#java #excel #grails #groovy #grails-2.0
Вопрос:
Я пытаюсь использовать пользовательские средства визуализации grails для рендеринга файла Excel XLSX с использованием библиотеки apache-poi. Я создал класс рендерера
class APIReportXLSXRenderer extends AbstractRenderer<APIReport> {
APIReportXLSXRenderer() {
super(APIReport, [new MimeType("application/vnd.ms-excel", "xlsx")] as MimeType[])
}
@Override
void render(APIReport output, RenderContext context) {
context.contentType = GrailsWebUtil.getContentType("application/vnd.ms-excel", GrailsWebUtil.DEFAULT_ENCODING)
def items = output.getItems()
def fields = output.getFields()
def headers = (fields.keySet() items[0].keySet()) as List
// convert maps to list of values each in order of the headers
def values = (items ?: []).collect { Map item -> headers.collect { String h -> item?.containsKey(h) ? item[h] : output[h] } }
def wos = new WriterOutputStream(context.writer)
createXLSXFile(headers, values, wos) // FIXME: This currently produces corrupt files.
}
// Lifted from ApiController
private static def createXLSXFile(List<String> headers = [], List items = [], OutputStream outputStream) {
Workbook wb = new XSSFWorkbook();
Sheet sheet = wb.createSheet();
int rowcount = 0;
// add header row
if (headers) {
Row row = sheet.createRow((short) rowcount );
headers.eachWithIndex { String entry, int i ->
row.createCell(i).setCellValue(entry)
}
}
// add cells
items?.each { List entry ->
Row row = sheet.createRow((short) rowcount );
entry.eachWithIndex { def value, int i -> row.createCell(i).setCellValue(value as String) }
}
wb.write(outputStream);
}
}
и мой контроллер отвечает объектом APIReport
respond(report)
Кажется, что это приводит к повреждению файла, но когда я делаю то же самое таким же образом в контроллере:
withFormat {
xlsx {
def items = output.getItems()
def fields = output.getFields()
response.setHeader("Content-Type", "application/vnd.ms-excel")
response.setHeader("Content-disposition", "attachment; filename="${filename}.${params.format}"")
def headers = (fields.keySet() items[0].keySet()) as List
// convert maps to list of values each in order of the headers
def values = (items?:[]).collect { headers.collect { String h -> it?.containsKey(h) ? it[h] : output[h] } }
createXLSXFile(headers, values, response.outputStream)
return
}
}
Это работает просто отлично.
Класс APIReport выглядит следующим образом:
class APIReport extends AbstractMap<String, Object> {
// request call
ApiParameters apicall;
// response
Map<String, Object> fields;
Long itemCount;
List<Map<String, Object>> items;
Map<String, Object> summary;
}
Я делаю что-то не так в рендерере? Или каков предпочтительный способ создания пользовательского рендерера в grails 2.3.8
Ответ №1:
Вместо WriterOutputStream, который предназначен для символьных данных, используйте двоичный поток, такой как BufferedOutputStream, для записи данных в браузер
Ответ №2:
В дополнение к ответу @tim_yates. Я немного изменил свою реализацию, чтобы получить доступ к объекту ответа и, следовательно, непосредственно к выходному потоку ответа:
class APIReportXLSXRenderer extends AbstractRenderer<APIReport> {
APIReportXLSXRenderer() {
super(APIReport, [new MimeType("application/vnd.ms-excel", "xlsx")] as MimeType[])
}
@Override
void render(APIReport output, RenderContext context) {
if (!(context instanceof ServletRenderContext)) {
throw new IllegalStateException("This renderer only works for servlet environment with ServletRendererContext")
}
ServletRenderContext ctx = (ServletRenderContext) context;
context.contentType = GrailsWebUtil.getContentType("application/vnd.ms-excel", GrailsWebUtil.DEFAULT_ENCODING)
def items = output.getItems()
def fields = output.getFields()
def headers = (fields.keySet() items[0].keySet()) as List
// convert maps to list of values each in order of the headers
def values = (items ?: []).collect { Map item -> headers.collect { String h -> item?.containsKey(h) ? item[h] : output[h] } }
createXLSXFile(headers, values, ctx.webRequest.response.outputStream)
}
// Lifted from ApiController
private static def createXLSXFile(List<String> headers = [], List items = [], OutputStream outputStream) {
Workbook wb = new XSSFWorkbook();
Sheet sheet = wb.createSheet();
int rowcount = 0;
// add header row
if (headers) {
Row row = sheet.createRow((short) rowcount );
headers.eachWithIndex { String entry, int i ->
row.createCell(i).setCellValue(entry)
}
}
// add cells
items?.each { List entry ->
Row row = sheet.createRow((short) rowcount );
entry.eachWithIndex { def value, int i -> row.createCell(i).setCellValue(value as String) }
}
wb.write(outputStream);
}
}
Оба варианта работают.