A pptx whose slide1.xml carries an <a:hlinkClick r:id='rId999'/> referencing a relationship id that does not exist in the slide's _rels sidecar. python-pptx must still load the presentation and expose visible text; only dereferencing the link's target is allowed to fail.
Library verdicts: python-pptx: — pptxjs: —
| Feature id | pptx/malformed-missing-rel |
|---|---|
| Format | pptx |
| Category | malformed-recovery |
| Spec | ecma-376-5-part-2 § 9.2 pkg:Relationship |
| ID / part | Predicate | XPath | python-pptx | pptxjs |
|---|---|---|---|---|
dangling-hlink-presentppt/slides/slide1.xml | existConfirms slide1.xml references rId999. | //a:hlinkClick[@r:id='rId999'] | — | — |
no-matching-relationshipppt/slides/_rels/slide1.xml.rels | absentslide1's _rels must NOT define rId999 — otherwise the fixture isn't malformed. | //rel:Relationship[@Id='rId999'] | — | — |
No render assertions declared.
scripts/gen_malformed_missing_rel_pptx.py
#!/usr/bin/env python3
"""Generate ``fixtures/pptx/malformed-missing-rel.pptx``.
Known-good one-slide presentation with an ``a:hlinkClick`` element
carrying ``r:id="rId999"`` injected into the title-shape's run
properties. No ``rId999`` entry exists in
``ppt/slides/_rels/slide1.xml.rels`` — so the hyperlink's address is
undefined. python-pptx must still load the presentation and expose
the slide's visible text.
"""
from __future__ import annotations
import datetime as _dt
import sys
import tempfile
from pathlib import Path
from pptx import Presentation
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" / "pptx" / "malformed-missing-rel.pptx"
# Inject an `<a:rPr>` element carrying a dangling `<a:hlinkClick/>`
# as the first child of the title run. python-pptx's minimal slide
# template emits `<a:r><a:t>text</a:t></a:r>` with no `<a:rPr>` at
# all; we insert one.
_HLINK_INJECT_PREFIX = (
b"<a:r>"
b'<a:rPr lang="en-US" dirty="0">'
b'<a:hlinkClick '
b'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" '
b'r:id="rId999"/>'
b"</a:rPr>"
)
def _inject(name: str, data: bytes) -> bytes:
if name != "ppt/slides/slide1.xml":
return data
# Replace the opening `<a:r>` of the first run with the same tag
# followed by an `<a:rPr>` carrying the dangling hyperlink.
marker = b"<a:r>"
idx = data.find(marker)
if idx < 0:
raise RuntimeError("could not locate <a:r> in slide1.xml")
return data[:idx] + _HLINK_INJECT_PREFIX + data[idx + len(marker):]
def main() -> int:
_OUT.parent.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile(suffix=".pptx", delete=False) as tmp:
good = Path(tmp.name)
try:
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[5])
slide.shapes.title.text = "dangling-rel slide"
prs.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": "pptx/malformed-missing-rel",
"title": "Malformed input: dangling relationship reference",
"format": "pptx",
"category": "malformed-recovery",
"summary": "A pptx whose slide1.xml carries an <a:hlinkClick r:id='rId999'/> referencing a relationship id that does not exist in the slide's _rels sidecar. python-pptx must still load the presentation and expose visible text; 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": "Same OPC packaging rule as docx/malformed-missing-rel — 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": "pptx/malformed-missing-rel"
},
"generator": {
"python": "scripts/gen_malformed_missing_rel_pptx.py"
},
"assertions": [
{
"id": "dangling-hlink-present",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"a": "http://schemas.openxmlformats.org/drawingml/2006/main",
"r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
},
"xpath": "//a:hlinkClick[@r:id='rId999']",
"must": "exist",
"description": "Confirms slide1.xml references rId999."
},
{
"id": "no-matching-relationship",
"part": "ppt/slides/_rels/slide1.xml.rels",
"namespaces": {
"rel": "http://schemas.openxmlformats.org/package/2006/relationships"
},
"xpath": "//rel:Relationship[@Id='rId999']",
"must": "absent",
"description": "slide1's _rels must NOT define rId999 — otherwise the fixture isn't malformed."
}
],
"behavior_assertions": [
{
"id": "default-load-succeeds",
"kind": "library_loads",
"library": "python-pptx",
"method": "pptx.Presentation",
"args": [
"{fixture_path}"
],
"description": "python-pptx tolerates dangling relationships at load time; the call must return without raising."
}
]
}
No rendered reference is available for this case.