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所示为例,但将内容替换到模板中变量后,内容却发生了变化;正常应该是四行"测试",但变量位置只有一行,剩下三行跑到了文档表格单元格的最下方。

2、Debug定位问题

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

① 前后端传参有误

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

③ 替换内容时有误

下面开始一一排查。

排查是否前后端传参有误

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

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

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

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

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

排查替换内容时有误

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

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

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

3、解决问题

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

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

而我们代码中:

java 复制代码
// 创建新段落
XWPFParagraph newParagraph = cell.addParagraph();

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

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

java 复制代码
/**
 * 续写后续的行,除了第一行
 * @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、还有话说

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

java 复制代码
for (int i = paragraph.getRuns().size() - 1; i >= 0; i--) {
    paragraph.removeRun(i);
}

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

相关推荐
万岳科技系统开发1 天前
AI赋能互联网医院小程序开启智慧医疗新时代
人工智能·小程序·apache
愤怒的苹果ext2 天前
Apache Superset 使用
apache·doris·bi·superset·ai bi
qq_185198692 天前
骆驼任务Flowable + Apache Camel 集成
spring·apache·flowable
ClickHouseDB2 天前
ClickHouse 十年开源,今非昔比!
clickhouse·开源·apache
代码不会写3 天前
Apache Iceberg:架构原理、读写机制、性能优化与生态
性能优化·架构·apache·iceberg
Gent_倪4 天前
数据治理之安全管理:Apache Ranger
apache
SelectDB5 天前
Apache Doris 实战教程:手把手实现 ClickHouse 表结构迁移与数据校验
apache
SelectDB5 天前
Apache Doris 实战教程:从零搭建 MCP Server,让 AI Agent 直接用自然语言查数据
apache
SelectDB5 天前
Apache Doris Segment V3 宽表优化实战教程:从建表到体验元数据按需加载
apache
java_logo5 天前
Docker Compose 部署 Apache Superset:轻松搭建开源 BI 平台
docker·开源·apache·superset·轩辕镜像·superset部署方案·docker superset