poi-tl导出word, 含表格单元格合并,表格单元格多图合并

poi-tl是干嘛的?

poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档,

支持:

        1.单系列图表指的是饼图(3D饼图)、圆环图等。

        2.多系列图表指的是条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、散点图等。

        3.组合图表指的是由多系列图表(柱形图、折线图、面积图)组合而成的图表。

等等…

官网地址:Poi-tl Documentation

  • 目录

    poi-tl是干嘛的?

    文章目录

    展示

    一、效果展示

    二、模板

     三、使用步骤

    1.引包

    2.代码

    四.测试及数据处理

    五.总结

展示

一、效果展示

二、模板

 三、使用步骤

1.引包

该案例使用poi-tl版本: 1.12.0

<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.12.0</version>
</dependency>

该案例引入的包

    <dependencies>
        <dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.17.1</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
            <version>1.7.2</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.28</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.12.RELEASE</version>
        </dependency>
    </dependencies>

2.代码

ServerTablePolicy:主要处理合并数据表格工具类

MergeRowsRenderPolicy:含图片表格合并工具类

MultiImageRenderPolicy:注册添加前缀为’&’的自定义插件:

2.1.ServerTablePolicy:主要处理合并数据表格工具类

public class ServerTablePolicy extends DynamicTableRenderPolicy {
    @Override
    public void render(XWPFTable xwpfTable, Object tableData) throws Exception {
        if (null == tableData) {
            return;
        }

        // 参数数据声明
        ServerTableData serverTableData = (ServerTableData) tableData;
        List<RowRenderData> serverDataList = serverTableData.getServerDataList();
        List<Map<String, Object>> groupDataList = serverTableData.getGroupDataList();
        Integer mergeColumn = serverTableData.getMergeColumn();

        if (CollectionUtils.isNotEmpty(serverDataList)) {
            // 先删除一行, demo中第一行是为了调整 三线表 样式
            xwpfTable.removeRow(1);

            // 行从中间插入, 因此采用倒序渲染数据
            for (int i = serverDataList.size() - 1; i >= 0; i--) {
                XWPFTableRow newRow = xwpfTable.insertNewTableRow(1);
                newRow.setHeight(400);
                //每一行填充多少个单元格
                for (int j = 0; j < serverDataList.get(0).getCells().size(); j++) {
                    newRow.createCell();
                }
                // 渲染一行数据
                TableRenderPolicy.Helper.renderRow(newRow, serverDataList.get(i));
            }


            // 处理合并
            for (int i = 0; i < serverDataList.size(); i++) {
                // 获取要合并的名称那一列数据 mergeColumn代表要合并的列,从0开始
                String typeNameData = serverDataList.get(i).getCells().get(mergeColumn).getParagraphs().get(0).getContents().get(0).toString();
                for (int j = 0; j < groupDataList.size(); j++) {
                    String typeNameTemplate = String.valueOf(groupDataList.get(j).get("typeName"));
                    int listSize = Integer.parseInt(String.valueOf(groupDataList.get(j).get("listSize")));
                    //如果只有一条数据不进行合并处理
                    if (listSize!=1) {
                        // 若匹配上 就直接合并
                        if (typeNameTemplate.equals(typeNameData)) {
                            //如果合并列不为空直接合并,否则提示输入要合并的列
                            if (CollectionUtils.isNotEmpty(serverTableData.getMergeColumnList())) {
                                for (Integer mergeColumns : serverTableData.getMergeColumnList()) {
                                    TableTools.mergeCellsVertically(xwpfTable, mergeColumns, i + 1, i + listSize);
                                }
                                groupDataList.remove(j);
                                break;
                            } else {
                                throw new Exception("要合并列不能为空!");
                            }
                        }
                    }
                }
            }
        }
    }
}

2.2.MergeRowsRenderPolicy:含图片表格合并工具类

public class MergeRowsRenderPolicy implements RenderPolicy {

    private final String prefix;
    private final String suffix;
    private final boolean onSameLine;

    public MergeRowsRenderPolicy() {
        this(false);
    }

    public MergeRowsRenderPolicy(boolean onSameLine) {
        this("[", "]", onSameLine);
    }

    public MergeRowsRenderPolicy(String prefix, String suffix) {
        this(prefix, suffix, false);
    }

    public MergeRowsRenderPolicy(String prefix, String suffix, boolean onSameLine) {
        this.prefix = prefix;
        this.suffix = suffix;
        this.onSameLine = onSameLine;
    }

