使用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:
- Word文档中编号列表通过
w:numPr/w:ilvl和w:numPr/w:numId关联编号定义,export-FO生成FO格式时,错误地将列表的最终编号值提前渲染到第一个列表项位置,导致叠加。 - 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编号列表的处理更准确:
- 添加依赖:
<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>
- 修改转换代码:
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
相关产品推荐
相关产品推荐

