A slide carries a title placeholder shape whose text body contains the slide title.
Library verdicts: python-pptx: — pptxjs: fail
| Feature id | pptx/slide-title |
|---|---|
| Format | pptx |
| Category | slide-layout |
| Spec | ecma-376-5-part-1 § 19.3.1 p:ph |
| ID / part | Predicate | XPath | python-pptx | pptxjs |
|---|---|---|---|---|
title-placeholder-text-presentppt/slides/slide1.xml | existA shape marked as a title or centre-title placeholder must carry a text run whose value is 'Hello slide world'. | //p:sp[p:nvSpPr/p:nvPr/p:ph[@type='ctrTitle' or @type='title']]//a:t[normalize-space()='Hello slide world'] | — | — |
title-placeholder-typeppt/slides/slide1.xml | match = ^(ctrTitle|title)$The placeholder carrying the title text must declare type='ctrTitle' or type='title'. | //p:sp[.//a:t[normalize-space()='Hello slide world']]/p:nvSpPr/p:nvPr/p:ph/@type | — | — |
| ID | Predicate | Selector | python-pptx | pptxjs |
|---|---|---|---|---|
slide-title-text-present | css_selector/ match-text = Hello slide worldA DOM node marked as a title placeholder (via data-placeholder-type or a .pptx-title class) must contain the slide title text. Falls back to h1/h2 for renderers that use semantic HTML for titles. | [data-placeholder-type='ctrTitle'], [data-placeholder-type='title'], .pptx-title, h1, h2 | — | fail |
slide-title-visual-ssim | visual_ssimA Playwright screenshot of the rendered slide must score >= 0.80 SSIM against the LibreOffice-rendered reference PNG (refs/pptx/slide-title-page1.png, resolved by the runner from the machine-fixture name). Threshold is lower than docx (0.85) because pptx slides carry shape, background, and positioning variance that can't be perfectly reproduced by a web renderer. | — | — | |
slide-count | css_selector/ equal-countExactly one slide container must be rendered. Catches renderers that emit zero slides (hard failure) or duplicate the slide. | section.pptx-slide, .pptx-slide, section[data-slide], .slide, [data-slide] | — | — |
slide-title-has-heading-role | css_selector/ existAccessibility: the slide title should carry heading semantics — a semantic <h1>/<h2>, an explicit role='heading', or (if it's used as the slide's aria-labelledby target) an id so the slide landmark can reference it. Accepts any of these; a plain styled <div>/<span> with no heading signal and no id fails this assertion. | h1, h2, [role='heading'], [data-placeholder-type='ctrTitle'][id], [data-placeholder-type='title'][id], .pptx-title[id] | — | — |
slide-has-landmark-a11y | css_selector/ existAccessibility: a slide should surface as a landmark for screen readers — either with a landmark role (region/article/group), or hooked to the title via aria-labelledby, or named with aria-label. Accepts any of these; a renderer that emits the slide as a plain <div> with no ARIA hook fails this assertion. | [role='region'], [role='article'], [role='group'], [aria-labelledby], [aria-label], section[aria-label], section[aria-labelledby], .pptx-slide[aria-label], .pptx-slide[aria-labelledby], [data-slide-index][aria-label], [data-slide-index][aria-labelledby] | — | — |
scripts/gen_slide_title.py
#!/usr/bin/env python3
"""Generate ``fixtures/pptx/slide-title.pptx``.
A minimal one-slide presentation exercising the ``p:ph type="ctrTitle"``
title placeholder. Designed to satisfy the assertions in
``features/pptx/slide-title.json``.
"""
from __future__ import annotations
from pathlib import Path
from pptx import Presentation
import datetime as _dt
_REPRODUCIBLE_DT = _dt.datetime(2000, 1, 1, 0, 0, 0)
_REPO_ROOT = Path(__file__).resolve().parent.parent
_OUT = _REPO_ROOT / "fixtures" / "pptx" / "slide-title.pptx"
def main() -> int:
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[0])
slide.shapes.title.text = "Hello slide world"
_OUT.parent.mkdir(parents=True, exist_ok=True)
prs.save(_OUT, zip_date_time=_REPRODUCIBLE_DT)
print(_OUT)
return 0
if __name__ == "__main__":
raise SystemExit(main()){
"$schema": "../manifest.schema.json",
"id": "pptx/slide-title",
"title": "Slide title placeholder",
"format": "pptx",
"category": "slide-layout",
"summary": "A slide carries a title placeholder shape whose text body contains the slide title.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "19.3.1",
"element": "p:ph",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/PresentationML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/pml.xsd",
"notes": "PowerPoint marks a title placeholder with <p:ph type=\"ctrTitle\"/> on a centre-title layout (slide_layouts[0]) or <p:ph type=\"title\"/> on other layouts. The assertion accepts either. Text lives in the <p:txBody>/<a:p>/<a:r>/<a:t> chain on the same <p:sp>. visual_ssim uses min_ssim=0.80 — pptx slides carry more visual complexity (shapes, text positioning, layout backgrounds) than a flat docx page, so the threshold is lower than the docx default of 0.85."
},
"fixtures": {
"machine": "pptx/slide-title",
"office": "pptx/slide-title"
},
"generator": {
"python": "scripts/gen_slide_title.py"
},
"assertions": [
{
"id": "title-placeholder-text-present",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"a": "http://schemas.openxmlformats.org/drawingml/2006/main"
},
"xpath": "//p:sp[p:nvSpPr/p:nvPr/p:ph[@type='ctrTitle' or @type='title']]//a:t[normalize-space()='Hello slide world']",
"must": "exist",
"description": "A shape marked as a title or centre-title placeholder must carry a text run whose value is 'Hello slide world'."
},
{
"id": "title-placeholder-type",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"a": "http://schemas.openxmlformats.org/drawingml/2006/main"
},
"xpath": "//p:sp[.//a:t[normalize-space()='Hello slide world']]/p:nvSpPr/p:nvPr/p:ph/@type",
"must": "match",
"value": "^(ctrTitle|title)$",
"description": "The placeholder carrying the title text must declare type='ctrTitle' or type='title'."
}
],
"render_assertions": [
{
"id": "slide-title-text-present",
"kind": "css_selector",
"selector": "[data-placeholder-type='ctrTitle'], [data-placeholder-type='title'], .pptx-title, h1, h2",
"must": "match-text",
"value": "Hello slide world",
"description": "A DOM node marked as a title placeholder (via data-placeholder-type or a .pptx-title class) must contain the slide title text. Falls back to h1/h2 for renderers that use semantic HTML for titles."
},
{
"id": "slide-title-visual-ssim",
"kind": "visual_ssim",
"min_ssim": 0.8,
"description": "A Playwright screenshot of the rendered slide must score >= 0.80 SSIM against the LibreOffice-rendered reference PNG (refs/pptx/slide-title-page1.png, resolved by the runner from the machine-fixture name). Threshold is lower than docx (0.85) because pptx slides carry shape, background, and positioning variance that can't be perfectly reproduced by a web renderer."
},
{
"id": "slide-count",
"kind": "css_selector",
"selector": "section.pptx-slide, .pptx-slide, section[data-slide], .slide, [data-slide]",
"must": "equal-count",
"count": 1,
"description": "Exactly one slide container must be rendered. Catches renderers that emit zero slides (hard failure) or duplicate the slide."
},
{
"id": "slide-title-has-heading-role",
"kind": "css_selector",
"selector": "h1, h2, [role='heading'], [data-placeholder-type='ctrTitle'][id], [data-placeholder-type='title'][id], .pptx-title[id]",
"must": "exist",
"description": "Accessibility: the slide title should carry heading semantics — a semantic <h1>/<h2>, an explicit role='heading', or (if it's used as the slide's aria-labelledby target) an id so the slide landmark can reference it. Accepts any of these; a plain styled <div>/<span> with no heading signal and no id fails this assertion."
},
{
"id": "slide-has-landmark-a11y",
"kind": "css_selector",
"selector": "[role='region'], [role='article'], [role='group'], [aria-labelledby], [aria-label], section[aria-label], section[aria-labelledby], .pptx-slide[aria-label], .pptx-slide[aria-labelledby], [data-slide-index][aria-label], [data-slide-index][aria-labelledby]",
"must": "exist",
"description": "Accessibility: a slide should surface as a landmark for screen readers — either with a landmark role (region/article/group), or hooked to the title via aria-labelledby, or named with aria-label. Accepts any of these; a renderer that emits the slide as a plain <div> with no ARIA hook fails this assertion."
}
]
}
