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

Word文档XML中多级列表编号文本缺失问题及查找方法咨询

解析Word XML时丢失多级列表编号的原因及解决方法

我用Python的xml.etree.ElementTree模块解析Word文档的XML内容,通过以下代码提取document.xml并保存为TXT文件:

from tkinter import filedialog
import zipfile
import xml.etree.ElementTree as ET

file_path = filedialog.askopenfilename()

def get_export_raw_xml(doc_filename):
    raw_xml = zipfile.ZipFile(doc_filename).read('word/document.xml')
    temp_root = ET.fromstring(raw_xml)
    FileXML = open("TempDocXML.txt", "w")
    FileXML.write(str(ET.tostring(temp_root)))
    FileXML.close() 

get_export_raw_xml(file_path)

提取后的XML里只能看到多级列表的标题内容(如“GENERAL”“First Section”),找不到定义的多级列表编号文本(如“CHAPTER 1—”“Section 1.01”)。


原因

Word的多级列表编号并非直接存储在document.xml的正文内容里,而是单独存放在word/numbering.xml文件中。document.xml里的标题段落只会引用对应的列表编号ID和级别,不会直接包含编号文本。Word渲染文档时,会根据numbering.xml里的规则和段落的引用信息,动态生成实际显示的编号文本。

解决方法

要获取完整的编号文本,需要同时解析numbering.xml和document.xml,步骤如下:

  • 从Word压缩包中提取numbering.xml,解析出所有列表编号的规则(包括前缀、编号格式等);
  • 在document.xml中找到每个标题段落对应的列表ID和级别,匹配numbering.xml中的对应规则;
  • 根据规则拼接编号文本和标题内容。

修改后的代码示例

from tkinter import filedialog
import zipfile
import xml.etree.ElementTree as ET

# Word XML的命名空间,必须指定才能正确查找元素
NS = {
    'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
}

file_path = filedialog.askopenfilename()

def parse_numbering_rules(zip_file):
    # 解析numbering.xml,返回编号规则字典:(编号ID, 级别) -> 编号前缀/格式
    numbering_content = zip_file.read('word/numbering.xml')
    root = ET.fromstring(numbering_content)
    num_rules = {}

    # 遍历每个编号定义
    for num_node in root.findall('.//w:num', NS):
        num_id = num_node.get(f'{{{NS["w"]}}}numId')
        # 获取关联的抽象编号ID
        abstract_num_id = num_node.find('.//w:abstractNumId', NS).get(f'{{{NS["w"]}}}val')
        abstract_num_node = root.find(f'.//w:abstractNum[@w:abstractNumId="{abstract_num_id}"]', NS)

        # 遍历每个级别的规则
        for level_node in abstract_num_node.findall('.//w:lvl', NS):
            level = level_node.get(f'{{{NS["w"]}}}ilvl')
            # 获取编号文本模板(比如"CHAPTER %1—")
            level_text = level_node.find('.//w:lvlText', NS)
            if level_text is not None:
                num_template = level_text.get(f'{{{NS["w"]}}}val')
                num_rules[(num_id, level)] = num_template

    return num_rules

def export_doc_with_numbers(doc_filename):
    with zipfile.ZipFile(doc_filename) as zf:
        # 先解析编号规则
        num_rules = parse_numbering_rules(zf)
        # 解析document.xml
        doc_content = zf.read('word/document.xml')
        doc_root = ET.fromstring(doc_content)

        # 保存带编号的文本
        with open("DocWithNumbers.txt", "w", encoding='utf-8') as output_file:
            # 简单维护编号计数器,复杂多级编号需调整层级重置逻辑
            num_counter = {}
            for para in doc_root.findall('.//w:p', NS):
                # 检查段落是否属于列表项
                num_pr = para.find('.//w:numPr', NS)
                if num_pr is not None:
                    num_id = num_pr.find('.//w:numId', NS).get(f'{{{NS["w"]}}}val')
                    level = num_pr.find('.//w:ilvl', NS).get(f'{{{NS["w"]}}}val')
                    # 获取对应的编号模板
                    num_template = num_rules.get((num_id, level), '')
                    # 更新计数器
                    key = f"{num_id}_{level}"
                    num_counter[key] = num_counter.get(key, 0) + 1
                    # 替换模板中的%1为当前编号值
                    number_text = num_template.replace('%1', str(num_counter[key]))
                    # 提取段落文本
                    para_text = ''.join([t.text for t in para.findall('.//w:t', NS) if t.text])
                    if number_text and para_text:
                        output_file.write(f"{number_text} {para_text}\n")
                    else:
                        output_file.write(f"{para_text}\n")
                else:
                    # 普通段落直接写入文本
                    para_text = ''.join([t.text for t in para.findall('.//w:t', NS) if t.text])
                    if para_text:
                        output_file.write(f"{para_text}\n")

export_doc_with_numbers(file_path)

代码关键点说明

  • 必须指定Word XML的命名空间,否则无法正确定位带w:前缀的元素;
  • parse_numbering_rules函数从numbering.xml中提取每个编号ID和级别对应的编号模板;
  • 示例用简单计数器处理编号递增,若要支持1.1、1.2这类嵌套多级编号,需根据层级关系调整计数器的重置逻辑;
  • 最终将编号文本和标题内容拼接后写入TXT文件,就能得到完整的带编号的标题内容。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 17:57:12