美文网首页Java webpoi
Java实现word导出(表格带图片)

Java实现word导出(表格带图片)

作者: ForeverChance | 来源:发表于2019-08-28 12:33 被阅读0次

    一、 关键词

    POI:Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能
    POI-TL:基于Apache POI的Word模板引擎,通过插件机制使其具有高度扩展性
    word格式:

     1. doc:POI组件HWPF组件支持对doc文件的操作
     2. docx:POI组件XWPF组件支持对docx文件的操作
     3. POI-TL使用的是XWPF组件,所以更好的支持docx文件的操作。

    二、 简单用法示例

     1. 依赖

    <!-- poi包 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>4.1.0</version>
    </dependency>
    
    <!-- poi处理xlsx格式,用于处理word中的表格 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>4.1.0</version>
    </dependency>
    
    <!-- poi-tl基于poi的word模板引擎 -->
    <dependency>
        <groupId>com.deepoove</groupId>
        <artifactId>poi-tl</artifactId>
        <version>1.5.0</version>
    </dependency>
    

     2. word模板

     3. 初始化数据

    public Map<String,Object> initWordData() throws Exception {
    
        Map<String, Object> renderData = new HashMap<>();
        // 文本标题
        TextRenderData title = new TextRenderData("这是一个标题");
        renderData.put("title", title);
        // 文本副标题
        TextRenderData subTitle = new TextRenderData("这是一个副标题");
        renderData.put("sub_title", subTitle);
        // 文本内容
        StringBuilder contentValue = new StringBuilder()
                .append("内容一")
                .append(System.lineSeparator())
                .append("内容二")
                .append(System.lineSeparator())
                .append("内容三")
                .append(System.lineSeparator())
                .append("内容四")
                .append(System.lineSeparator())
                .append("......");
        TextRenderData content = new TextRenderData("00C1FF" ,contentValue.toString());
        renderData.put("content", content);
        // 作者信息
        Map<String, Object> author = new HashMap<>();
        author.put("name", "作者");
        author.put("email", new HyperLinkTextRenderData("xxx@xxx.xxx","https://www.baidu.com"));
        PictureRenderData avatar = new PictureRenderData(100, 100, ".jpg", BytePictureUtils.getUrlBufferedImage("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1566878817&di=ecc0bc000703c669f5d660ca9bb70f17&src=http://img11.photophoto.cn/20090505/0035035789118626_s.jpg"));
        author.put("avatar", avatar);
        renderData.put("author", author);
    
        // 表格1 (使用表格模板)
        RowRenderData header = RowRenderData.build("单元格1", "单元格2");
        RowRenderData row1 = RowRenderData.build("测试11", "测试12");
        RowRenderData row2 = RowRenderData.build("测试21", "测试22");
        List<RowRenderData> table1RenderData = new ArrayList<>();
        table1RenderData.add(row1);
        table1RenderData.add(row2);
        MiniTableRenderData miniTableRenderData = new MiniTableRenderData(header, table1RenderData, MiniTableRenderData.WIDTH_A4_MEDIUM_FULL);
        renderData.put("table1", miniTableRenderData);
    
        // 表格2 (需要添加填充表信息策略)
        List<RowRenderData> table2RenderData = new ArrayList<>();
        table2RenderData.add(RowRenderData.build("张三","地址xxx"));
        table2RenderData.add(RowRenderData.build("李四","地址xxx"));
        renderData.put("table2", table2RenderData);
    
        return renderData;
    }
    

     4. 定义现有表格填充数据策略

    // 自定义表格填充策略
    public class DetailTablePolicy extends DynamicTableRenderPolicy {
    
        // 填充的起始行,
        // 注意:行号从0开始
        private int row = 1;// 默认从第一行开始
    
        public int getRow() {
            return row;
        }
    
        public void setRow(int row) {
            this.row = row;
        }
    
        public DetailTablePolicy() {
        }
    
        public DetailTablePolicy(int row) {
            this.row = row;
        }
    
        @Override
        public void render(XWPFTable table, Object data) {
            if (null == data) return;
            // 对应渲染的表格数据
            List<RowRenderData> table2RenderData = (List<RowRenderData>) data;
            // 删除当前起始行
            table.removeRow(row);
            // 插入每一行
            for (int i = 0,len = table2RenderData.size(); i < len; i++) {
                XWPFTableRow insertNewTableRow = table.insertNewTableRow(row+i);
                RowRenderData rowRenderData = table2RenderData.get(i);
                for (int j = 0,len2 = rowRenderData.size(); j < len2; j++) insertNewTableRow.createCell();
                // 渲染每一行数据
                MiniTableRenderPolicy.Helper.renderRow(table, row + i, rowRenderData);
            }
        }
    }
    

     5. 导出word

    public void exportWord() {
    
        OutputStream os = null;
        XWPFTemplate template = null;
        try {
            Map<String, Object> renderData = initWordData();
            Configure.ConfigureBuilder builder = Configure.newBuilder();
            // 使用自定义表格填充策略
            builder.customPolicy("table2", new DetailTablePolicy(1));
            Configure configure = builder.build();
            // 模板文件
            String templatePath = "D:\\Desktop\\SimpleTemplate.docx";
            InputStream is = new FileInputStream(new File(templatePath));
            // 输出位置
            String outPath = "D:\\Desktop\\test.docx";
            os = new FileOutputStream(new File(outPath));
            // 编译,导入策略插件,并渲染数据
            template = XWPFTemplate.compile(is, configure).render(renderData);
            // 输出
            template.write(os);
            os.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
                if (template != null) {
                    template.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

     6. 运行结果

     7. 注意:

      当word中有表格需要渲染时(无论是{{#table}}或者是自定义的表格),都需要加入poi-ooxml依赖,否则会报错
    如图:(模板中存在table1和table2)

    注释poi-ooxml
      运行时会报错:

    三、 自定义策略

     1. 导出表格(带图片)

      1.1. 依赖

       同简单示例

      1.2. 模板
      1.3. 初始化数据
       1.3.1. 注意:

       ● RowRenderData中存放的数据是String或TextRenderData,因此需要将PictureRenderData转为String类型
       ● 然后渲染数据时,再将String转为PictureRenderData类型
       ● PictureRenderData没有无参构造函数,无法使用com.fasterxml.jackson.databind.ObjectMapper进行相互转换。因此需要自定义一个类来实现PictureRenderData与String之间的转换
       ● 具体实现如下:
        ○ 将PictureRenderData转换为PicRenderDataConvert对象,然后将PicRenderDataConvert对象转为String类型写入RowRenderData中

    PictureRenderData avatar = new PictureRenderData(100, 100, ".jpg", BytePictureUtils.getUrlBufferedImage("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1566878817&di=ecc0bc000703c669f5d660ca9bb70f17&src=http://img11.photophoto.cn/20090505/0035035789118626_s.jpg"));
    PicRenderDataConvert picRenderDataConvert = new PicRenderDataConvert(avatar);
    List<RowRenderData> picRenderData = new ArrayList<>();
    RowRenderData picRow = RowRenderData.build("张三", mapper.writeValueAsString(picRenderDataConvert));
    

        ○ 在渲染数据时,再获取PictureRenderData对象

    PicRenderDataConvert picRenderDataConvert = mapper.readValue(text, PicRenderDataConvert.class);
    // 转换图片
    PictureRenderData pictureRenderData = picRenderDataConvert.convert();
    if (pictureRenderData != null) {
        com.deepoove.poi.policy.PictureRenderPolicy.Helper.renderPicture(par.createRun(), pictureRenderData);
    }
    
       1.3.2. 自定义PictureRenderData转换类
    public class PicRenderDataConvert {
        // 字段与PictureRenderData相同
        private int width;
        private int height;
        private String path;
        private transient byte[] data;
        private String altMeta;
    
        // 无参构造
        public PicRenderDataConvert() {
        }
    
        public PicRenderDataConvert(PictureRenderData pictureRenderData) {
            this.width = pictureRenderData.getWidth();
            this.height = pictureRenderData.getHeight();
            this.path = pictureRenderData.getPath();
            this.altMeta = pictureRenderData.getAltMeta();
            this.data = pictureRenderData.getData();
        }
    
        // 转换方法,获取PictureRenderData对象
        public PictureRenderData convert() {
            if (data == null && StringUtils.isEmpty(path)) {
                return null;
            }
            PictureRenderData pictureRenderData = new PictureRenderData(width, height, path, data);
            return pictureRenderData;
        }
    }
    
       1.3.3. 初始化数据
    public Map<String,Object> initWordData() throws Exception {
    
        Map<String, Object> renderData = new HashMap<>();
        PictureRenderData avatar = new PictureRenderData(100, 100, ".jpg", BytePictureUtils.getUrlBufferedImage("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1566878817&di=ecc0bc000703c669f5d660ca9bb70f17&src=http://img11.photophoto.cn/20090505/0035035789118626_s.jpg"));
        // 渲染表格(包含图片)
        List<RowRenderData> picRenderData = new ArrayList<>();
        PicRenderDataConvert picRenderDataConvert = new PicRenderDataConvert(avatar);
        RowRenderData picRow = RowRenderData.build("张三", mapper.writeValueAsString(picRenderDataConvert));
        picRenderData.addAll(Arrays.asList(picRow, picRow));
        renderData.put("pic_table", picRenderData);
    
        return renderData;
    }
    
      1.4. 定义图片表格策略
    public class DetailPicTablePolicy extends DynamicTableRenderPolicy {
    
        // 填充数据其实行
        // 注意:行号从0开始
        private int row = 1;
    
        ObjectMapper mapper = new ObjectMapper();
    
        public DetailPicTablePolicy() {
        }
    
        public DetailPicTablePolicy(int row) {
            this.row = row;
        }
    
        public int getRow() {
            return row;
        }
    
        public void setRow(int row) {
            this.row = row;
        }
    
        @Override
        public void render(XWPFTable xwpfTable, Object data) {
            if (null == data) return;
            List<RowRenderData> renderData = (List<RowRenderData>) data;
            if (null != renderData) {
                xwpfTable.removeRow(row);
                // 循环插入行
                for (int i = 0, len = renderData.size(); i < len; i++) {
                    XWPFTableRow insertNewTableRow = xwpfTable.insertNewTableRow(row + i);
                    RowRenderData rowRenderData = renderData.get(i);
                    // 插入每一个单元格
                    for (int j = 0, len2 = rowRenderData.getCellDatas().size(); j < len2; j++)
                        insertNewTableRow.createCell();
                    // 自定义渲染表格(包括图片)
                    renderCellPic(xwpfTable, row + i, rowRenderData);
                }
            }
        }
        private void renderCellPic(XWPFTable table, int row, RowRenderData rowData) {
            if (null != rowData && rowData.size() > 0) {
                XWPFTableRow tableRow = table.getRow(row);
                ObjectUtils.requireNonNull(tableRow, "Row " + row + " do not exist in the table");
                TableStyle rowStyle = rowData.getRowStyle();
                List<CellRenderData> cellList = rowData.getCellDatas();
                XWPFTableCell cell = null;
    
                for (int i = 0; i < cellList.size(); ++i) {
                    cell = tableRow.getCell(i);
                    if (null == cell) {
                        RenderPolicy.logger.warn("Extra cell data at row {}, but no extra cell: col {}", row, cell);
                        break;
                    }
                    renderCell(cell, (CellRenderData) cellList.get(i), rowStyle);
                }
            }
        }
    
        public void renderCell(XWPFTableCell cell, CellRenderData cellData, TableStyle rowStyle) {
            TableStyle cellStyle = null == cellData.getCellStyle() ? rowStyle : cellData.getCellStyle();
            if (null != cellStyle && null != cellStyle.getBackgroundColor()) {
                cell.setColor(cellStyle.getBackgroundColor());
            }
    
            TextRenderData renderData = cellData.getRenderData();
            String cellText = renderData.getText();
            if (!StringUtils.isBlank(cellText)) {
                CTTc ctTc = cell.getCTTc();
                CTP ctP = ctTc.sizeOfPArray() == 0 ? ctTc.addNewP() : ctTc.getPArray(0);
                XWPFParagraph par = new XWPFParagraph(ctP, cell);
                StyleUtils.styleTableParagraph(par, cellStyle);
                String text = renderData.getText();
                String[] fragment = text.split("\\n", -1);
                if (fragment.length <= 1) {
                    try {
                        PicRenderDataConvert picRenderDataConvert = mapper.readValue(text, PicRenderDataConvert.class);
                        // 渲染图片
                        PictureRenderData pictureRenderData = picRenderDataConvert.convert();
                        if (pictureRenderData != null) {
                            com.deepoove.poi.policy.PictureRenderPolicy.Helper.renderPicture(par.createRun(), pictureRenderData);
                        }
                    } catch (Exception e) {
                        // 渲染文本
                        com.deepoove.poi.policy.TextRenderPolicy.Helper.renderTextRun(par.createRun(), renderData);
                    }
                } else {
                    for (int j = 0; j < fragment.length; ++j) {
                        if (0 != j) {
                            par = cell.addParagraph();
                            StyleUtils.styleTableParagraph(par, cellStyle);
                        }
    
                        XWPFRun run = par.createRun();
                        StyleUtils.styleRun(run, renderData.getStyle());
                        run.setText(fragment[j]);
                    }
                }
    
            }
        }
    }
    
      1.5. 浏览器导出word
    public void exportWord(HttpServletRequest request,
                           HttpServletResponse response) throws Exception {
    
            Map<String, Object> renderData = initWordData();
            Configure.ConfigureBuilder builder = Configure.newBuilder();
            // 表格(含图片)渲染策略
            builder.customPolicy("pic_table", new DetailPicTablePolicy(1));
            Configure configure = builder.build();
            String fileName = "测试文档";
    
            String templatePath = "D:\\Desktop\\PicTableTemplate.docx";
            renderWord(request, response, fileName, templatePath, renderData, configure);
    }
    
    public void renderWord(HttpServletRequest request,
                           HttpServletResponse response,
                           String fileName,
                           String templatePath,
                           Map<String, Object> renderData,
                           Configure configure) {
        String codedFileName = "临时文档.docx";
        ServletOutputStream out = null;
        XWPFTemplate template = null;
        try {
            if (!StringUtils.isEmpty(fileName)) {
                codedFileName = fileName + ".docx";
            }
            if (request.getHeader("USER-AGENT").toLowerCase().indexOf("msie") > 0 || request.getHeader("USER-AGENT").toLowerCase().indexOf("rv:11.0") > 0 || request.getHeader("USER-AGENT").toLowerCase().indexOf("edge") > 0) {
                codedFileName = URLEncoder.encode(codedFileName, "UTF8");
            } else {
                codedFileName = new String(codedFileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            }
    
            response.setHeader("content-disposition", "attachment;filename=" + codedFileName);
            InputStream is = new FileInputStream(new File(templatePath));
            if (configure == null) {
                template = XWPFTemplate.compile(is).render(renderData);
            } else {
                template = XWPFTemplate.compile(is, configure).render(renderData);
            }
            out = response.getOutputStream();
            template.write(out);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (template != null) {
                    template.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
      1.6. 运行结果

    四、 总结

     1. 依赖完整

      必须存在\color{#FF0000}{\small\mathbf{poi-ooxml}}依赖,否则无法进行word中的表格渲染

     2. 插件机制实现可扩展的word导出

     3. POI-TL支持\color{#FF0000}{\small\mathbf{docx}}文件操作

     4. 参考文档

      ● POI-TL入门
      ● Apache POI Word(docx) 入门
      ● Apache POI

    相关文章

      网友评论

        本文标题:Java实现word导出(表格带图片)

        本文链接:https://www.haomeiwen.com/subject/rrdpectx.html