    @Override
    public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
        RunTemplate runTemplate = (RunTemplate) eleTemplate;
        XWPFRun run = runTemplate.getRun();
        try {
            if (!TableTools.isInsideTable(run)) {
                throw new IllegalStateException(
                        "The template tag " + runTemplate.getSource() + " must be inside a table");
            }
            XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
            XWPFTable table = tagCell.getTableRow().getTable();
            run.setText("", 0);

            int templateRowIndex = getTemplateRowIndex(tagCell);
            if (data instanceof Iterable) {
                Iterator<?> iterator = ((Iterable<?>) data).iterator();
                XWPFTableRow templateRow = table.getRow(templateRowIndex);
                int insertPosition = templateRowIndex;

                TemplateResolver resolver = new TemplateResolver(template.getConfig().copy(prefix, suffix));
                boolean firstFlag = true;
                int index = 0;
                boolean hasNext = iterator.hasNext();
                while (hasNext) {
                    Object root = iterator.next();
                    hasNext = iterator.hasNext();

                    insertPosition = templateRowIndex++;
                    table.insertNewTableRow(insertPosition);
                    setTableRow(table, templateRow, insertPosition);

                    // double set row
                    XmlCursor newCursor = templateRow.getCtRow().newCursor();
                    newCursor.toPrevSibling();
                    XmlObject object = newCursor.getObject();
                    XWPFTableRow nextRow = new XWPFTableRow((CTRow) object, table);
                    if (!firstFlag) {
                        // update VMerge cells for non-first row
                        List<XWPFTableCell> tableCells = nextRow.getTableCells();
                        for (XWPFTableCell cell : tableCells) {
                            CTTcPr tcPr = TableTools.getTcPr(cell);
                            CTVMerge vMerge = tcPr.getVMerge();
                            if (null == vMerge) {continue;}
                            if (STMerge.RESTART == vMerge.getVal()) {
                                vMerge.setVal(STMerge.CONTINUE);
                            }
                        }
                    } else {
                        firstFlag = false;
                    }
                    setTableRow(table, nextRow, insertPosition);

                    RenderDataCompute dataCompute = template.getConfig()
                            .getRenderDataComputeFactory()
                            .newCompute(EnvModel.of(root, EnvIterator.makeEnv(index++, hasNext)));
                    List<XWPFTableCell> cells = nextRow.getTableCells();
                    cells.forEach(cell -> {
                        List<MetaTemplate> templates = resolver.resolveBodyElements(cell.getBodyElements());
                        new DocumentProcessor(template, resolver, dataCompute).process(templates);
                    });
                }
                // 处理连续数据相同行合并
                int currPos = getTemplateRowIndex(tagCell);
                XWPFTableRow prevRow = table.getRow(currPos);
                while (currPos <= insertPosition) {
                    currPos++;
                    XWPFTableRow nextRow = table.getRow(currPos);
                    List<XWPFTableCell> cells = nextRow.getTableCells();
                    int currCellPos = 0;
                    while (currCellPos < cells.size()) {
                        CTTcPr tcPrPrev = TableTools.getTcPr(prevRow.getCell(currCellPos));
                        CTVMerge vMergePrev = tcPrPrev.getVMerge();
                        if (null == vMergePrev) {
                            vMergePrev = tcPrPrev.addNewVMerge();
                            vMergePrev.setVal(STMerge.RESTART);
                        }
                        CTTcPr tcPrNext = TableTools.getTcPr(nextRow.getCell(currCellPos));
                        CTVMerge vMergeNext = tcPrNext.getVMerge();
                        if (null == vMergeNext) {
                            vMergeNext = tcPrNext.addNewVMerge();
                            vMergeNext.setVal(STMerge.RESTART);
                        }
                        // 判断上下两行的字符串值是否一样,一样就合并
                        if (StringUtils.equalsIgnoreCase(
                                prevRow.getCell(currCellPos).getText(),
                                nextRow.getCell(currCellPos).getText())) {
                            vMergeNext.setVal(STMerge.CONTINUE);
                        }
                        currCellPos++;
                    }
                    prevRow = nextRow;
                }

            }

            table.removeRow(templateRowIndex);
            afterloop(table, data);
        } catch (Exception e) {
            throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
        }
    }

    private int getTemplateRowIndex(XWPFTableCell tagCell) {
        XWPFTableRow tagRow = tagCell.getTableRow();
        return onSameLine ? getRowIndex(tagRow) : (getRowIndex(tagRow) + 1);
    }

    protected void afterloop(XWPFTable table, Object data) {
    }

    @SuppressWarnings("unchecked")
    private void setTableRow(XWPFTable table, XWPFTableRow templateRow, int pos) {
        List<XWPFTableRow> rows = (List<XWPFTableRow>) ReflectionUtils.getValue("tableRows", table);
        rows.set(pos, templateRow);
        table.getCTTbl().setTrArray(pos, templateRow.getCtRow());
    }

    private int getRowIndex(XWPFTableRow row) {
        List<XWPFTableRow> rows = row.getTable().getRows();
        return rows.indexOf(row);
    }
}

