Paragraph horizontal alignment (w:jc) exercised across all six ST_Jc values commonly emitted by Word: left, center, right, both, distribute, start.
Library verdicts: python-docx: — docxjs: pass
| Feature id | docx/paragraph-alignment-param--both |
|---|---|
| Format | docx |
| Category | paragraph-layout |
| Family | docx/paragraph-alignment-param |
| Axis values | jc=both |
| Spec | ecma-376-5-part-1 § 17.3.1.13 w:jc |
| ID / part | Predicate | XPath | python-docx | docxjs |
|---|---|---|---|---|
jc-val-is-bothword/document.xml | equal = bothThe paragraph's <w:jc> must declare the expected ST_Jc value. | //w:p[w:r/w:t[normalize-space()='Justified text longer than one line to see the effect']]/w:pPr/w:jc/@w:val | — | — |
| ID | Predicate | Selector | python-docx | docxjs |
|---|---|---|---|---|
paragraph-alignment-both-text-present | css_selector/ match-text = Justified text longer than one line to see the effectThe rendered DOM must contain the fixture text. | .docx-wrapper p | — | pass |
paragraph-alignment-both-computed | computed_style = ^(justify)$ style=text-alignComputed text-align on the paragraph must match the expected CSS value(s) for this jc. | .docx-wrapper p | — | pass |
scripts/gen_paragraph_alignment_param.py
#!/usr/bin/env python3
"""Generate a ``fixtures/docx/paragraph-alignment-param--<id>.docx`` fixture.
Parameterised generator for the ``docx/paragraph-alignment-param`` feature
family. One invocation writes a single fixture for the given ``w:jc`` value.
Usage::
python scripts/gen_paragraph_alignment_param.py \
--jc center \
--text "Center aligned" \
--out fixtures/docx/paragraph-alignment-param--center.docx
The ``--jc`` argument takes any ``w:jc`` ST_Jc value from ECMA-376-5 Part 1
clause 17.18.44 / 17.3.1.13. Values handled explicitly in this family:
- ``left`` -> WD_ALIGN_PARAGRAPH.LEFT
- ``center`` -> WD_ALIGN_PARAGRAPH.CENTER
- ``right`` -> WD_ALIGN_PARAGRAPH.RIGHT
- ``both`` -> WD_ALIGN_PARAGRAPH.JUSTIFY
- ``distribute`` -> WD_ALIGN_PARAGRAPH.DISTRIBUTE
- ``start`` -> no python-docx enum member; set via OxmlElement directly
Any other string is accepted as an escape hatch and written straight into
``w:jc/@w:val`` with OxmlElement, so additional Jc values (``end``,
``mediumKashida``, etc.) can be exercised without code changes.
"""
from __future__ import annotations
import argparse
from pathlib import Path
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
_REPO_ROOT = Path(__file__).resolve().parent.parent
# w:jc values that map cleanly onto python-docx's WD_ALIGN_PARAGRAPH.
_ENUM_MAP = {
"left": WD_ALIGN_PARAGRAPH.LEFT,
"center": WD_ALIGN_PARAGRAPH.CENTER,
"right": WD_ALIGN_PARAGRAPH.RIGHT,
"both": WD_ALIGN_PARAGRAPH.JUSTIFY,
"distribute": WD_ALIGN_PARAGRAPH.DISTRIBUTE,
}
def _set_jc_raw(paragraph, val: str) -> None:
"""Force ``w:pPr/w:jc@w:val`` to ``val`` via direct XML manipulation.
Used for ST_Jc values that WD_ALIGN_PARAGRAPH doesn't enumerate (e.g.
``start``, ``end``, ``mediumKashida``).
"""
pPr = paragraph._p.get_or_add_pPr()
for existing in pPr.findall(qn("w:jc")):
pPr.remove(existing)
jc = OxmlElement("w:jc")
jc.set(qn("w:val"), val)
pPr.append(jc)
def _write(jc_val: str, text: str, out: Path) -> None:
doc = Document()
paragraph = doc.add_paragraph(text)
if jc_val in _ENUM_MAP:
paragraph.alignment = _ENUM_MAP[jc_val]
else:
_set_jc_raw(paragraph, jc_val)
out.parent.mkdir(parents=True, exist_ok=True)
doc.save(out, reproducible=True)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--jc",
required=True,
help="ST_Jc value to emit as <w:jc w:val=...>, e.g. left/center/right/both/distribute/start.",
)
parser.add_argument(
"--text",
required=True,
help="Paragraph text content.",
)
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.jc, args.text, out_path)
print(out_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "docx/paragraph-alignment-param--both",
"kind": "literal",
"title": "Paragraph alignment (parameterised)",
"format": "docx",
"category": "paragraph-layout",
"summary": "Paragraph horizontal alignment (w:jc) exercised across all six ST_Jc values commonly emitted by Word: left, center, right, both, distribute, start.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "17.3.1.13",
"element": "w:jc",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
"notes": "Horizontal alignment is carried by <w:jc w:val=\"...\"/> inside <w:pPr>. ST_Jc (clause 17.18.44) enumerates left, center, right, both, distribute, start, end, mediumKashida, and kashida. This parameterised manifest covers the six Word-interoperable values; literal companion manifests (docx/alignment-{left,center,right,justify}) remain as the pre-parameterised shape."
},
"fixtures": {
"machine": "docx/paragraph-alignment-param--both"
},
"generator": {
"python": "scripts/gen_paragraph_alignment_param.py",
"arg_template": "--jc {jc.val} --text \"{jc.text}\" --out fixtures/docx/paragraph-alignment-param--{jc.id}.docx"
},
"_expansion": {
"parent_id": "docx/paragraph-alignment-param",
"bindings": {
"jc": "both"
}
},
"assertions": [
{
"id": "jc-val-is-both",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:p[w:r/w:t[normalize-space()='Justified text longer than one line to see the effect']]/w:pPr/w:jc/@w:val",
"must": "equal",
"value": "both",
"description": "The paragraph's <w:jc> must declare the expected ST_Jc value."
}
],
"render_assertions": [
{
"id": "paragraph-alignment-both-text-present",
"kind": "css_selector",
"selector": ".docx-wrapper p",
"must": "match-text",
"value": "Justified text longer than one line to see the effect",
"description": "The rendered DOM must contain the fixture text."
},
{
"id": "paragraph-alignment-both-computed",
"kind": "computed_style",
"selector": ".docx-wrapper p",
"style_property": "text-align",
"value": "^(justify)$",
"description": "Computed text-align on the paragraph must match the expected CSS value(s) for this jc."
}
]
}
