Exercise ST_NumberFormat values (decimal, upperRoman, ideographDigital, aiueo, ...) on a single-level numbered list via <w:lvl>/<w:numFmt>.
Library verdicts: python-docx: — docxjs: —
| Feature id | docx/list-numbering-format--japanese-digital-ten-thousand |
|---|---|
| Format | docx |
| Category | lists |
| Family | docx/list-numbering-format |
| Axis values | numfmt=japanese-digital-ten-thousand |
| Spec | ecma-376-5-part-1 § 17.9.17 w:numFmt |
| ID / part | Predicate | XPath | python-docx | docxjs |
|---|---|---|---|---|
list-numbering-format-japanese-digital-ten-thousand-numfmt-presentword/numbering.xml | existThe numbering part must contain a <w:lvl>/<w:numFmt> whose @w:val is 'japaneseDigitalTenThousand'. | //w:lvl/w:numFmt[@w:val='japaneseDigitalTenThousand'] | — | — |
No render assertions declared.
scripts/gen_list_numbering_format.py
#!/usr/bin/env python3
"""Generate a ``fixtures/docx/list-numbering-format--<id>.docx`` fixture.
Parameterised family exercising ST_NumberFormat values (ECMA-376 Part 1
clause 17.9.17). Each generated fixture contains a single numbered list
item (``numId=1``, ``ilvl=0``) whose abstract-num level declares the
specified ``w:numFmt/@w:val``.
python-docx's high-level numbering API does not expose per-level
``w:numFmt`` control, so the numbering part is built from scratch via
``OxmlElement`` (bypassing the :class:`WD_NUMBER_FORMAT` enum, which
only covers the most common subset).
"""
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
def _write(numfmt_val: str, out: Path) -> None:
doc = Document()
# Ensure numbering part exists, then wipe any existing abstractNum / num
# so the fixture starts from a clean slate.
numbering = doc.part.numbering_part.element
for child in list(numbering):
if child.tag in (qn("w:num"), qn("w:abstractNum")):
numbering.remove(child)
abs_num = OxmlElement("w:abstractNum")
abs_num.set(qn("w:abstractNumId"), "0")
lvl = OxmlElement("w:lvl")
lvl.set(qn("w:ilvl"), "0")
start = OxmlElement("w:start")
start.set(qn("w:val"), "1")
lvl.append(start)
fmt = OxmlElement("w:numFmt")
fmt.set(qn("w:val"), numfmt_val)
lvl.append(fmt)
lvl_text = OxmlElement("w:lvlText")
lvl_text.set(qn("w:val"), "%1.")
lvl.append(lvl_text)
abs_num.append(lvl)
numbering.append(abs_num)
num = OxmlElement("w:num")
num.set(qn("w:numId"), "1")
abs_ref = OxmlElement("w:abstractNumId")
abs_ref.set(qn("w:val"), "0")
num.append(abs_ref)
numbering.append(num)
p = doc.add_paragraph("Numbered item one")
pPr = p._p.get_or_add_pPr()
numPr = OxmlElement("w:numPr")
ilvl = OxmlElement("w:ilvl")
ilvl.set(qn("w:val"), "0")
numPr.append(ilvl)
numId = OxmlElement("w:numId")
numId.set(qn("w:val"), "1")
numPr.append(numId)
pPr.append(numPr)
out.parent.mkdir(parents=True, exist_ok=True)
doc.save(out, reproducible=True)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--numfmt", required=True, help="ST_NumberFormat value, e.g. 'decimal'")
parser.add_argument("--out", required=True)
args = parser.parse_args()
out_path = Path(args.out)
if not out_path.is_absolute():
out_path = _REPO_ROOT / out_path
_write(args.numfmt, out_path)
print(out_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "docx/list-numbering-format--japanese-digital-ten-thousand",
"kind": "literal",
"title": "List numbering format",
"format": "docx",
"category": "lists",
"summary": "Exercise ST_NumberFormat values (decimal, upperRoman, ideographDigital, aiueo, ...) on a single-level numbered list via <w:lvl>/<w:numFmt>.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "17.9.17",
"element": "w:numFmt",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
"notes": "ST_NumberFormat is the simple type backing <w:numFmt w:val='...'/>. It enumerates ~60 numbering-style tokens (decimal, upperRoman, ordinal, cardinalText, chicago, ideographDigital, japaneseCounting, aiueo, iroha, decimalFullWidth, ganada, chosung, decimalEnclosedCircle, bullet, ...). Each parameter case in this family emits a fresh numbering part containing one <w:abstractNum> with a single <w:lvl ilvl='0'> whose <w:numFmt/@w:val> is the parameter token, plus one <w:num numId='1'> that references it. A paragraph in word/document.xml carries <w:numPr><w:numId val='1'/></w:numPr> to pull the item into that list. python-docx's WD_NUMBER_FORMAT enum only covers the common subset; the generator builds the numFmt via OxmlElement to bypass that restriction."
},
"fixtures": {
"machine": "docx/list-numbering-format--japanese-digital-ten-thousand"
},
"generator": {
"python": "scripts/gen_list_numbering_format.py",
"arg_template": "--numfmt {numfmt.val} --out fixtures/docx/list-numbering-format--{numfmt.id}.docx"
},
"_expansion": {
"parent_id": "docx/list-numbering-format",
"bindings": {
"numfmt": "japanese-digital-ten-thousand"
}
},
"assertions": [
{
"id": "list-numbering-format-japanese-digital-ten-thousand-numfmt-present",
"part": "word/numbering.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:lvl/w:numFmt[@w:val='japaneseDigitalTenThousand']",
"must": "exist",
"description": "The numbering part must contain a <w:lvl>/<w:numFmt> whose @w:val is 'japaneseDigitalTenThousand'."
}
]
}
