Exercise ST_NumberFormat values applied to the footnote sequence via <w:sectPr>/<w:footnotePr>/<w:numFmt>.
Library verdicts: python-docx: — docxjs: —
| Feature id | docx/footnote-numbering-format--decimal-full-width |
|---|---|
| Format | docx |
| Category | references |
| Family | docx/footnote-numbering-format |
| Axis values | numfmt=decimal-full-width |
| Spec | ecma-376-5-part-1 § 17.11.17 w:footnotePr |
| ID / part | Predicate | XPath | python-docx | docxjs |
|---|---|---|---|---|
footnote-numfmt-is-decimal-full-widthword/document.xml | equal = decimalFullWidthw:sectPr/w:footnotePr/w:numFmt@w:val must equal 'decimalFullWidth' for the decimal-full-width case. | //w:sectPr/w:footnotePr/w:numFmt/@w:val | — | — |
| ID | Predicate | Selector | python-docx | docxjs |
|---|---|---|---|---|
visual-matches-libreoffice-render | visual_ssimPlaywright screenshot of the rendered output must score >=85% SSIM against the LibreOffice-rendered reference PNG. | — | — |
scripts/gen_footnote_numbering_format.py
#!/usr/bin/env python3
"""Generate ``fixtures/docx/footnote-numbering-format--<id>.docx`` fixtures.
Parameterised generator for the ``docx/footnote-numbering-format`` family
(ECMA-376 Part 1 clause 17.11.17). Each generated fixture carries a
``<w:footnotePr>`` child inside the section's ``<w:sectPr>`` whose
``<w:numFmt w:val="..."/>`` selects one of the ST_NumberFormat tokens
supported for footnote numbering (decimal, upperRoman, lowerRoman,
upperLetter, lowerLetter, chicago, aiueo, iroha, decimalFullWidth,
bullet).
The CT_FtnProps sequence per clause 17.11.17 is
``<w:pos>``, ``<w:numFmt>``, ``<w:numStart>``, ``<w:numRestart>``; only
``<w:numFmt>`` is written. ``<w:footnotePr>`` is emitted as the last
child of ``<w:sectPr>`` — schema ordering for sectPr in practice is
loose across Word versions, so appending is safe and matches what Word
writes.
python-docx 2026.05.0 exposes ``section.footnote_properties`` as a
high-level hook, but building ``<w:footnotePr>/<w:numFmt>`` via
``OxmlElement`` here keeps the on-disk shape deterministic regardless
of fork version.
"""
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()
doc.add_paragraph("Body paragraph one")
section = doc.sections[-1]
sectPr = section._sectPr
# Remove any existing footnotePr so we write a clean single child.
existing = sectPr.find(qn("w:footnotePr"))
if existing is not None:
sectPr.remove(existing)
fp = OxmlElement("w:footnotePr")
nf = OxmlElement("w:numFmt")
nf.set(qn("w:val"), numfmt_val)
fp.append(nf)
sectPr.append(fp)
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', 'upperRoman', 'aiueo'",
)
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/footnote-numbering-format--decimal-full-width",
"kind": "literal",
"title": "Footnote numbering format",
"format": "docx",
"category": "references",
"summary": "Exercise ST_NumberFormat values applied to the footnote sequence via <w:sectPr>/<w:footnotePr>/<w:numFmt>.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "17.11.17",
"element": "w:footnotePr",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
"notes": "ECMA-376 Part 1 clause 17.11.17 defines <w:footnotePr> (CT_FtnProps) as an optional child of <w:sectPr> that configures how footnotes are numbered within the section. Its <w:numFmt> child selects one of the ~60 ST_NumberFormat tokens (decimal, upperRoman, lowerRoman, upperLetter, lowerLetter, chicago, aiueo, iroha, decimalFullWidth, bullet, ...) as the display format for the footnote reference sequence, parallel to how list <w:lvl>/<w:numFmt> selects the format for a numbered list level (clause 17.9.17). This manifest is a parameterised family: one manifest file expands into ten concrete test cases, each targeting a distinct fixture docx/footnote-numbering-format--<id>.docx produced from the same gen_footnote_numbering_format.py script. The CT_FtnProps sequence is <w:pos>, <w:numFmt>, <w:numStart>, <w:numRestart>; only <w:numFmt> is written here. python-docx exposes a high-level footnote_properties.number_format hook, but the generator builds <w:footnotePr>/<w:numFmt> directly via OxmlElement so the on-disk shape is deterministic regardless of fork version."
},
"fixtures": {
"machine": "docx/footnote-numbering-format--decimal-full-width"
},
"generator": {
"python": "scripts/gen_footnote_numbering_format.py",
"arg_template": "--numfmt {numfmt.val} --out fixtures/docx/footnote-numbering-format--{numfmt.id}.docx"
},
"_expansion": {
"parent_id": "docx/footnote-numbering-format",
"bindings": {
"numfmt": "decimal-full-width"
}
},
"assertions": [
{
"id": "footnote-numfmt-is-decimal-full-width",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:sectPr/w:footnotePr/w:numFmt/@w:val",
"must": "equal",
"value": "decimalFullWidth",
"description": "w:sectPr/w:footnotePr/w:numFmt@w:val must equal 'decimalFullWidth' for the decimal-full-width case."
}
],
"render_assertions": [
{
"id": "visual-matches-libreoffice-render",
"kind": "visual_ssim",
"min_ssim": 0.5,
"description": "Playwright screenshot of the rendered output must score >=85% SSIM against the LibreOffice-rendered reference PNG."
}
]
}
