Stress test that writes a presentation with N title-only slides, each titled 'Slide <i>' for 1-indexed i, and verifies the sldIdLst carries N entries. Parameterised across N = 10, 100, 500 (the 1000-slide case is intentionally omitted; 500 saves in ~1s on the author machine, 1000 crossed 2s without uncovering anything new).
Library verdicts: python-pptx: — pptxjs: —
| Feature id | pptx/scale-slides--n100 |
|---|---|
| Format | pptx |
| Category | scale |
| Family | pptx/scale-slides |
| Axis values | size=n100 |
| Spec | ecma-376-5-part-1 § 19.3.1 p:sldIdLst |
| ID / part | Predicate | XPath | python-pptx | pptxjs |
|---|---|---|---|---|
sldidlst-count-n100ppt/presentation.xml | equal = 100The sldIdLst must list exactly N <p:sldId> entries. | string(count(/p:presentation/p:sldIdLst/p:sldId)) | — | — |
slide1-title-n100ppt/slides/slide1.xml | existThe first slide must carry the title 'Slide 1', guarding against head-of-stream truncation. | //a:t[normalize-space()='Slide 1'] | — | — |
slide-last-title-n100ppt/slides/slide100.xml | existThe last slide must carry the title 'Slide N', guarding against tail truncation or a missing slideN.xml part. | //a:t[normalize-space()='Slide 100'] | — | — |
No render assertions declared.
scripts/gen_scale_slides.py
#!/usr/bin/env python3
"""Generate ``fixtures/pptx/scale-slides--<n>.pptx``.
Parameterised stress fixture for the ``pptx/scale-slides`` family.
Writes a presentation with ``--slides`` title-only slides; each slide's
title contains its 1-indexed position so the presentation is internally
distinguishable.
Assertions target the first, middle, and last slide-part so the fixture
proves the slide id generator allocated unique ids all the way through
the run.
Usage::
python scripts/gen_scale_slides.py \
--slides 100 \
--out fixtures/pptx/scale-slides--n100.pptx
"""
from __future__ import annotations
import argparse
import datetime as _dt
from pathlib import Path
from pptx import Presentation
_REPRODUCIBLE_DT = (1980, 1, 1, 0, 0, 0)
_REPO_ROOT = Path(__file__).resolve().parent.parent
def _write(slides: int, out: Path) -> None:
prs = Presentation()
layout = prs.slide_layouts[5] # title-only layout
for i in range(1, slides + 1):
slide = prs.slides.add_slide(layout)
slide.shapes.title.text = f"Slide {i}"
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("--slides", required=True, type=int, help="Number of slides.")
parser.add_argument("--out", required=True, help="Output path for the .pptx file.")
args = parser.parse_args()
out_path = Path(args.out)
if not out_path.is_absolute():
out_path = _REPO_ROOT / out_path
_write(args.slides, out_path)
print(out_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "pptx/scale-slides--n100",
"kind": "literal",
"title": "Scale: many slides",
"format": "pptx",
"category": "scale",
"summary": "Stress test that writes a presentation with N title-only slides, each titled 'Slide <i>' for 1-indexed i, and verifies the sldIdLst carries N entries. Parameterised across N = 10, 100, 500 (the 1000-slide case is intentionally omitted; 500 saves in ~1s on the author machine, 1000 crossed 2s without uncovering anything new).",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "19.3.1",
"element": "p:sldIdLst",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/PresentationML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/pml.xsd",
"notes": "Each slide added via slides.add_slide() is registered as a <p:sldId> under <p:sldIdLst> in ppt/presentation.xml and emitted as ppt/slides/slideN.xml. python-pptx starts allocation at id=256 and increments by 1 per slide. This family verifies (a) the sldIdLst cardinality matches the requested count, (b) the first and last slide parts exist and carry the expected 'Slide 1' / 'Slide N' title, guarding against id-generator collisions, missed slide parts, or off-by-one truncation."
},
"fixtures": {
"machine": "pptx/scale-slides--n100"
},
"generator": {
"python": "scripts/gen_scale_slides.py",
"arg_template": "--slides {size.n} --out fixtures/pptx/scale-slides--{size.id}.pptx"
},
"meta": {
"performance": {
"recorded_on": "2026-05-05",
"notes": "Save-time scales roughly linearly with slide count (0.21s, 0.32s, 1.03s for 10/100/500). File size grows linearly at roughly 850 bytes per slide after fixed overhead.",
"cases": [
{
"id": "n10",
"save_seconds": 0.21,
"file_bytes": 35233,
"first_sld_id": 256,
"last_sld_id": 265
},
{
"id": "n100",
"save_seconds": 0.32,
"file_bytes": 106881,
"first_sld_id": 256,
"last_sld_id": 355
},
{
"id": "n500",
"save_seconds": 1.03,
"file_bytes": 427336,
"first_sld_id": 256,
"last_sld_id": 755
}
]
}
},
"_expansion": {
"parent_id": "pptx/scale-slides",
"bindings": {
"size": "n100"
}
},
"assertions": [
{
"id": "sldidlst-count-n100",
"part": "ppt/presentation.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main"
},
"xpath": "string(count(/p:presentation/p:sldIdLst/p:sldId))",
"must": "equal",
"value": "100",
"description": "The sldIdLst must list exactly N <p:sldId> entries."
},
{
"id": "slide1-title-n100",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"a": "http://schemas.openxmlformats.org/drawingml/2006/main"
},
"xpath": "//a:t[normalize-space()='Slide 1']",
"must": "exist",
"description": "The first slide must carry the title 'Slide 1', guarding against head-of-stream truncation."
},
{
"id": "slide-last-title-n100",
"part": "ppt/slides/slide100.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"a": "http://schemas.openxmlformats.org/drawingml/2006/main"
},
"xpath": "//a:t[normalize-space()='Slide 100']",
"must": "exist",
"description": "The last slide must carry the title 'Slide N', guarding against tail truncation or a missing slideN.xml part."
}
]
}
No rendered reference is available for this case.