A family covering PowerPoint's pattern of advertising post-2007 features via <p:extLst>/<p:ext uri="{GUID}">, with the payload wrapped in <mc:AlternateContent> so older clients can fall back to an empty <mc:Fallback> and drop the feature silently. Two cases: (1) the Word-2016 chart-tracking creationId (p188 namespace), (2) the Word-2010 showMediaInPreview setting (p14 namespace).
Library verdicts: python-pptx: — pptxjs: —
| Feature id | pptx/mc-alternate-content--show-media-in-preview |
|---|---|
| Format | pptx |
| Category | markup-compatibility |
| Family | pptx/mc-alternate-content |
| Axis values | case=show-media-in-preview |
| Spec | ecma-376-5-part-3 § 10.2 mc:AlternateContent |
| ID / part | Predicate | XPath | python-pptx | pptxjs |
|---|---|---|---|---|
extlst-ext-present-show-media-in-previewppt/slides/slide1.xml | existThe slide must carry a <p:extLst> containing at least one <p:ext> wrapper. | //p:extLst/p:ext | — | — |
ext-uri-attribute-show-media-in-previewppt/slides/slide1.xml | equal = {D42F4F17-F0D6-40B4-B0DA-55EDBFD4C8EE}The <p:ext> must carry the Microsoft-allocated GUID URI identifying the extension. | //p:extLst/p:ext/@uri | — | — |
alternate-content-inside-ext-show-media-in-previewppt/slides/slide1.xml | existThe extension payload must be wrapped in <mc:AlternateContent>. | //p:extLst/p:ext/mc:AlternateContent | — | — |
choice-requires-show-media-in-previewppt/slides/slide1.xml | equal = p14<mc:Choice Requires="..."> must name the namespace prefix the modern branch relies on. | //p:extLst/p:ext/mc:AlternateContent/mc:Choice/@Requires | — | — |
fallback-present-show-media-in-previewppt/slides/slide1.xml | existAn <mc:Fallback> (empty or otherwise) must be present so readers lacking the Requires-listed extension have a defined branch to select. | //p:extLst/p:ext/mc:AlternateContent/mc:Fallback | — | — |
No render assertions declared.
scripts/gen_pptx_mc_alternate_content.py
#!/usr/bin/env python3
"""Generate ``fixtures/pptx/mc-alternate-content--<case>.pptx``.
Parameterised generator for the ``pptx/mc-alternate-content`` feature
family. Each case authors an ``<p:extLst>`` on the slide carrying an
``<p:ext uri="{GUID}">`` whose payload is wrapped in an
``<mc:AlternateContent>``. PowerPoint uses this pattern to expose
Word-2013+ / Word-2019+ features that older clients would otherwise
reject.
Cases:
* ``creation-id``: ``<p:ext uri="{BB962C8B-B14F-4119-B1C7-8E2F5F9F6F7A}">``
carrying a ``p188:creationId`` per Word 2016+. The ``<mc:Choice>``
declares ``Requires="p188"`` and contains the creationId element; the
``<mc:Fallback>`` is empty, which is the canonical "ignore this
feature" fallback.
* ``show-media-in-preview``: a ``p14:`` extension advertising a Word
2010 feature, with a ``<mc:Choice Requires="p14">`` branch holding
``<p14:showMediaInPreview val="1"/>`` and an empty fallback.
"""
from __future__ import annotations
import argparse
import datetime as _dt
from pathlib import Path
from pptx import Presentation
from pptx.oxml.ns import qn
from lxml import etree
_REPO_ROOT = Path(__file__).resolve().parent.parent
_REPRODUCIBLE_DT = _dt.datetime(2000, 1, 1, 0, 0, 0)
_MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006"
_P_NS = "http://schemas.openxmlformats.org/presentationml/2006/main"
_P14_NS = "http://schemas.microsoft.com/office/powerpoint/2010/main"
_P188_NS = "http://schemas.microsoft.com/office/powerpoint/2016/overrides"
def _creation_id_ext_xml() -> bytes:
"""<p:extLst> with a creationId (Word 2016+) in <mc:Choice Requires="p188">."""
return f"""
<p:extLst xmlns:p="{_P_NS}" xmlns:mc="{_MC_NS}" xmlns:p188="{_P188_NS}">
<p:ext uri="{{BB962C8B-B14F-4119-B1C7-8E2F5F9F6F7A}}">
<mc:AlternateContent>
<mc:Choice Requires="p188">
<p188:creationId val="2837716234"/>
</mc:Choice>
<mc:Fallback/>
</mc:AlternateContent>
</p:ext>
</p:extLst>
""".strip().encode("utf-8")
def _show_media_in_preview_ext_xml() -> bytes:
"""<p:extLst> with a p14:showMediaInPreview in <mc:Choice Requires="p14">."""
return f"""
<p:extLst xmlns:p="{_P_NS}" xmlns:mc="{_MC_NS}" xmlns:p14="{_P14_NS}">
<p:ext uri="{{D42F4F17-F0D6-40B4-B0DA-55EDBFD4C8EE}}">
<mc:AlternateContent>
<mc:Choice Requires="p14">
<p14:showMediaInPreview val="1"/>
</mc:Choice>
<mc:Fallback/>
</mc:AlternateContent>
</p:ext>
</p:extLst>
""".strip().encode("utf-8")
_BUILDERS = {
"creation-id": _creation_id_ext_xml,
"show-media-in-preview": _show_media_in_preview_ext_xml,
}
def _write(case: str, out: Path) -> None:
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[0])
slide.shapes.title.text = "Markup-compatibility demo"
# Inject the <p:extLst> directly onto <p:sld>. It is the last
# element in the sequence (p:cSld, p:clrMapOvr, p:transition,
# p:timing, p:extLst), so a plain append() respects schema order
# on a freshly-created slide that has no trailing siblings.
ext_lst = etree.fromstring(_BUILDERS[case]())
slide.element.append(ext_lst)
out.parent.mkdir(parents=True, exist_ok=True)
prs.save(out, zip_date_time=_REPRODUCIBLE_DT)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--case", required=True, choices=sorted(_BUILDERS))
parser.add_argument("--out", required=True)
args = parser.parse_args()
out_path = Path(args.out)
if not out_path.is_absolute():
out_path = _REPO_ROOT / out_path
_write(args.case, out_path)
print(out_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "pptx/mc-alternate-content--show-media-in-preview",
"kind": "literal",
"title": "Markup Compatibility: mc:AlternateContent inside a slide extLst",
"format": "pptx",
"category": "markup-compatibility",
"summary": "A family covering PowerPoint's pattern of advertising post-2007 features via <p:extLst>/<p:ext uri=\"{GUID}\">, with the payload wrapped in <mc:AlternateContent> so older clients can fall back to an empty <mc:Fallback> and drop the feature silently. Two cases: (1) the Word-2016 chart-tracking creationId (p188 namespace), (2) the Word-2010 showMediaInPreview setting (p14 namespace).",
"spec": {
"source": "ecma-376-5-part-3",
"clause": "10.2",
"element": "mc:AlternateContent",
"notes": "ECMA-376 Part 3 §10.2 lets producers stash namespace-versioned extensions inside Part-1 <extLst>/<ext uri=\"{GUID}\"> wrappers. The URI is a Microsoft-allocated GUID; the payload opens with <mc:AlternateContent> whose <mc:Choice Requires=\"...\"> branch names the namespace prefix a reader needs (p14 for Word 2010, p15 for Word 2013, p188 for Word 2016). Empty <mc:Fallback/> is the canonical 'ignore this feature' shape — older clients that match only the fallback discard the ext. Round-tripping the extension intact matters: if python-pptx's PARSE_TREE silently drops <mc:AlternateContent> inside <p:ext>, the attached feature (chart tracking IDs, smart-art version markers, ink, ...) is lost on re-save."
},
"fixtures": {
"machine": "pptx/mc-alternate-content--show-media-in-preview"
},
"generator": {
"python": "scripts/gen_pptx_mc_alternate_content.py",
"arg_template": "--case {case.id} --out fixtures/pptx/mc-alternate-content--{case.id}.pptx"
},
"_expansion": {
"parent_id": "pptx/mc-alternate-content",
"bindings": {
"case": "show-media-in-preview"
}
},
"assertions": [
{
"id": "extlst-ext-present-show-media-in-preview",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main"
},
"xpath": "//p:extLst/p:ext",
"must": "exist",
"description": "The slide must carry a <p:extLst> containing at least one <p:ext> wrapper."
},
{
"id": "ext-uri-attribute-show-media-in-preview",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main"
},
"xpath": "//p:extLst/p:ext/@uri",
"must": "equal",
"value": "{D42F4F17-F0D6-40B4-B0DA-55EDBFD4C8EE}",
"description": "The <p:ext> must carry the Microsoft-allocated GUID URI identifying the extension."
},
{
"id": "alternate-content-inside-ext-show-media-in-preview",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "//p:extLst/p:ext/mc:AlternateContent",
"must": "exist",
"description": "The extension payload must be wrapped in <mc:AlternateContent>."
},
{
"id": "choice-requires-show-media-in-preview",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "//p:extLst/p:ext/mc:AlternateContent/mc:Choice/@Requires",
"must": "equal",
"value": "p14",
"description": "<mc:Choice Requires=\"...\"> must name the namespace prefix the modern branch relies on."
},
{
"id": "fallback-present-show-media-in-preview",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "//p:extLst/p:ext/mc:AlternateContent/mc:Fallback",
"must": "exist",
"description": "An <mc:Fallback> (empty or otherwise) must be present so readers lacking the Requires-listed extension have a defined branch to select."
}
]
}
No rendered reference is available for this case.