使用Apache POI复制Word段落时如何保留项目符号/编号列表格式?
解决POI复制Word段落时项目符号/编号格式丢失问题
问题根源:直接复制段落的PPr(段落属性)时,段落的编号引用(numPr)指向的是源文档的编号ID,但目标文档中没有对应的编号样式定义,POI会自动 fallback 到默认编号列表,导致项目符号变成编号。
要保留原列表格式,需要两步:
- 将源文档的编号/项目符号定义完整复制到目标文档
- 确保复制的段落引用目标文档中正确的编号ID
实现代码
第一步:复制编号定义到目标文档
private static void copyNumbering(XWPFDocument sourceDoc, XWPFDocument targetDoc) { XWPFNumbering sourceNumbering = sourceDoc.getNumbering(); if (sourceNumbering == null) return; XWPFNumbering targetNumbering = targetDoc.createNumbering(); if (targetNumbering == null) targetNumbering = targetDoc.getNumbering(); // 先复制所有抽象编号(abstractNum) sourceNumbering.getAbstractNums().forEach(sourceAbstractNum -> { CTAbstractNum ctAbstractNum = CTAbstractNum.Factory.newInstance(); ctAbstractNum.set(sourceAbstractNum.getCTAbstractNum()); targetNumbering.addAbstractNum(ctAbstractNum); }); // 再复制所有具体编号(num) sourceNumbering.getNums().forEach(sourceNum -> { CTNum ctNum = CTNum.Factory.newInstance(); ctNum.set(sourceNum.getCTNum()); targetNumbering.addNum(ctNum); }); }
第二步:修改段落复制方法,确保编号引用正确
static void copyParagraph(XWPFParagraph source, XWPFParagraph target, XWPFDocument targetDoc) { // 复制段落属性 if (source.getCTP().getPPr() != null) { CTPPr sourcePPr = source.getCTP().getPPr(); CTPPr targetPPr = target.getCTP().getPPr(); if (targetPPr == null) { targetPPr = target.getCTP().addNewPPr(); } targetPPr.set(sourcePPr); // 处理编号引用:确保numId对应目标文档中的定义 if (sourcePPr.getNumPr() != null && sourcePPr.getNumPr().getNumId() != null) { String sourceNumId = sourcePPr.getNumPr().getNumId().getStringValue(); targetPPr.getNumPr().getNumId().setStringValue(sourceNumId); } } // 复制文本Run和图片 source.getRuns().forEach(run -> { XWPFRun newR = target.createRun(); if (run.getText(0) != null) { newR.setText(run.getText(0)); } if (run.getCTR().getRPr() != null) { newR.getCTR().setRPr(run.getCTR().getRPr()); } if (!run.getEmbeddedPictures().isEmpty()) { XWPFPicture picture = run.getEmbeddedPictures().get(0); try { // 直接用字节流处理图片,无需保存到本地 byte[] picData = picture.getPictureData().getData(); newR.addPicture(new ByteArrayInputStream(picData), picture.getPictureData().getPictureType(), picture.getPictureData().getFileName(), Units.toEMU((int) picture.getWidth()), Units.toEMU((int) picture.getDepth())); } catch (IOException | InvalidFormatException e) { throw new RuntimeException(e); } } }); }
使用示例
public static void main(String[] args) throws IOException { try (XWPFDocument sourceDoc = new XWPFDocument(new FileInputStream("source.docx")); XWPFDocument targetDoc = new XWPFDocument()) { // 先复制编号定义,这是保留列表格式的关键 copyNumbering(sourceDoc, targetDoc); // 逐段落复制内容 sourceDoc.getParagraphs().forEach(sourcePara -> { XWPFParagraph targetPara = targetDoc.createParagraph(); copyParagraph(sourcePara, targetPara, targetDoc); }); // 保存目标文档 try (FileOutputStream out = new FileOutputStream("target.docx")) { targetDoc.write(out); } } }
关键说明
- Word的编号/项目符号存储在文档的
Numbering.xml中,包含**抽象编号(abstractNum)和具体编号(num)**两层结构,必须完整复制才能保留原样式。 - 若跳过编号定义复制,段落的编号ID在目标文档中无匹配项,POI会自动使用默认编号列表,导致格式错乱。
- 图片处理部分优化为直接读取字节流,避免不必要的本地文件IO操作。
内容的提问来源于stack exchange,提问作者Dhruvin Moradiya
相关产品推荐
相关产品推荐

