Exercise the five ST_TabJc alignments (left, center, right, decimal, bar) combined with no leader and dot leader.
| Library | Pass | Fail | Pending |
|---|---|---|---|
python-docx | 0 | 0 | 10 |
docxjs | 0 | 0 | 10 |
alignment (5 values): left, center, right, decimal, barleader (2 values): none, dotscripts/gen_tab_stop_alignment.py — runs with --arg_template --alignment {alignment.val} --leader {leader.val} --out fixtures/docx/tab-stop-alignment--{alignment.id}--{leader.id}.docx
#!/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",
"kind": "parameterised",
"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"
},
"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"
},
"parameters": {
"alignment": [
{
"id": "left",
"val": "left"
},
{
"id": "center",
"val": "center"
},
{
"id": "right",
"val": "right"
},
{
"id": "decimal",
"val": "decimal"
},
{
"id": "bar",
"val": "bar"
}
],
"leader": [
{
"id": "none",
"val": "none"
},
{
"id": "dot",
"val": "dot"
}
]
},
"assertions_template": [
{
"id": "tabs-element-present-{alignment.id}-{leader.id}",
"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-{alignment.id}",
"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": "{alignment.val}",
"description": "The w:tab element must carry the expected w:val alignment."
},
{
"id": "tab-pos-is-2880-{alignment.id}-{leader.id}",
"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-{leader.id}",
"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": "{leader.val}",
"description": "The w:tab element must carry the expected w:leader attribute."
}
]
}