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: —
| Feature id | xlsx/malformed-missing-rel |
|---|---|
| Format | xlsx |
| Category | malformed-recovery |
| Spec | ecma-376-5-part-2 § 9.2 pkg:Relationship |
| ID / part | Predicate | XPath | python-xlsx | xlsxjs |
|---|---|---|---|---|
dangling-drawing-presentxl/worksheets/sheet1.xml | existConfirms 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'] | — | — |
No render assertions declared.
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())
{
"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."
}
]
}
No rendered reference is available for this case.