2.3.MultiImageRenderPolicy:注册添加前缀为’&’的自定义插件:

public class MultiImageRenderPolicy extends AbstractRenderPolicy<Collection<PictureRenderData>> {

    @Override
    protected void afterRender(RenderContext<Collection<PictureRenderData>> context) {
        clearPlaceholder(context, true);
    }

    @Override
    public void doRender(RenderContext<Collection<PictureRenderData>> context) throws Exception {
        IRunBody iRunBody = context.getRun().getParent();
        if (iRunBody instanceof XWPFParagraph) {
            XWPFParagraph p = (XWPFParagraph) iRunBody;
            Collection<PictureRenderData> data = context.getData();
            if (CollectionUtils.isNotEmpty(data)) {
                for (PictureRenderData pic : data) {
                    PictureRenderPolicy.Helper.renderPicture(p.createRun(), pic);
                }
            }
        }
    }
}

2.4. 数据表格合并实体类

@Data
public class ServerTableData {

    /**
     *  携带表格中真实数据
     */
    private List<RowRenderData> serverDataList;

    /**
     * 携带要分组的信息
     */
    private List<Map<String, Object>> groupDataList;

    /**
     * 需要合并的列,从0开始
     */
    private Integer mergeColumn;

    /**
     * 具体要合并的列集合
     */
    private List<Integer> mergeColumnList;
}

四.测试及数据处理

public class WordExport {

    public static void main(String[] args) throws IOException {
        // 获取模板文件流
        String filePath = "D:\\poi-tl\\template.docx";
        String targetPath = "D:\\poi-tl\\templateTest.docx";
        String picturePath = "D:\\poi-tl\\1_1.jpg";
        String picturePaths="D:\\poi-tl\\1_1.jpg,D:\\poi-tl\\2_2.jpg,D:\\poi-tl\\3_3.jpg,D:\\poi-tl\\4_4.jpg";
        String httpPicturePath = "http://deepoove.com/images/icecream.png";
        String httpPicturePaths = "http://deepoove.com/images/icecream.png,http://deepoove.com/images/icecream.png";

        //单个图片数据模板
        PictureRenderData picture = Pictures.ofStream(new FileInputStream(picturePath), PictureType.JPEG)
                .size(400, 300).create();

        //多张图片数据模板

        ArrayList<JSONObject> pictures = new ArrayList<JSONObject>() {
            {
                add(new JSONObject().fluentPut("url",picturePath));
                add(new JSONObject().fluentPut("url", httpPicturePath));
            }
        };

        //多张图片处理
        List<Map<String, Object>> pictureList = new ArrayList(){{
            for (int i = 0; i < 2; i++) {
                add(new HashMap<String,Object>(){{
                    put("url", Pictures.ofStream(new FileInputStream(picturePath), PictureType.JPEG)
                            .size(220,150).create());
                }});
            }}};

        //如果表格内不需要合并数据直接put("",new ArrayList<string,object>/new ArrayList<实体类>)

        //单元格多图片
        ArrayList<JSONObject> arrayList = getArrayList(picturePaths,httpPicturePaths);

        // 伪造一个表格数据
        //具体要合并的列
        List<Integer> mergeColumnList = Arrays.asList(0,3);
        ServerTableData oneTable = getServerTableData(mergeColumnList);

        //表格绑定
        Configure config = Configure.builder()
                //注册添加前缀为'&'的自定义插件:
                .addPlugin('&', new MultiImageRenderPolicy())
                //含图片数据表格合并同列相邻的单元格
                .bind("items", new MergeRowsRenderPolicy())
                //数据表格可指定合并列
                .bind("oneTable",new ServerTablePolicy())
                .build();

        XWPFTemplate template = XWPFTemplate.compile(filePath, config).render(
                new HashMap<String, Object>(){{
                    //单张图片
                    put("image", picture);
                    //单张图片描述
                    put("imageDesc","这是图片描述");
                    //多张图片1
                    put("pictures", pictures);
                    //多张图片2
                    put("pictureList", pictureList);
                    //合并表格
                    put("oneTable",oneTable);
                    //单元格多个图片
                    put("items", arrayList);


                }});

        //输出网络流 本地导出
        template.writeAndClose(new FileOutputStream(targetPath));

        //网络导出
        //exportFile(filePath,hashMap,config);
    }

