Как написать пользовательский рендерер XLSX для grails

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

Оба варианта работают.