python-docx KeyError: NULL

  1. 报错
  2. 清理关系
  3. 结果
  4. 参考

报错

Traceback (most recent call last):
  File "read_docx.py", line 27, in <module>
    document = docx.Document(docx_file)
  File "docx/api.py", line 30, in Document
    document_part = cast("DocumentPart", Package.open(docx).main_document_part)
  File "docx/opc/package.py", line 126, in open
    pkg_reader = PackageReader.from_file(pkg_file)
  File "docx/opc/pkgreader.py", line 96, in _walk_phys_parts
    blob = phys_reader.blob_for(partname)
  File "zipfile.py", line 1484, in getinfo
    raise KeyError(
KeyError: "There is no item named 'NULL' in the archive"

DOCX 是 ZIP 格式的 Open XML 包。异常文件的某个 *.rels 中存在无效关系:

<Relationship
  Id="rId8"
  Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
  Target="../NULL"/>

python-docx 解析关系后尝试从压缩包读取 NULL,最终由 zipfile 抛出 KeyError

关系文件位置可以直接列出:

unzip -Z1 broken.docx | rg '\.rels$'

搜索异常 Target:

for rels in $(unzip -Z1 broken.docx | rg '\.rels$'); do
  unzip -p broken.docx "$rels" | rg -n 'Target="(\.\./)?NULL"'
done

清理关系

不修改原文件,清理后的文档写入新路径:

from pathlib import Path
from xml.etree import ElementTree
from zipfile import ZipFile


INVALID_TARGETS = {"NULL", "../NULL"}


def remove_null_relationships(xml_data: bytes):
    root = ElementTree.fromstring(xml_data)
    removed = []

    for relationship in list(root):
        target = relationship.get("Target", "")
        if target in INVALID_TARGETS:
            removed.append((relationship.get("Id", ""), target))
            root.remove(relationship)

    if not removed:
        return xml_data, removed

    repaired = ElementTree.tostring(
        root,
        encoding="utf-8",
        xml_declaration=True,
    )
    return repaired, removed


def repair_docx(source: Path, destination: Path):
    removed = []

    with ZipFile(source) as source_zip, ZipFile(destination, "w") as dest_zip:
        for item in source_zip.infolist():
            data = source_zip.read(item.filename)
            if item.filename.endswith(".rels"):
                data, current = remove_null_relationships(data)
                removed.extend(
                    (item.filename, relation_id, target)
                    for relation_id, target in current
                )
            dest_zip.writestr(item, data)

    return removed


source = Path("broken.docx")
destination = Path("fixed.docx")

for filename, relation_id, target in repair_docx(source, destination):
    print(filename, relation_id, target)

结果

使用包含 Target="../NULL" 的测试文档复现:

before: "There is no item named 'NULL' in the archive"
removed: word/_rels/document.xml.rels rIdNull ../NULL
after: test

清理后 python-docx 1.2.0 可以正常读取 fixed.docx。原始 broken.docx 保持不变。

参考

python-docx issue #1105


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。

文章标题:python-docx KeyError: NULL

字数:410

本文作者:

发布时间:2023-08-01, 01:24:00

最后更新:2026-08-26, 14:57:00

原始链接:https://cnlnn.pages.dev/posts/python-docx-null-relationship/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。