[解决方案] Word转PDF

背景:

之前做过一些pdf导出, 客户提了一个特别急的需求, 要求根据一个模版跟一个csv的数据源, 批量生成PDF, 因为之前用过FOP, 知道调整样式需要特别长的时间, 这个需求又特别急, 所以寻找了一个其他的方案。

优点:

生成快捷,代码简单, 样式依赖模版,所见即所得

缺点:

模版难以调整

思路:

既然已经放弃FOP,那么就直接从模版生成新的word文档, 并且将word文档直接导出

第一版思路:

复制代码
<dependency>
      <groupId>org.docx4j</groupId>
      <artifactId>docx4j-core</artifactId>
      <version>8.3.9</version>
    </dependency> <!-- :contentReference[oaicite:0]{index=0} -->

    <!-- 内置 MOXy JAXB 实现 -->
    <dependency>
      <groupId>org.docx4j</groupId>
      <artifactId>docx4j-JAXB-MOXy</artifactId>
      <version>8.3.9</version>
    </dependency> <!-- :contentReference[oaicite:1]{index=1} -->

<!--    &lt;!&ndash; FO 导出,用于生成 XSL-FO &ndash;&gt;-->
    <dependency>
      <groupId>org.docx4j</groupId>
      <artifactId>docx4j-export-fo</artifactId>
      <version>8.3.9</version>
    </dependency> <!-- :contentReference[oaicite:2]{index=2} -->


public static void main(String[] args) throws Exception {

        // 1. 加载模板
        InputStream tpl = Word2PDF.class
                .getResourceAsStream("/template.docx");
        if (tpl == null) {
            throw new RuntimeException("未找到模板 template.docx");
        }

        //这部分非必须, 是为了多次导出,不重复读模版
        byte[] template = tpl.readAllBytes();

        // 2. 准备多条替换数据
        List<Map<String,String>> dataList = new ArrayList<>();
        Map<String,String> maps = new HashMap<>();

        maps.put("firstName","Alice");
        maps.put("context","测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测");
        maps.put("lastName","Wang");
        maps.put("date","2025-05-20");
        dataList.add(maps);
        maps = new HashMap<>();
        maps.put("firstName","Bob");
        maps.put("lastName","Li");
        maps.put("date","2025-05-21");

        dataList.add(maps);
        maps = new HashMap<>();
        maps.put("firstName","Carol");
        maps.put("lastName","Zhang");
        maps.put("date","2025-05-22");
        dataList.add(maps);


        // 3. 循环生成
        for (Map<String,String> row : dataList) {
            // 3.1 重新加载模板
            WordprocessingMLPackage pkg;
            try (InputStream tplStream = new ByteArrayInputStream(template)) {
                pkg = WordprocessingMLPackage.load(tplStream);
            }

            // 3.2 执行替换 (${key})
            MainDocumentPart mdp = pkg.getMainDocumentPart();
            mdp.variableReplace(row);
            // 替换 ${firstName}、${lastName}、${date} :contentReference[oaicite:2]{index=2}

            // 3.3 保存为 DOCX
            String name = row.get("firstName");
            String docxPath = "/Users/Documents/" + name + ".docx";
            pkg.save(new File(docxPath));

            try(OutputStream os = new FileOutputStream("/Users/Documents/" + name + ".pdf"))  {
                Docx4J.toPDF(pkg, os);
            }
        }

    }

这种方式全部依赖docx4j的jar包,进行导出。

缺点, 当模版有复杂模型,比如侧边栏时这种方式是无法导出的, 在网上找到的解决方案也是无效的。可能是因为JDK版本的升级。

版本2:

上面代码的逻辑一样,额外使用了documents4j的jar

复制代码
<dependency>
      <groupId>org.docx4j</groupId>
      <artifactId>docx4j-core</artifactId>
      <version>8.3.9</version>
    </dependency> <!-- :contentReference[oaicite:0]{index=0} -->

    <!-- 内置 MOXy JAXB 实现 -->
    <dependency>
      <groupId>org.docx4j</groupId>
      <artifactId>docx4j-JAXB-MOXy</artifactId>
      <version>8.3.9</version>
    </dependency> <!-- :contentReference[oaicite:1]{index=1} -->

<!--    &lt;!&ndash; FO 导出,用于生成 XSL-FO &ndash;&gt;-->
    <dependency>
      <groupId>org.docx4j</groupId>
      <artifactId>docx4j-export-fo</artifactId>
      <version>8.3.9</version>
    </dependency> <!-- :contentReference[oaicite:2]{index=2} -->

//转化为PDF的代码使用

//声明转换器,可重用
IConverter converter = LocalConverter.builder()
                        .baseFolder(new File(targetPath))
                        .workerPool(5,15,30, TimeUnit.SECONDS)
                        .processTimeout (60, TimeUnit.SECONDS)
                        .build();
//声明 转换, 最后一步有schedule excute 两种写法, excute是直接生成,结果是boolean,是单条生成的,这种是为了批量运行
Future<Boolean> future = converter
                        .convert(word)
                        .as(DocumentType.MS_WORD)
                        .to(new File(wordName + ".pdf"))
                        .as(DocumentType.PDF)
                        .schedule();


//对应 schedule的运行
future.get();

这种方式可以达成所见即所得。

PS:

之前提出了模版难以修改,是因为模版中要使用{替换名称}的方式, 但是word有时会自动截断一个字符串, 导致实际上变成了{替 换名称 }的样式, 需要多改几次试下,连续输入试一下。

有一种比较简单的方式,就是将word文件的后缀名改成zip ,然后拿出document.xml 可以在这个里面直接改,名称改回后记得打开看是否报错, 如果报错,另存一下,就可以去掉报错。

相关推荐
xiaopengbc6 小时前
安卓手机格式转换,支持PDF转Word、PDF转Excel、PDF转PPT、PDT转图片
pdf·powerpoint
java技术之路1 天前
【免费AI文档助手开发实战系列】基于正则表达式的PDF脱敏python服务构建(一)
python·pdf·pymupdf·免费pdf脱敏
耘田2 天前
Markdown to PDF/PNG Converter
pdf
AirDroid_cn3 天前
PDF转图片需要用到什么技术?苹果手机怎样将PDF转为jpg?
pdf·iphone·ipad·快捷指令
拓端研究室3 天前
专题:2025全球消费趋势与中国市场洞察报告|附300+份报告PDF、原数据表汇总下载
大数据·信息可视化·pdf
Kyln.Wu5 天前
【python实用小脚本-190】Python一键删除PDF任意页:输入页码秒出干净文件——再也不用在线裁剪排队
服务器·python·pdf
阿幸软件杂货间5 天前
免费万能电子书格式转换器!Neat Converter支持 ePub、Azw3、Mobi、Doc、PDF、TXT 文件的相互转换。
pdf·格式转换
星马梦缘6 天前
CSDN转PDF【无水印且免费!!!】
pdf·免费·pandoc·转pdf·无水印·csdn转pdf·wkhtmlpdf
画月的亮6 天前
前端处理导出PDF。Vue导出pdf
前端·vue.js·pdf
伊织code7 天前
pdfminer.six
python·pdf·图片·提取·文本·pdfminer·pdfminer.six