W6-B tombstone: a slide that Office authored with a `p:extLst` containing a `p:ext uri="{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}"` wrapping `p14:creationId` must still carry that extension after being loaded and re-saved by python-pptx. Office emits this extension on every slide created in PowerPoint 2010+; if python-pptx ever strips unknown-URI extensions on save it will silently destroy provenance metadata on every re-saved deck. This tombstone catches such a regression: the generator loads `prs-add-slide.pptx` from the python-pptx feature fixtures (an Office-authored file known to carry the extension), re-saves with python-pptx, and the assertion below must still find `p14:creationId` on slide1.
Library verdicts: python-pptx: — pptxjs: —
| Feature id | pptx/preserve-extlst-creationid |
|---|---|
| Format | pptx |
| Category | round-trip-fidelity |
| Spec | ecma-376-5-part-1 § 19.3.1.12 p:extLst |
| ID / part | Predicate | XPath | python-pptx | pptxjs |
|---|---|---|---|---|
extension-uri-is-creationidppt/slides/slide1.xml | existAfter python-pptx round-trips the fixture, slide1 must still carry a p:ext with the PowerPoint creationId URI. | /p:sld/p:cSld/p:extLst/p:ext[@uri='{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}'] | — | — |
creationid-child-survivesppt/slides/slide1.xml | existThe p14:creationId element inside the extension must still be present. | /p:sld/p:cSld/p:extLst/p:ext/p14:creationId | — | — |
creationid-val-preservedppt/slides/slide1.xml | match = ^[0-9]+$The @val attribute on p14:creationId must still be a numeric identifier (PowerPoint writes an unsigned 32-bit integer). | /p:sld/p:cSld/p:extLst/p:ext/p14:creationId/@val | — | — |
No render assertions declared.
scripts/gen_preserve_extlst_creationid.py
#!/usr/bin/env python3
"""Generate the ``preserve-extlst-creationid`` tombstone fixture.
Tombstone generator: loads an Office-authored fixture containing
``<p:extLst><p:ext uri="{BB962C8B-...}"><p14:creationId/>`` on a slide,
round-trips it through ``python-pptx`` (``Presentation(...).save(...)``),
and writes the re-saved archive to the output path. The tombstone
manifest then asserts that the ``p14:creationId`` extension element
still resolves on the saved slide.
Input fixture: the generator copies an Office-authored fixture from the
local python-pptx checkout by default. Override with ``--source`` if
you need to test a different starting point.
"""
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 contains <p:extLst><p:ext uri="{BB962C8B-...}">
# <p14:creationId/>. Verified via the W6-B audit against
# python-pptx/features/steps/test_files.
_DEFAULT_SOURCE = Path(
"/home/ben/code/python-pptx/features/steps/test_files/prs-add-slide.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 a p14:creationId extension 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-extlst-creationid",
"kind": "literal",
"title": "Preserve <p:extLst>/<p14:creationId> through python-pptx load+save",
"format": "pptx",
"category": "round-trip-fidelity",
"summary": "W6-B tombstone: a slide that Office authored with a `p:extLst` containing a `p:ext uri=\"{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}\"` wrapping `p14:creationId` must still carry that extension after being loaded and re-saved by python-pptx. Office emits this extension on every slide created in PowerPoint 2010+; if python-pptx ever strips unknown-URI extensions on save it will silently destroy provenance metadata on every re-saved deck. This tombstone catches such a regression: the generator loads `prs-add-slide.pptx` from the python-pptx feature fixtures (an Office-authored file known to carry the extension), re-saves with python-pptx, and the assertion below must still find `p14:creationId` on slide1.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "19.3.1.12",
"element": "p:extLst",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/pml.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/pml.xsd",
"notes": "p:ext is the generic spec-compliant container for future-or-vendor extensions. PowerPoint 2010+ populates it with <p14:creationId val=\"...\"/> (Microsoft's per-slide provenance ID) gated by the URI {BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}. A conformant consumer must preserve p:ext subtrees it does not understand (ECMA-376 Part 3 'markup compatibility'). Dropping them is a silent fidelity bug."
},
"fixtures": {
"machine": "pptx/preserve-extlst-creationid"
},
"generator": {
"python": "scripts/gen_preserve_extlst_creationid.py",
"arg_template": "--out fixtures/pptx/preserve-extlst-creationid.pptx"
},
"assertions": [
{
"id": "extension-uri-is-creationid",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main"
},
"xpath": "/p:sld/p:cSld/p:extLst/p:ext[@uri='{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}']",
"must": "exist",
"description": "After python-pptx round-trips the fixture, slide1 must still carry a p:ext with the PowerPoint creationId URI."
},
{
"id": "creationid-child-survives",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"p14": "http://schemas.microsoft.com/office/powerpoint/2010/main"
},
"xpath": "/p:sld/p:cSld/p:extLst/p:ext/p14:creationId",
"must": "exist",
"description": "The p14:creationId element inside the extension must still be present."
},
{
"id": "creationid-val-preserved",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"p14": "http://schemas.microsoft.com/office/powerpoint/2010/main"
},
"xpath": "/p:sld/p:cSld/p:extLst/p:ext/p14:creationId/@val",
"must": "match",
"value": "^[0-9]+$",
"description": "The @val attribute on p14:creationId must still be a numeric identifier (PowerPoint writes an unsigned 32-bit integer)."
}
]
}
No rendered reference is available for this case.