Exercise the five ST_TabJc alignments (left, center, right, decimal, bar) combined with no leader and dot leader.
Library verdicts: python-docx: — docxjs: —
| Feature id | docx/tab-stop-alignment--left--dot |
|---|---|
| Format | docx |
| Category | paragraph-layout |
| Family | docx/tab-stop-alignment |
| Axis values | alignment=left, leader=dot |
| Spec | ecma-376-5-part-1 § 17.9.37 w:tab |
| ID / part | Predicate | XPath | python-docx | docxjs |
|---|---|---|---|---|
tabs-element-present-left-dotword/document.xml | existThe paragraph must carry a <w:tabs> child inside <w:pPr>. | //w:p[w:r/w:t[contains(normalize-space(), 'Tab aligned text')]]/w:pPr/w:tabs | — | — |
tab-alignment-is-leftword/document.xml | equal = leftThe w:tab element must carry the expected w:val alignment. | //w:p[w:r/w:t[contains(normalize-space(), 'Tab aligned text')]]/w:pPr/w:tabs/w:tab/@w:val | — | — |
tab-pos-is-2880-left-dotword/document.xml | equal = 2880The tab stop must be positioned at 2880 twips (2 inches). | //w:p[w:r/w:t[contains(normalize-space(), 'Tab aligned text')]]/w:pPr/w:tabs/w:tab/@w:pos | — | — |
tab-leader-is-dotword/document.xml | equal = dotThe w:tab element must carry the expected w:leader attribute. | //w:p[w:r/w:t[contains(normalize-space(), 'Tab aligned text')]]/w:pPr/w:tabs/w:tab/@w:leader | — | — |
No render assertions declared.
scripts/gen_tab_stop_alignment.py
#!/usr/bin/env python3
"""Generate a ``fixtures/docx/tab-stop-alignment--<align>--<leader>.docx`` fixture.
Parameterised generator for the ``docx/tab-stop-alignment`` feature
family. One invocation writes a single fixture with a single custom
tab stop at 2 inches (2880 twips), using the requested ST_TabJc
alignment and ST_TabTlc leader character.
Usage::
python scripts/gen_tab_stop_alignment.py \\
--alignment decimal \\
--leader dot \\
--out fixtures/docx/tab-stop-alignment--decimal--dot.docx
``--alignment`` is one of ``left``, ``center``, ``right``, ``decimal``,
``bar`` (ST_TabJc enumerants exercised by this family).
``--leader`` is one of ``none`` or ``dot`` (ST_TabTlc enumerants
exercised by this family).
"""
from __future__ import annotations
import argparse
from pathlib import Path
from docx import Document
from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER
from docx.oxml.ns import qn
from docx.shared import Inches
_REPO_ROOT = Path(__file__).resolve().parent.parent
_ALIGNMENTS = {
"left": WD_TAB_ALIGNMENT.LEFT,
"center": WD_TAB_ALIGNMENT.CENTER,
"right": WD_TAB_ALIGNMENT.RIGHT,
"decimal": WD_TAB_ALIGNMENT.DECIMAL,
"bar": WD_TAB_ALIGNMENT.BAR,
}
_LEADERS = {
"none": WD_TAB_LEADER.SPACES,
"dot": WD_TAB_LEADER.DOTS,
}
def _write(alignment: str, leader: str, out: Path) -> None:
try:
wd_alignment = _ALIGNMENTS[alignment]
except KeyError as exc:
raise ValueError(
f"Unknown alignment {alignment!r}; expected one of {sorted(_ALIGNMENTS)}"
) from exc
try:
wd_leader = _LEADERS[leader]
except KeyError as exc:
raise ValueError(
f"Unknown leader {leader!r}; expected one of {sorted(_LEADERS)}"
) from exc
doc = Document()
paragraph = doc.add_paragraph()
tab_stop = paragraph.paragraph_format.tab_stops.add_tab_stop(
Inches(2), wd_alignment, wd_leader
)
# python-docx treats WD_TAB_LEADER.SPACES as the spec default and
# omits the w:leader attribute. Force it to be written so the
# assertion can compare the serialised value for every case.
tab_stop._tab.set(qn("w:leader"), leader)
paragraph.add_run("\tTab aligned text")
out.parent.mkdir(parents=True, exist_ok=True)
doc.save(out, reproducible=True)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--alignment",
required=True,
help=f"Tab stop alignment, one of {sorted(_ALIGNMENTS)}.",
)
parser.add_argument(
"--leader",
required=True,
help=f"Tab stop leader, one of {sorted(_LEADERS)}.",
)
parser.add_argument(
"--out",
required=True,
help="Repo-relative or absolute output path for the .docx.",
)
args = parser.parse_args()
out_path = Path(args.out)
if not out_path.is_absolute():
out_path = _REPO_ROOT / out_path
_write(args.alignment, args.leader, out_path)
print(out_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "docx/tab-stop-alignment--left--dot",
"kind": "literal",
"title": "Tab stop alignment and leader",
"format": "docx",
"category": "paragraph-layout",
"summary": "Exercise the five ST_TabJc alignments (left, center, right, decimal, bar) combined with no leader and dot leader.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "17.9.37",
"element": "w:tab",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
"notes": "<w:tabs> on <w:pPr> holds one or more <w:tab> children. Each <w:tab> carries w:val (ST_TabJc alignment), w:pos (position in twips), and optional w:leader (ST_TabTlc leader character). This family parameterises 5 alignments x 2 leaders = 10 fixtures at w:pos=2880 (2in). The 'none' leader is serialised by python-docx as the literal attribute value 'none' (python-docx emits the attribute even for the spec default); the dot leader is serialised as 'dot'. Generator uses paragraph.paragraph_format.tab_stops.add_tab_stop with WD_TAB_ALIGNMENT and WD_TAB_LEADER."
},
"fixtures": {
"machine": "docx/tab-stop-alignment--left--dot"
},
"generator": {
"python": "scripts/gen_tab_stop_alignment.py",
"arg_template": "--alignment {alignment.val} --leader {leader.val} --out fixtures/docx/tab-stop-alignment--{alignment.id}--{leader.id}.docx"
},
"_expansion": {
"parent_id": "docx/tab-stop-alignment",
"bindings": {
"alignment": "left",
"leader": "dot"
}
},
"assertions": [
{
"id": "tabs-element-present-left-dot",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:p[w:r/w:t[contains(normalize-space(), 'Tab aligned text')]]/w:pPr/w:tabs",
"must": "exist",
"description": "The paragraph must carry a <w:tabs> child inside <w:pPr>."
},
{
"id": "tab-alignment-is-left",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:p[w:r/w:t[contains(normalize-space(), 'Tab aligned text')]]/w:pPr/w:tabs/w:tab/@w:val",
"must": "equal",
"value": "left",
"description": "The w:tab element must carry the expected w:val alignment."
},
{
"id": "tab-pos-is-2880-left-dot",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:p[w:r/w:t[contains(normalize-space(), 'Tab aligned text')]]/w:pPr/w:tabs/w:tab/@w:pos",
"must": "equal",
"value": "2880",
"description": "The tab stop must be positioned at 2880 twips (2 inches)."
},
{
"id": "tab-leader-is-dot",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:p[w:r/w:t[contains(normalize-space(), 'Tab aligned text')]]/w:pPr/w:tabs/w:tab/@w:leader",
"must": "equal",
"value": "dot",
"description": "The w:tab element must carry the expected w:leader attribute."
}
]
}
