Apache POI操作Docx文档时踩坑指南

Apache POI处理Docx踩坑解析

1、背景

Apache POI是什么

百度百科介绍:

Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能。

系统环境

前端:Vue2.7

后端:SpringBoot 2.3.12.RELEASE

文档编辑组件:Apache POI 4.1.2

开发工具:IDEA 2024.1

问题描述

文档中的变量以${里面是变量内容}的形式存在,在扫描文档变量时,会将里面的变量名展示在系统界面中。对变量名赋值后,会将变量替换。

如图1所示为模板文档,其中包含了一个变量,在系统界面中展示时以图2所示为例,但将内容替换到模板中变量后,内容却发生了变化;正常应该是四行“测试”,但变量位置只有一行,剩下三行跑到了文档表格单元格的最下方。

图1 模板文档图2 系统界面

图3 生成的文档

2、Debug定位问题

先构思一个可能存在问题的节点列表:

① 前后端传参有误

② 替换内容时寻找替换位置有误

③ 替换内容时有误

下面开始一一排查。

排查是否前后端传参有误

先判断前后端传参是否有误,服务端以Debug启动,并在下载文档接口处进行断点,查看接口断点处接收到的构建参数。

图4 接口断点处参数

下载文档接口断点处传入参数现实是正确的,四行测试内容。所以排除第一个可能。

排查替换内容时寻找替换位置有误

直接在替换完成后的地方打断点,查看替换完成后的表格单元格内容。

图5 替换完成的单元格结果
替换完成后的单元格内容只呈现一行测试,剩下三行同样是消失不见了,但位置是没有问题的,因为在替换之前这个位置是变量占位符的位置。

排查替换内容时有误

排查这项就麻烦了,需要进入替换方法一行一行的跟着看,但进入这个方法后,我看了一遍代码基本上初步确定问题了,但还是走一遍Debug确认一下。

通过断点查看XWPFTableCell cell的变化,在进入循环之前都是正常的,第一行测试正常替换掉了原占位变量。

图6 第一行测试

跟着过了第一次循环,也就是将第二行测试放到第一行测试之后,也需要在第一行测试之后创建段落,但段落被创建到了单元格的最后了,也就导致了剩下所有的内容都被放到了最后。

图7 第二行测试位置

3、解决问题

首先需要理解Apache POI在编辑Docx的基本概念,才能知道这里应该怎么改。

在Apache POI中依赖关系是:文档 -> 表格 -> 单元格 -> 段落 -> 文本片段。

而我们代码中:

// 创建新段落
XWPFParagraph newParagraph = cell.addParagraph();

实际上是给单元格添加一个段落,而默认的段落位置就是在单元格的最后,这就是问题所在。

所以我们只需要在创建新段落的时候告诉POI要在哪里创建新段落即可,下面是将该逻辑单独抽象出来的方法:

/**
 * 续写后续的行,除了第一行
 * @param cell 表格单元格
 * @param paragraph 命中关键词的行
 * @param lines 要续写的新文本
 */
private void continuedWritingLineToCell(XWPFTableCell cell, XWPFParagraph paragraph, String[] lines) {
    for (int i = 1; i < lines.length; i++) {
        // 使用目标段落的光标,并移动到段落的末尾
        XWPFParagraph newParagraph;
        int paragraphIndex = cell.getParagraphs().indexOf(paragraph);
        if (cell.getParagraphs().size() - 1 == paragraphIndex) { // 最后一个段落
            newParagraph = cell.addParagraph();
        }else{ // 中间的段落
            newParagraph = cell.insertNewParagraph(cell.getParagraphs().get(paragraphIndex + 1).getCTP().newCursor());
        }
        // 创建运行
        XWPFRun newRun = newParagraph.createRun();
        newRun.setText(lines[i]);
        this.copyFormatting(newParagraph, paragraph);

        // 更新 paragraph 引用,以便后续的段落插入正确的位置
        paragraph = newParagraph;
    }
}

4、总结

利用开源工具做一个业务的时候必须理解开源工具的基本构造,知道每一个方法是干啥的,调用方法之后会产生什么效果,否则只会越改越乱。

5、还有话说

而且,我解决这个问题的时候还发现了一个新问题,在这个方法内的前几行代码:

for (int i = paragraph.getRuns().size() - 1; i >= 0; i--) {
    paragraph.removeRun(i);
}

这个操作是删除一个段落中的所有文本片段,但这样会导致变量如何不是自己独占一个段落时其他内容也会被删除。这个问题修改起来就扯的更多了,下次再展开说这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值