如何为grails编写XLSX自定义渲染器

时间:2014-06-17 18:53:14

标签: java excel grails groovy grails-2.0

我正在尝试使用grails自定义渲染器来使用apache-poi库渲染Excel XLSX文件。我做了一个渲染器类

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中制作自定义渲染器的首选方法是什么

2 个答案:

答案 0 :(得分:2)

使用二进制流(如BufferedOutputStream)将数据写入浏览器,而不是用于字符数据的WriterOutputStream

答案 1 :(得分:0)

除了@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);
    }
}

这两个选项都有效。