Tag a run's script slot (ASCII/Latin or East-Asian) with a language code so spell-check and hyphenation can pick the right dictionary.
| Library | Pass | Fail | Pending |
|---|---|---|---|
python-docx | 0 | 0 | 10 |
docxjs | 0 | 0 | 10 |
lang (5 values): en-us, en-gb, fr-fr, de-de, es-esslot (2 values): ascii, eascripts/gen_run_language.py — runs with --arg_template --lang {lang.val} --slot {slot.attr} --out fixtures/docx/run-language--{lang.id}--{slot.id}.docx
#!/usr/bin/env python3
"""Generate a ``fixtures/docx/run-language--<lang>--<slot>.docx`` fixture.
Parameterised generator for the ``docx/run-language`` feature family.
One invocation writes a single fixture with a single run whose rPr
carries ``<w:lang/>`` with the target slot attribute set to the
requested BCP-47 language tag.
Usage::
python scripts/gen_run_language.py \\
--lang fr-FR \\
--slot val \\
--out fixtures/docx/run-language--fr-fr--ascii.docx
``--lang`` is a BCP-47 tag written into the selected slot attribute.
``--slot`` is the local name of the target ``w:lang`` attribute:
- ``val`` -> ``w:val`` (ASCII / Latin)
- ``eastAsia`` -> ``w:eastAsia`` (East-Asian)
- ``bidi`` -> ``w:bidi`` (complex-script / RTL)
python-docx has no high-level language API; the generator reaches
down to :class:`docx.oxml.OxmlElement` so each fixture only exercises
one slot.
"""
from __future__ import annotations
import argparse
from pathlib import Path
from docx import Document
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
_REPO_ROOT = Path(__file__).resolve().parent.parent
_VALID_SLOTS = {"val", "eastAsia", "bidi"}
def _write(lang: str, slot: str, out: Path) -> None:
if slot not in _VALID_SLOTS:
raise ValueError(
f"Unknown slot {slot!r}; expected one of {sorted(_VALID_SLOTS)}"
)
doc = Document()
paragraph = doc.add_paragraph()
run = paragraph.add_run("Lang tagged text")
rPr = run._r.get_or_add_rPr()
# Keep the element a singleton — if a previous setter added w:lang,
# remove it before appending ours.
for existing in rPr.findall(qn("w:lang")):
rPr.remove(existing)
lang_el = OxmlElement("w:lang")
lang_el.set(qn(f"w:{slot}"), lang)
rPr.append(lang_el)
out.parent.mkdir(parents=True, exist_ok=True)
doc.save(out, reproducible=True)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--lang",
required=True,
help="BCP-47 language tag written into the target w:lang slot.",
)
parser.add_argument(
"--slot",
required=True,
help=f"w:lang slot local-name; one of {sorted(_VALID_SLOTS)}.",
)
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.lang, args.slot, out_path)
print(out_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "docx/run-language",
"kind": "parameterised",
"title": "Run language",
"format": "docx",
"category": "text-formatting",
"summary": "Tag a run's script slot (ASCII/Latin or East-Asian) with a language code so spell-check and hyphenation can pick the right dictionary.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "17.3.2.20",
"element": "w:lang",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
"notes": "<w:lang/> on a run's rPr has three per-script attributes: w:val (Latin/default BCP-47 language tag, stored in slot name 'val'), w:eastAsia (CJK), and w:bidi (complex-script / RTL). This family pairs five language tags with two script slots (ASCII/Latin and East-Asian) yielding 10 fixtures. python-docx has no first-class API for w:lang, so the generator writes the element via OxmlElement and sets only the targeted slot attribute."
},
"fixtures": {
"machine": "docx/run-language"
},
"generator": {
"python": "scripts/gen_run_language.py",
"arg_template": "--lang {lang.val} --slot {slot.attr} --out fixtures/docx/run-language--{lang.id}--{slot.id}.docx"
},
"parameters": {
"lang": [
{
"id": "en-us",
"val": "en-US"
},
{
"id": "en-gb",
"val": "en-GB"
},
{
"id": "fr-fr",
"val": "fr-FR"
},
{
"id": "de-de",
"val": "de-DE"
},
{
"id": "es-es",
"val": "es-ES"
}
],
"slot": [
{
"id": "ascii",
"attr": "val"
},
{
"id": "ea",
"attr": "eastAsia"
}
]
},
"assertions_template": [
{
"id": "lang-element-present-{lang.id}-{slot.id}",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:r[w:t[normalize-space()='Lang tagged text']]/w:rPr/w:lang",
"must": "exist",
"description": "The run must carry a <w:lang> child inside <w:rPr>."
},
{
"id": "lang-{slot.id}-is-{lang.id}",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:r[w:t[normalize-space()='Lang tagged text']]/w:rPr/w:lang/@w:{slot.attr}",
"must": "equal",
"value": "{lang.val}",
"description": "The selected w:lang slot attribute must carry the expected BCP-47 language tag."
}
],
"render_assertions_template": [
{
"id": "lang-attribute-propagated-{lang.id}-{slot.id}",
"kind": "css_selector",
"selector": "html[lang], .docx-wrapper[lang], .docx-wrapper [lang], .docx-wrapper [xml\\:lang]",
"must": "exist",
"description": "Accessibility: the w:lang tag on the run should surface somewhere in the rendered DOM — as a lang attribute on the root html, on the .docx-wrapper, on any descendant element (paragraph or run), or as an xml:lang attribute. Screen readers switch pronunciation on this hint; a renderer that drops it entirely fails this assertion."
}
]
}