A title slide carries a subtitle placeholder shape whose text body contains subtitle text below the title.
Library verdicts: python-pptx: — pptxjs: fail
| Feature id | pptx/slide-with-subtitle |
|---|---|
| Format | pptx |
| Category | slide-content |
| Spec | ecma-376-5-part-1 § 19.3.1.36 p:ph |
| ID / part | Predicate | XPath | python-pptx | pptxjs |
|---|---|---|---|---|
subtitle-placeholder-text-presentppt/slides/slide1.xml | existA shape marked as a subTitle placeholder must carry a text run whose value is 'A subtitle appears below the title'. | //p:sp[p:nvSpPr/p:nvPr/p:ph[@type='subTitle']]//a:t[normalize-space()='A subtitle appears below the title'] | — | — |
subtitle-placeholder-typeppt/slides/slide1.xml | equal = subTitleThe placeholder carrying the subtitle text must declare type='subTitle'. | //p:sp[.//a:t[normalize-space()='A subtitle appears below the title']]/p:nvSpPr/p:nvPr/p:ph/@type | — | — |
| ID | Predicate | Selector | python-pptx | pptxjs |
|---|---|---|---|---|
slide-subtitle-text-present | css_selector/ match-text = A subtitle appears below the titleA DOM node marked as a subTitle placeholder (via data-placeholder-type or a .pptx-subtitle class) must contain the subtitle text. Falls back to h2 for renderers that use semantic HTML for subtitles. | [data-placeholder-type='subTitle'], .pptx-subtitle, h2 | — | fail |
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-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/subtitle 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_with_subtitle.py
#!/usr/bin/env python3
"""Generate ``fixtures/pptx/slide-with-subtitle.pptx``.
A minimal one-slide presentation exercising the ``p:ph type="subTitle"``
subtitle placeholder on the title-slide layout. Designed to satisfy the
assertions in ``features/pptx/slide-with-subtitle.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-with-subtitle.pptx"
def main() -> int:
prs = Presentation()
# slide_layouts[0] is the title + subtitle layout.
slide = prs.slides.add_slide(prs.slide_layouts[0])
slide.shapes.title.text = "Title with subtitle"
slide.placeholders[1].text = "A subtitle appears below the title"
_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-with-subtitle",
"title": "Slide subtitle placeholder",
"format": "pptx",
"category": "slide-content",
"summary": "A title slide carries a subtitle placeholder shape whose text body contains subtitle text below the title.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "19.3.1.36",
"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 subtitle placeholder with <p:ph type=\"subTitle\"/> on the title-slide layout (slide_layouts[0]). Text lives in the <p:txBody>/<a:p>/<a:r>/<a:t> chain on the same <p:sp>."
},
"fixtures": {
"machine": "pptx/slide-with-subtitle",
"office": "pptx/slide-with-subtitle"
},
"generator": {
"python": "scripts/gen_slide_with_subtitle.py"
},
"assertions": [
{
"id": "subtitle-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='subTitle']]//a:t[normalize-space()='A subtitle appears below the title']",
"must": "exist",
"description": "A shape marked as a subTitle placeholder must carry a text run whose value is 'A subtitle appears below the title'."
},
{
"id": "subtitle-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()='A subtitle appears below the title']]/p:nvSpPr/p:nvPr/p:ph/@type",
"must": "equal",
"value": "subTitle",
"description": "The placeholder carrying the subtitle text must declare type='subTitle'."
}
],
"render_assertions": [
{
"id": "slide-subtitle-text-present",
"kind": "css_selector",
"selector": "[data-placeholder-type='subTitle'], .pptx-subtitle, h2",
"must": "match-text",
"value": "A subtitle appears below the title",
"description": "A DOM node marked as a subTitle placeholder (via data-placeholder-type or a .pptx-subtitle class) must contain the subtitle text. Falls back to h2 for renderers that use semantic HTML for subtitles."
},
{
"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-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/subtitle 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."
}
]
}
