docx/run-language--en-us--ea

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 verdicts: python-docx: — docxjs: —

Metadata

Feature iddocx/run-language--en-us--ea
Formatdocx
Categorytext-formatting
Familydocx/run-language
Axis valueslang=en-us, slot=ea
Spececma-376-5-part-1 § 17.3.2.20 w:lang

XPath assertions

ID / partPredicateXPathpython-docxdocxjs
lang-element-present-en-us-ea
word/document.xml
exist
The run must carry a <w:lang> child inside <w:rPr>.
//w:r[w:t[normalize-space()='Lang tagged text']]/w:rPr/w:lang
lang-ea-is-en-us
word/document.xml
equal = en-US
The selected w:lang slot attribute must carry the expected BCP-47 language tag.
//w:r[w:t[normalize-space()='Lang tagged text']]/w:rPr/w:lang/@w:eastAsia

Render assertions

IDPredicateSelectorpython-docxdocxjs
lang-attribute-propagated-en-us-eacss_selector/ exist
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.
html[lang], .docx-wrapper[lang], .docx-wrapper [lang], .docx-wrapper [xml\:lang]

Generator source

scripts/gen_run_language.py

#!/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())

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/run-language--en-us--ea",
  "kind": "literal",
  "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--en-us--ea"
  },
  "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"
  },
  "_expansion": {
    "parent_id": "docx/run-language",
    "bindings": {
      "lang": "en-us",
      "slot": "ea"
    }
  },
  "assertions": [
    {
      "id": "lang-element-present-en-us-ea",
      "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-ea-is-en-us",
      "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:eastAsia",
      "must": "equal",
      "value": "en-US",
      "description": "The selected w:lang slot attribute must carry the expected BCP-47 language tag."
    }
  ],
  "render_assertions": [
    {
      "id": "lang-attribute-propagated-en-us-ea",
      "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."
    }
  ]
}

Fixture

Download run-language--en-us--ea.docx (18.6 KB)

Reference preview

Reference (machine, page 1 PNG)

docx/run-language--en-us--ea page 1 reference

Reference PDF


Download PDF Open in PDF.js

Spec 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.