docx/malformed-missing-rel

A docx whose document.xml references a relationship id (rId999) that does not exist in word/_rels/document.xml.rels. The library must still load the package without raising, and the dangling link's run text must remain readable; only dereferencing the link's target is allowed to fail.

Library verdicts: python-docx: — docxjs: —

Metadata

Feature iddocx/malformed-missing-rel
Formatdocx
Categorymalformed-recovery
Spececma-376-5-part-2 § 9.2 pkg:Relationship

XPath assertions

ID / partPredicateXPathpython-docxdocxjs
dangling-link-text-present
word/document.xml
exist
The fixture's body carries a <w:hyperlink r:id='rId999'> whose inner run text is 'dangling link target' — confirms the corruption is in place.
//w:hyperlink[@r:id='rId999']/w:r/w:t[normalize-space()='dangling link target']
no-matching-relationship
word/_rels/document.xml.rels
absent
The relationships part must NOT carry a matching rId999 entry — otherwise the fixture isn't actually malformed.
//rel:Relationship[@Id='rId999']

Render assertions

No render assertions declared.

Generator source

scripts/gen_malformed_missing_rel_docx.py

#!/usr/bin/env python3
"""Generate ``fixtures/docx/malformed-missing-rel.docx``.

Known-good docx with one hyperlink injected into the body referencing
``rId999`` — a relationship id that is NOT present in
``word/_rels/document.xml.rels``. python-docx must still load the
package (the hyperlink's run text remains readable), and the caller
must fail with a ``KeyError`` only if it asks for the hyperlink's
address — NOT at Document construction.
"""

from __future__ import annotations

import sys
import tempfile
from pathlib import Path

from docx import Document

sys.path.insert(0, str(Path(__file__).resolve().parent))
from _malformed_common import REPRODUCIBLE_DT, rewrite_zip

_REPO_ROOT = Path(__file__).resolve().parent.parent
_OUT = _REPO_ROOT / "fixtures" / "docx" / "malformed-missing-rel.docx"

_HYPERLINK_INJECT = (
    b'<w:p><w:hyperlink r:id="rId999"><w:r><w:t>'
    b"dangling link target</w:t></w:r></w:hyperlink></w:p>"
)


def _inject(name: str, data: bytes) -> bytes:
    if name != "word/document.xml":
        return data
    # Inject the dangling hyperlink paragraph at the start of the body.
    # Use the opening <w:body> tag as an anchor — every python-docx
    # generated document writes it on one line with no attributes.
    marker = b"<w:body>"
    idx = data.find(marker)
    if idx < 0:
        raise RuntimeError("could not locate <w:body> in document.xml")
    insert_at = idx + len(marker)
    return data[:insert_at] + _HYPERLINK_INJECT + data[insert_at:]


def main() -> int:
    _OUT.parent.mkdir(parents=True, exist_ok=True)
    with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as tmp:
        good = Path(tmp.name)
    try:
        doc = Document()
        doc.add_paragraph("anchor paragraph (keeps document non-empty)")
        doc.save(good, reproducible=True)
        rewrite_zip(good, _OUT, edit=_inject)
    finally:
        good.unlink(missing_ok=True)
    print(_OUT)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Manifest (expanded)

{
  "id": "docx/malformed-missing-rel",
  "title": "Malformed input: dangling relationship reference",
  "format": "docx",
  "category": "malformed-recovery",
  "summary": "A docx whose document.xml references a relationship id (rId999) that does not exist in word/_rels/document.xml.rels. The library must still load the package without raising, and the dangling link's run text must remain readable; only dereferencing the link's target is allowed to fail.",
  "roles": [
    "authoring"
  ],
  "spec": {
    "source": "ecma-376-5-part-2",
    "clause": "9.2",
    "element": "pkg:Relationship",
    "notes": "OPC packaging rules require every rel referenced from a part to resolve to a Relationship element inside the part's _rels sidecar. A dangling rel is a spec violation; a tolerant reader loads the package and fails lazily on the specific lookup rather than refusing the whole document."
  },
  "fixtures": {
    "machine": "docx/malformed-missing-rel"
  },
  "generator": {
    "python": "scripts/gen_malformed_missing_rel_docx.py"
  },
  "assertions": [
    {
      "id": "dangling-link-text-present",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
        "r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
      },
      "xpath": "//w:hyperlink[@r:id='rId999']/w:r/w:t[normalize-space()='dangling link target']",
      "must": "exist",
      "description": "The fixture's body carries a <w:hyperlink r:id='rId999'> whose inner run text is 'dangling link target' — confirms the corruption is in place."
    },
    {
      "id": "no-matching-relationship",
      "part": "word/_rels/document.xml.rels",
      "namespaces": {
        "rel": "http://schemas.openxmlformats.org/package/2006/relationships"
      },
      "xpath": "//rel:Relationship[@Id='rId999']",
      "must": "absent",
      "description": "The relationships part must NOT carry a matching rId999 entry — otherwise the fixture isn't actually malformed."
    }
  ],
  "behavior_assertions": [
    {
      "id": "default-load-succeeds",
      "kind": "library_loads",
      "library": "python-docx",
      "method": "docx.Document",
      "args": [
        "{fixture_path}"
      ],
      "description": "python-docx tolerates dangling relationships at load time. The call must return without raising."
    },
    {
      "id": "recover-load-succeeds",
      "kind": "library_loads",
      "library": "python-docx",
      "method": "docx.Document",
      "args": [
        "{fixture_path}"
      ],
      "kwargs": {
        "recover": true
      },
      "description": "recover=True must also succeed, returning a Document whose paragraphs are iterable and whose dangling-link run text is preserved."
    }
  ]
}

Fixture

Download malformed-missing-rel.docx (18.6 KB)

Reference preview

No rendered reference is available for this case.

Spec notes

OPC packaging rules require every rel referenced from a part to resolve to a Relationship element inside the part's _rels sidecar. A dangling rel is a spec violation; a tolerant reader loads the package and fails lazily on the specific lookup rather than refusing the whole document.