W6-B tombstone: a slide authored with a Markup-Compatibility block (<mc:AlternateContent> carrying an <mc:Choice Requires="a14"> subtree and an <mc:Fallback> subtree) must still carry both branches after python-pptx round-trips it. MC is how Office carries forward-looking features (a14:, p14:, p15:) without breaking older readers — losing either the Choice or the Fallback subtree silently destroys fidelity in either a modern or a legacy reader. The generator re-saves `shp-mc-alternate-content.pptx` from the python-pptx feature fixtures and the assertions confirm both branches survive on slide1.
Library verdicts: python-pptx: — pptxjs: —
| Feature id | pptx/preserve-alternate-content |
|---|---|
| Format | pptx |
| Category | round-trip-fidelity |
| Spec | ecma-376-5-part-3 § 10.2.1 mc:AlternateContent |
| ID / part | Predicate | XPath | python-pptx | pptxjs |
|---|---|---|---|---|
alternate-content-wrapper-survivesppt/slides/slide1.xml | existSlide1 must still carry an <mc:AlternateContent> inside spTree after round-trip. | /p:sld/p:cSld/p:spTree/mc:AlternateContent | — | — |
choice-branch-survivesppt/slides/slide1.xml | existThe <mc:Choice> branch must survive. | /p:sld/p:cSld/p:spTree/mc:AlternateContent/mc:Choice | — | — |
choice-requires-attribute-preservedppt/slides/slide1.xml | equal = a14The Requires attribute (a14) on <mc:Choice> must be preserved verbatim. | /p:sld/p:cSld/p:spTree/mc:AlternateContent/mc:Choice/@Requires | — | — |
fallback-branch-survivesppt/slides/slide1.xml | existThe <mc:Fallback> branch must survive. | /p:sld/p:cSld/p:spTree/mc:AlternateContent/mc:Fallback | — | — |
choice-shape-preservedppt/slides/slide1.xml | existThe shape inside the Choice branch must survive (python-pptx must not collapse the Choice tree into a single fallback shape). | /p:sld/p:cSld/p:spTree/mc:AlternateContent/mc:Choice/p:sp | — | — |
No render assertions declared.
scripts/gen_preserve_alternate_content.py
#!/usr/bin/env python3
"""Generate the ``preserve-alternate-content`` tombstone fixture.
Tombstone generator: loads an Office-authored fixture containing
``<mc:AlternateContent>`` with ``mc:Choice`` / ``mc:Fallback`` children on
slide1, round-trips it through ``python-pptx``, and writes the re-saved
archive. The manifest then asserts that both the Choice and Fallback
branches still resolve on the saved slide.
Markup Compatibility (MC) is how PowerPoint carries forward-looking
features that older consumers cannot parse: the ``mc:Choice`` branch
requires a newer namespace (e.g. ``a14:`` Office 2010 drawingML); the
``mc:Fallback`` branch is a graceful-degradation tree renderable by any
reader. Stripping either branch on save is a silent fidelity bug.
"""
from __future__ import annotations
import argparse
import datetime as _dt
from pathlib import Path
from pptx import Presentation
_REPO_ROOT = Path(__file__).resolve().parent.parent
_REPRODUCIBLE_DT = _dt.datetime(2000, 1, 1, 0, 0, 0)
# Office-authored input that embeds <mc:AlternateContent> on slide1.
# See python-pptx/features/steps/test_files/shp-mc-alternate-content.pptx.
_DEFAULT_SOURCE = Path(
"/home/ben/code/python-pptx/features/steps/test_files/"
"shp-mc-alternate-content.pptx"
)
def _roundtrip(source: Path, out: Path) -> None:
prs = Presentation(str(source))
out.parent.mkdir(parents=True, exist_ok=True)
prs.save(out, zip_date_time=_REPRODUCIBLE_DT)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--source",
type=Path,
default=_DEFAULT_SOURCE,
help="Input .pptx containing <mc:AlternateContent> on slide1.",
)
parser.add_argument(
"--out",
required=True,
help="Output fixture path (absolute or repo-relative).",
)
args = parser.parse_args()
out_path = Path(args.out)
if not out_path.is_absolute():
out_path = _REPO_ROOT / out_path
_roundtrip(args.source, out_path)
print(out_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "pptx/preserve-alternate-content",
"kind": "literal",
"title": "Preserve <mc:AlternateContent> through python-pptx load+save",
"format": "pptx",
"category": "round-trip-fidelity",
"summary": "W6-B tombstone: a slide authored with a Markup-Compatibility block (<mc:AlternateContent> carrying an <mc:Choice Requires=\"a14\"> subtree and an <mc:Fallback> subtree) must still carry both branches after python-pptx round-trips it. MC is how Office carries forward-looking features (a14:, p14:, p15:) without breaking older readers — losing either the Choice or the Fallback subtree silently destroys fidelity in either a modern or a legacy reader. The generator re-saves `shp-mc-alternate-content.pptx` from the python-pptx feature fixtures and the assertions confirm both branches survive on slide1.",
"spec": {
"source": "ecma-376-5-part-3",
"clause": "10.2.1",
"element": "mc:AlternateContent",
"notes": "ECMA-376 Part 3 defines Markup Compatibility. <mc:AlternateContent> is a choice wrapper: exactly one <mc:Choice Requires=\"<prefix>\"> branch plus at most one <mc:Fallback>. Consumers that understand the Requires-listed namespaces render the Choice; others render the Fallback. python-pptx reads AlternateContent transparently via oxml; this tombstone verifies the wrapper and both branches still round-trip intact."
},
"fixtures": {
"machine": "pptx/preserve-alternate-content"
},
"generator": {
"python": "scripts/gen_preserve_alternate_content.py",
"arg_template": "--out fixtures/pptx/preserve-alternate-content.pptx"
},
"assertions": [
{
"id": "alternate-content-wrapper-survives",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/p:sld/p:cSld/p:spTree/mc:AlternateContent",
"must": "exist",
"description": "Slide1 must still carry an <mc:AlternateContent> inside spTree after round-trip."
},
{
"id": "choice-branch-survives",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/p:sld/p:cSld/p:spTree/mc:AlternateContent/mc:Choice",
"must": "exist",
"description": "The <mc:Choice> branch must survive."
},
{
"id": "choice-requires-attribute-preserved",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/p:sld/p:cSld/p:spTree/mc:AlternateContent/mc:Choice/@Requires",
"must": "equal",
"value": "a14",
"description": "The Requires attribute (a14) on <mc:Choice> must be preserved verbatim."
},
{
"id": "fallback-branch-survives",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/p:sld/p:cSld/p:spTree/mc:AlternateContent/mc:Fallback",
"must": "exist",
"description": "The <mc:Fallback> branch must survive."
},
{
"id": "choice-shape-preserved",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/p:sld/p:cSld/p:spTree/mc:AlternateContent/mc:Choice/p:sp",
"must": "exist",
"description": "The shape inside the Choice branch must survive (python-pptx must not collapse the Choice tree into a single fallback shape)."
}
]
}
No rendered reference is available for this case.