You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用docx4j export-FO转Docx到Pdf时编号列表出现叠加异常

问题描述

我需要在不修改内容的前提下,将Docx文档直接转换为PDF。由于Microsoft Graph和documents4j方案不符合需求,采用了docx4j的export-FO方式转换,但转换后的PDF中编号列表出现异常:列表的首个编号被该列表最后一个编号加1的数字叠加覆盖。

转换代码

public class Main {
    public static void main(String[] args) throws Exception {
        InputStream templateInputStream = new FileInputStream("document.docx");
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(templateInputStream);

        Mapper fontMapper = new BestMatchingMapper();
        wordMLPackage.setFontMapper(fontMapper);

        OutputStream os = new FileOutputStream("document.pdf");
        Docx4J.toPDF(wordMLPackage, os);
    }
}

项目依赖

<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-core</artifactId>
    <version>11.5.2</version>
</dependency>

<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-export-fo</artifactId>
    <version>11.5.2</version>
</dependency>

<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
    <version>11.5.2</version>
</dependency>

<dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>fop</artifactId>
    <version>2.10</version>
</dependency>

补充信息

  • 存在对应问题截图,直观显示编号叠加异常
  • 有可测试的源Docx文档用于复现问题

问题分析与解决方案

成因

该异常是docx4j的export-FO模块处理Word编号列表时的逻辑bug:

  1. Word文档中编号列表通过w:numPr/w:ilvl和w:numPr/w:numId关联编号定义,export-FO生成FO格式时,错误地将列表的最终编号值提前渲染到第一个列表项位置,导致叠加。
  2. FO生成器处理编号序列计数器初始化时,未正确重置计数器,而是沿用了前一个列表或文档末尾的编号状态,最终造成第一个项被后续最终编号覆盖。

解决方案

方案1:升级docx4j版本

docx4j在12.0.0及以上版本中修复了多个export-FO的编号处理bug,尝试升级到最新稳定版:

<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-core</artifactId>
    <version>12.6.0</version>
</dependency>

<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-export-fo</artifactId>
    <version>12.6.0</version>
</dependency>

<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
    <version>12.6.0</version>
</dependency>

注意:升级后需同步将fop版本调整为兼容的最新版(如2.9或3.0),避免依赖冲突。

方案2:手动重置编号计数器(临时修复)

若无法升级版本,可在转换前手动遍历文档列表项,强制重置每个列表的起始编号:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.List;
import java.util.stream.Collectors;
import org.docx4j.model.fonts.Mapper;
import org.docx4j.model.fonts.BestMatchingMapper;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.NumPr;
import org.docx4j.wml.NumStart;
import org.docx4j.wml.P;

public class Main {
    public static void main(String[] args) throws Exception {
        InputStream templateInputStream = new FileInputStream("document.docx");
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(templateInputStream);

        // 遍历所有段落,重置编号列表起始值
        List<P> paragraphs = wordMLPackage.getMainDocumentPart().getContent().stream()
                .filter(obj -> obj instanceof P)
                .map(obj -> (P) obj)
                .collect(Collectors.toList());

        for (P p : paragraphs) {
            if (p.getPPr() != null && p.getPPr().getNumPr() != null) {
                NumPr numPr = p.getPPr().getNumPr();
                // 强制每个编号项从1开始计数
                NumStart numStart = new NumStart();
                numStart.setVal(BigInteger.ONE);
                numPr.setNumStart(numStart);
            }
        }

        Mapper fontMapper = new BestMatchingMapper();
        wordMLPackage.setFontMapper(fontMapper);

        OutputStream os = new FileOutputStream("document.pdf");
        Docx4J.toPDF(wordMLPackage, os);
    }
}

该方法通过给每个带编号的段落添加numStart属性,强制FO生成器从1开始计数,避免计数器异常叠加。

方案3:替换为docx4j的export-common转换引擎

如果export-FO的问题无法解决,可尝试使用docx4j推荐的export-common结合PDFBox生成PDF,该引擎对Word编号列表的处理更准确:

  1. 添加依赖:
<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-core</artifactId>
    <version>12.6.0</version>
</dependency>
<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-export-common</artifactId>
    <version>12.6.0</version>
</dependency>
<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-pdfexport</artifactId>
    <version>12.6.0</version>
</dependency>
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.32</version>
</dependency>
  1. 修改转换代码:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import org.docx4j.model.fonts.Mapper;
import org.docx4j.model.fonts.BestMatchingMapper;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.convert.out.pdf.PdfConversion;

public class Main {
    public static void main(String[] args) throws Exception {
        InputStream templateInputStream = new FileInputStream("document.docx");
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(templateInputStream);

        Mapper fontMapper = new BestMatchingMapper();
        wordMLPackage.setFontMapper(fontMapper);

        OutputStream os = new FileOutputStream("document.pdf");
        PdfConversion conversion = new PdfConversion(wordMLPackage);
        conversion.output(os);
    }
}

内容的提问来源于stack exchange,提问作者Anatoly Sokolov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 17:27:05