    /**
     *  表格合并数据处理
     * @param mergeColumnList 要合并的列集合
     * @return 数据集合
     */
    private static ServerTableData getServerTableData(List<Integer> mergeColumnList) {
        ServerTableData serverTableData = new ServerTableData();
        List<RowRenderData> serverDataList = new ArrayList<>();
        for (int j = 0; j < 5; j++) {
            String typeName;
            if (j > 1) {
                typeName = "张三";
                serverDataList.add(Rows.of(typeName, "男", "3", "喝酒").center().create());
            }else {
                typeName = "李四";
                serverDataList.add(Rows.of(typeName, "女", "4", "逛街").center().create());
            }
        }

        List<Map<String, Object>> groupDataList = new ArrayList<>();
        Map<String, Object> groupData1 = new HashMap<>();
        //typeName是张三的数据有两条
        groupData1.put("typeName", "张三");
        groupData1.put("listSize", "3");
        Map<String, Object> groupData2 = new HashMap<>();
        //typeName是李四的数据有三条
        groupData2.put("typeName", "李四");
        groupData2.put("listSize", "2");
        groupDataList.add(groupData1);
        groupDataList.add(groupData2);

        //具体合并的列
        serverTableData.setMergeColumnList(mergeColumnList);
        //表格中数据
        serverTableData.setServerDataList(serverDataList);
        //分组的信息
        serverTableData.setGroupDataList(groupDataList);
        //从哪列开始合并
        serverTableData.setMergeColumn(0);
        return serverTableData;
    }

    /**
     * 含图片表格数据处理, 可单独抽成工具类,官网支持多种不同图片处理
     * @param picturePaths java图片路径
     * @param httpPicturePaths 网络图片路径
     * @return 数据集合
     * @throws IOException 抛出io流异常
     */
    public static ArrayList<JSONObject> getArrayList(String picturePaths,String httpPicturePaths) throws IOException {

        String[] picturePathArr = picturePaths.split(",");
        String[] httpPicturePathArr = httpPicturePaths.split(",");

        word模板中使用 {{&images}}来插入多张图片了
        ArrayList<JSONObject> arrayList = new ArrayList<JSONObject>() {{
            //网络图片
            add(new JSONObject().fluentPut("title", "第一组图片")
                    .fluentPut("detail", "这是第1.1组图片描述")
                    .fluentPut("images", new ArrayList<PictureRenderData>() {{
                        for (String httpPicturePath : httpPicturePathArr) {
                            add(Pictures.ofUrl(httpPicturePath).create());
                        }
                    }}));
            //网络图片
            add(new JSONObject().fluentPut("title", "第一组图片")
                    .fluentPut("detail", "这是第1.2组图片描述")
                    .fluentPut("images", new ArrayList<PictureRenderData>() {{
                        for (String httpPicturePath : httpPicturePathArr) {
                            add(Pictures.ofUrl(httpPicturePath).create());
                        }
                    }}));

            // java图片
            add(new JSONObject().fluentPut("title", "第二组图片")
                    .fluentPut("detail", "这是第2.1组图片描述")
                    .fluentPut("images", new ArrayList<PictureRenderData>() {{
                        for (String picturePath : picturePathArr) {
                            BufferedImage bufferedImage = ImageIO.read(new FileInputStream(picturePath));
                            add(Pictures.ofBufferedImage(bufferedImage, PictureType.PNG)
                                    .size(205, 205).create());
                        }
                    }}));
        }};
        return arrayList;
    }

    /**
     * 浏览器导出
     * @param fileName 文件名称
     * @param map 数据集合
     * @param configure 配置信息
     */
    public static void exportFile(String fileName, Map<String, Object> map,Configure configure) {
        try {

            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletResponse response = attributes.getResponse();

            // 获取Word模板,模板存放路径在项目的resources目录下
            //PoiController就是方法所在的类
            XWPFTemplate template = XWPFTemplate.compile(fileName,configure).render(map);

            //处理文件名乱码
            String attachName = new String(("测试.docx").getBytes(), "ISO-8859-1");

            // 浏览器端下载
            response.setContentType("application/x-download;charset=" + "utf-8");
            response.addHeader("Content-Disposition", "attachment;filename=" + attachName);

            OutputStream out = response.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(out);
            template.write(bos);
            bos.flush();
            out.flush();
            PoitlIOUtils.closeQuietlyMulti(template, bos, out);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

五.总结

如果出现出现与项目其他技术不兼容,可单独拉出一个服务,但此时涉及到不同服务之间通过feign实现Response传递可参考https://mp.csdn.net/mp_blog/creation/editor/129615552

针对于未接触的技术,先了解官方文档,然后百度,谷歌,嘎嘎一顿乱造,想要的结果也就差不多了,

有疑问欢迎交流沟通!!!

版权声明:本文为博主作者:小菜鸡℡原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/m0_47520320/article/details/129855663

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2023年12月29日
下一篇 2023年12月29日

相关推荐