A second-level heading paragraph styled with the built-in Heading 2 style.
Library verdicts: python-docx: — docxjs: pass
| Feature id | docx/heading-2 |
|---|---|
| Format | docx |
| Category | headings |
| Spec | ecma-376-5-part-1 § 17.9.2 w:pStyle |
| ID / part | Predicate | XPath | python-docx | docxjs |
|---|---|---|---|---|
heading-2-pstyle-presentword/document.xml | existThe heading paragraph must declare a pStyle inside its pPr. | //w:p[w:r/w:t[normalize-space()='Heading 2 Subtitle']]/w:pPr/w:pStyle | — | — |
heading-2-pstyle-valword/document.xml | equal = Heading2The pStyle value for a level-2 heading must be 'Heading2'. | //w:p[w:r/w:t[normalize-space()='Heading 2 Subtitle']]/w:pPr/w:pStyle/@w:val | — | — |
| ID | Predicate | Selector | python-docx | docxjs |
|---|---|---|---|---|
heading-2-rendered | css_selector/ match-text = Heading 2 SubtitleA heading-2 DOM node (h2, .docx-heading-2, or Heading2-classed element) must contain the fixture text. Falls back to any paragraph since renderer conventions vary. | .docx-wrapper h2, .docx-wrapper .docx-heading-2, .docx-wrapper [class*='Heading2'], .docx-wrapper p | — | pass |
heading-2-has-semantic-role | css_selector/ existAccessibility: the Heading 2 paragraph must surface either as a semantic <h2> element OR as an element with role='heading' and aria-level='2'. Either convention is accepted; a renderer that emits only a styled <p> or <div> with no heading role/level fails this assertion — that's the intended signal. | .docx-wrapper h2, .docx-wrapper [role='heading'][aria-level='2'], .docx-wrapper [role='heading'][aria-level="2"] | — | — |
scripts/gen_heading_2.py
#!/usr/bin/env python3
"""Generate ``fixtures/docx/heading-2.docx``.
A minimal document exercising the ``w:pStyle`` paragraph-style reference
for the built-in Heading 2 style. Satisfies the assertions in
``features/docx/heading-2.json``.
"""
from __future__ import annotations
from pathlib import Path
from docx import Document
_REPO_ROOT = Path(__file__).resolve().parent.parent
_OUT = _REPO_ROOT / "fixtures" / "docx" / "heading-2.docx"
def main() -> int:
doc = Document()
doc.add_heading("Heading 2 Subtitle", level=2)
_OUT.parent.mkdir(parents=True, exist_ok=True)
doc.save(_OUT, reproducible=True)
print(_OUT)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "docx/heading-2",
"title": "Heading 2",
"format": "docx",
"category": "headings",
"summary": "A second-level heading paragraph styled with the built-in Heading 2 style.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "17.9.2",
"element": "w:pStyle",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
"notes": "Heading style is applied via <w:pPr><w:pStyle w:val=\"Heading2\"/></w:pPr>. The XML pStyle value is 'Heading2' (whitespace removed from the friendly style name)."
},
"fixtures": {
"machine": "docx/heading-2",
"office": "docx/heading-2"
},
"generator": {
"python": "scripts/gen_heading_2.py"
},
"assertions": [
{
"id": "heading-2-pstyle-present",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:p[w:r/w:t[normalize-space()='Heading 2 Subtitle']]/w:pPr/w:pStyle",
"must": "exist",
"description": "The heading paragraph must declare a pStyle inside its pPr."
},
{
"id": "heading-2-pstyle-val",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:p[w:r/w:t[normalize-space()='Heading 2 Subtitle']]/w:pPr/w:pStyle/@w:val",
"must": "equal",
"value": "Heading2",
"description": "The pStyle value for a level-2 heading must be 'Heading2'."
}
],
"render_assertions": [
{
"id": "heading-2-rendered",
"kind": "css_selector",
"selector": ".docx-wrapper h2, .docx-wrapper .docx-heading-2, .docx-wrapper [class*='Heading2'], .docx-wrapper p",
"must": "match-text",
"value": "Heading 2 Subtitle",
"description": "A heading-2 DOM node (h2, .docx-heading-2, or Heading2-classed element) must contain the fixture text. Falls back to any paragraph since renderer conventions vary."
},
{
"id": "heading-2-has-semantic-role",
"kind": "css_selector",
"selector": ".docx-wrapper h2, .docx-wrapper [role='heading'][aria-level='2'], .docx-wrapper [role='heading'][aria-level=\"2\"]",
"must": "exist",
"description": "Accessibility: the Heading 2 paragraph must surface either as a semantic <h2> element OR as an element with role='heading' and aria-level='2'. Either convention is accepted; a renderer that emits only a styled <p> or <div> with no heading role/level fails this assertion — that's the intended signal."
}
]
}
