pptx/malformed-missing-rel

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: —

Metadata

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

XPath assertions

ID / partPredicateXPathpython-pptxpptxjs
dangling-hlink-present
ppt/slides/slide1.xml
exist
Confirms slide1.xml references rId999.
//a:hlinkClick[@r:id='rId999']
no-matching-relationship
ppt/slides/_rels/slide1.xml.rels
absent
slide1's _rels must NOT define rId999 — otherwise the fixture isn't malformed.
//rel:Relationship[@Id='rId999']

Render assertions

No render assertions declared.

Generator source

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())

Manifest (expanded)

{
  "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."
    }
  ]
}

Fixture

Download malformed-missing-rel.pptx (27.5 KB)

Reference preview

No rendered reference is available for this case.

Spec 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.