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

如何使用POI读取Word文档中自动生成的标题编号?

嘿,这个问题我之前帮不少开发者踩过坑!Word里的自动编号可不是标题文本的一部分哦——它属于段落的「编号格式属性」,所以直接读段落内容肯定拿不到。下面分两种常用的Word格式(.docx和旧版.doc)给你具体的实现方案:

处理.docx格式(XWPF)

对于现代.docx文件,我们需要通过XWPFParagraph的编号属性(NumPr)来获取编号信息,再结合文档的编号定义解析出完整的编号值:

import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import java.io.FileInputStream;
import java.io.IOException;

public class ReadDocxHeadingNumber {
    public static void main(String[] args) throws IOException {
        // 替换为你的docx文件路径
        try (XWPFDocument doc = new XWPFDocument(new FileInputStream("your-document.docx"))) {
            for (XWPFParagraph para : doc.getParagraphs()) {
                // 判断当前段落是否为标题(根据样式名判断)
                if (para.getStyle() != null && para.getStyle().startsWith("Heading")) {
                    String headingText = para.getText().trim();
                    String headingNumber = extractHeadingNumber(para, doc);
                    System.out.println(headingNumber + " " + headingText);
                }
            }
        }
    }

    private static String extractHeadingNumber(XWPFParagraph para, XWPFDocument doc) {
        CTP ctp = para.getCTP();
        CTNumPr numPr = ctp.getNumPr();
        
        // 如果段落没有设置自动编号,返回空字符串
        if (numPr == null) return "";

        CTNumId numId = numPr.getNumId();
        CTILvl ilvl = numPr.getIlvl();
        if (numId == null || ilvl == null) return "";

        XWPFNumbering numbering = doc.getNumbering();
        XWPFNum num = numbering.getNum(numId.getVal());
        if (num == null) return "";

        // 获取编号的抽象定义和对应级别
        XWPFAbstractNum abstractNum = numbering.getAbstractNum(num.getAbstractNumId());
        XWPFNumLevel numLevel = abstractNum.getNumLevel(ilvl.getVal().intValue());
        if (numLevel == null) return "";

        // 构建多级编号(这里简化处理阿拉伯数字格式,如需其他格式可扩展)
        StringBuilder numberBuilder = new StringBuilder();
        int currentLevel = ilvl.getVal().intValue();
        // 递归获取父级编号
        while (currentLevel >= 0) {
            XWPFNumLevel level = abstractNum.getNumLevel(currentLevel);
            if (level == null) break;
            // 获取当前级别的编号值(POI中编号计数从1开始)
            int numValue = para.getNumID().getVal().intValue();
            numberBuilder.insert(0, numValue + (currentLevel > 0 ? "." : ""));
            currentLevel--;
        }

        return numberBuilder.toString();
    }
}

注意事项

  • 上面的示例仅处理了阿拉伯数字的多级编号,如果你的文档用了罗马数字、字母等格式,需要额外添加格式转换逻辑(比如把数字转成罗马数字/字母)。
  • 标题的判断是通过样式名开头为Heading,如果你的文档自定义了标题样式,需要调整判断逻辑。

处理旧版.doc格式(HWPF)

对于旧版.doc文件,使用HWPF API的处理逻辑略有不同,核心同样是读取段落的编号属性:

import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.*;

import java.io.FileInputStream;
import java.io.IOException;

public class ReadDocHeadingNumber {
    public static void main(String[] args) throws IOException {
        // 替换为你的doc文件路径
        try (HWPFDocument doc = new HWPFDocument(new FileInputStream("your-document.doc"))) {
            Range range = doc.getRange();
            for (int i = 0; i < range.numParagraphs(); i++) {
                Paragraph para = range.getParagraph(i);
                // 判断是否为标题段落
                if (para.getStyleIndex() != -1) {
                    String styleName = doc.getStyleSheet().getStyle(para.getStyleIndex()).getName();
                    if (styleName.startsWith("Heading")) {
                        String headingText = para.text().trim();
                        String headingNumber = extractOldDocHeadingNumber(para, doc);
                        System.out.println(headingNumber + " " + headingText);
                    }
                }
            }
        }
    }

    private static String extractOldDocHeadingNumber(Paragraph para, HWPFDocument doc) {
        int numId = para.getNumId();
        int numLevel = para.getNumLevel();
        
        // 无编号则返回空
        if (numId == -1 || numLevel == -1) return "";

        Numbering numbering = doc.getNumbering();
        NumberingLevel level = numbering.getLevel(numId, numLevel);
        if (level == null) return "";

        // 构建多级编号(简化处理,实际需根据格式扩展)
        StringBuilder numberBuilder = new StringBuilder();
        int currentLevel = numLevel;
        while (currentLevel >= 0) {
            NumberingLevel currentLvl = numbering.getLevel(numId, currentLevel);
            if (currentLvl == null) break;
            // 获取当前段落的编号值
            int numValue = para.getNum();
            numberBuilder.insert(0, numValue + (currentLevel > 0 ? "." : ""));
            currentLevel--;
        }

        return numberBuilder.toString();
    }
}

注意事项

  • 旧版.doc的编号层级关联更复杂,上面的示例是简化版,如果需要精确获取父级编号值,可能需要遍历段落树来追踪层级关系。
  • HWPF对复杂格式的支持不如XWPF,如果文档有特殊编号格式,可能需要额外的解析逻辑。

核心思路总结

Word的自动编号本质是段落的格式元数据,而非文本内容的一部分,所以必须通过POI提供的编号属性API来读取,而不是直接从段落文本中提取。根据文档格式选择对应的API(XWPF/HWPF),再结合编号定义解析出完整的编号值即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 04:21:39