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);
}

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

相关推荐
阿里云云原生9 小时前
乌镇大赛丨5 万奖金已备好!RocketMQ 等你一起打造全新 AI-Native 管控平台
apache·rocketmq
摇滚侠2 天前
Apache Skywalking 实战 阅读笔记 第三章
笔记·apache·skywalking
摇滚侠3 天前
Apache Skywalking 实战 阅读笔记 第二章
笔记·apache·skywalking
摇滚侠4 天前
Apache Skywalking 实战 阅读笔记 第一章
笔记·apache·skywalking
小小龙学IT21 天前
Apache Airflow 2.x 深度指南:用 Python 编排一切的现代化工作流引擎
开发语言·python·apache
Shepherd061921 天前
【IT 运维】Apache 使用 mod_remoteip 恢复 Cloudflare 后的真实访客 IP
运维·tcp/ip·apache
isyangli_blog22 天前
SDN 基本应用实践 —— 使用命令行实现简易防火墙功能实验报告
服务器·php·apache
小小龙学IT23 天前
Apache Pulsar 深度解析:从架构设计到生产落地
apache
Full Stack Developme23 天前
Apache Tika 教程
java·开发语言·python·apache
laplaya24 天前
C++大型项目组件通信与依赖管理实践
c++·log4j·apache