Java webpoi

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

2019-08-28  本文已影响0人  ForeverChance

一、 关键词

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

上一篇下一篇

猜你喜欢

热点阅读