xlsx/malformed-missing-rel

An xlsx whose sheet1.xml carries <drawing r:id='rId999'/> referencing a relationship id that does not exist in the sheet's _rels sidecar. python-xlsx must still load the workbook and expose cell values; only dereferencing the drawing's target is allowed to fail.

Library verdicts: python-xlsx: — xlsxjs: —

Metadata

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

XPath assertions

ID / partPredicateXPathpython-xlsxxlsxjs
dangling-drawing-present
xl/worksheets/sheet1.xml
exist
Confirms sheet1.xml references rId999 via <drawing/>. python-xlsx's minimal sheet has no _rels sidecar at all, so the 'no matching relationship' condition is trivially satisfied — we do not emit a separate absent-assertion because the runner would error on the missing part.
//*[local-name()='drawing'][@r:id='rId999']

Render assertions

No render assertions declared.

Generator source

scripts/gen_malformed_missing_rel_xlsx.py

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

Known-good one-sheet workbook with ``<drawing r:id="rId999"/>``
appended just inside ``</worksheet>`` on ``xl/worksheets/sheet1.xml``.
No ``rId999`` entry exists in ``xl/worksheets/_rels/sheet1.xml.rels``.
python-xlsx must still load the workbook and expose cell values
without trying to dereference the dangling drawing relationship.
"""

from __future__ import annotations

import datetime as _dt
import sys
import tempfile
from pathlib import Path

from xlsx import Workbook

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

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

_DRAWING_REF = (
    b'<drawing xmlns:r="http://schemas.openxmlformats.org/'
    b'officeDocument/2006/relationships" r:id="rId999"/>'
)


def _inject(name: str, data: bytes) -> bytes:
    if name != "xl/worksheets/sheet1.xml":
        return data
    # Insert the dangling <drawing/> just before </worksheet>.
    closing = b"</worksheet>"
    idx = data.rfind(closing)
    if idx < 0:
        raise RuntimeError("could not locate </worksheet> in sheet1.xml")
    return data[:idx] + _DRAWING_REF + data[idx:]


def main() -> int:
    _OUT.parent.mkdir(parents=True, exist_ok=True)
    with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp:
        good = Path(tmp.name)
    try:
        wb = Workbook()
        # Fix the core-properties timestamps so every regeneration of
        # the fixture hashes identically. python-xlsx otherwise stamps
        # wall-clock time into docProps/core.xml.
        wb.properties.created = _dt.datetime(1980, 1, 1, 0, 0, 0)
        wb.properties.modified = _dt.datetime(1980, 1, 1, 0, 0, 0)
        ws = wb.active
        ws["A1"] = "dangling-rel sheet"
        ws["A2"] = "second row"
        wb.save(good, zip_date_time=_dt.datetime(1980, 1, 1, 0, 0, 0))
        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": "xlsx/malformed-missing-rel",
  "title": "Malformed input: dangling relationship reference",
  "format": "xlsx",
  "category": "malformed-recovery",
  "summary": "An xlsx whose sheet1.xml carries <drawing r:id='rId999'/> referencing a relationship id that does not exist in the sheet's _rels sidecar. python-xlsx must still load the workbook and expose cell values; only dereferencing the drawing's target is allowed to fail.",
  "roles": [
    "authoring"
  ],
  "spec": {
    "source": "ecma-376-5-part-2",
    "clause": "9.2",
    "element": "pkg:Relationship",
    "notes": "Same OPC packaging rule as docx/malformed-missing-rel. A dangling drawing rel forces the reader to choose between refusing the workbook or loading cells while treating the drawing as absent; the latter is the interoperable default."
  },
  "fixtures": {
    "machine": "xlsx/malformed-missing-rel"
  },
  "generator": {
    "python": "scripts/gen_malformed_missing_rel_xlsx.py"
  },
  "assertions": [
    {
      "id": "dangling-drawing-present",
      "part": "xl/worksheets/sheet1.xml",
      "namespaces": {
        "x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
        "r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
      },
      "xpath": "//*[local-name()='drawing'][@r:id='rId999']",
      "must": "exist",
      "description": "Confirms sheet1.xml references rId999 via <drawing/>. python-xlsx's minimal sheet has no _rels sidecar at all, so the 'no matching relationship' condition is trivially satisfied — we do not emit a separate absent-assertion because the runner would error on the missing part."
    }
  ],
  "behavior_assertions": [
    {
      "id": "default-load-succeeds",
      "kind": "library_loads",
      "library": "python-xlsx",
      "method": "xlsx.load_workbook",
      "args": [
        "{fixture_path}"
      ],
      "description": "python-xlsx tolerates dangling relationships at load time; the call must return without raising."
    }
  ]
}

Fixture

Download malformed-missing-rel.xlsx (4.8 KB)

Reference preview

No rendered reference is available for this case.

Spec notes

Same OPC packaging rule as docx/malformed-missing-rel. A dangling drawing rel forces the reader to choose between refusing the workbook or loading cells while treating the drawing as absent; the latter is the interoperable default.