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

使用Apache POI复制Word段落时如何保留项目符号/编号列表格式?

解决POI复制Word段落时项目符号/编号格式丢失问题

问题根源:直接复制段落的PPr(段落属性)时,段落的编号引用(numPr)指向的是源文档的编号ID,但目标文档中没有对应的编号样式定义,POI会自动 fallback 到默认编号列表,导致项目符号变成编号。

要保留原列表格式,需要两步:

  1. 将源文档的编号/项目符号定义完整复制到目标文档
  2. 确保复制的段落引用目标文档中正确的编号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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 19:27:22