Open alignment-left.docx (plain, no bold), set font.bold = True on the first run, save. Assert the run now carries BOTH <w:b/> and <w:bCs/> (per the 2026.05.1 fix), the run's text and rsid attributes are preserved, and adjacent paragraph structure (paragraph-level w:jc alignment, paragraph's w14:paraId, w:sectPr) is intact. Note: python-docx's save-time _mirror_run_formatting_to_paragraph_mark deliberately copies w:b/w:bCs onto the paragraph mark's pPr/rPr so 'keep typing in bold' works in Word; this is documented behaviour and accommodated rather than flagged.
Library verdicts: python-docx: — docxjs: —
| Feature id | docx/modify-set-run-bold |
|---|---|
| Format | docx |
| Category | modify-in-place |
| Spec | ecma-376-5-part-1 § 17.3.2.1 w:b |
| ID / part | Predicate | XPath | python-docx | docxjs |
|---|---|---|---|---|
run-has-w-bword/document.xml | existThe mutated run must carry <w:b/>. | //w:r[w:t[normalize-space()='Left aligned paragraph']]/w:rPr/w:b | — | — |
run-has-w-bCsword/document.xml | existThe mutated run must also carry <w:bCs/> (2026.05.1 fix) — omitting it silently drops bold on complex-script runs when Word reopens the file. | //w:r[w:t[normalize-space()='Left aligned paragraph']]/w:rPr/w:bCs | — | — |
run-bold-val-not-falseword/document.xml | not-match = ^(0|false|off)$If w:val is present it must not disable bold. | //w:r[w:t[normalize-space()='Left aligned paragraph']]/w:rPr/w:b/@w:val | — | — |
run-text-preservedword/document.xml | existThe run's text must be unchanged. | //w:r/w:t[normalize-space()='Left aligned paragraph'] | — | — |
run-rsid-preservedword/document.xml | equal = 00000001The run's w:rsidR attribute must be preserved — bold flip must not re-author adjacent attributes. | //w:r[w:t[normalize-space()='Left aligned paragraph']]/@w:rsidR | — | — |
paragraph-alignment-preservedword/document.xml | equal = leftThe paragraph-level w:jc alignment — present as a SIBLING of the run we mutated — must be preserved. | //w:body/w:p[1]/w:pPr/w:jc/@w:val | — | — |
paragraph-paraId-preservedword/document.xml | equal = 81A65E80w14:paraId on the paragraph must be byte-identical to the source. | //w:body/w:p[1]/@w14:paraId | — | — |
run-count-unchangedword/document.xml | match = ^1(\.0+)?$Paragraph had 1 run before and must still have 1 run — bold flip must not split or duplicate runs. | count(//w:body/w:p[1]/w:r) | — | — |
sectPr-pgSz-preservedword/document.xml | equal = 12240Section properties (distant adjacent element) must be untouched. | //w:body/w:sectPr/w:pgSz/@w:w | — | — |
No render assertions declared.
scripts/gen_modify_set_run_bold.py
#!/usr/bin/env python3
"""Generate ``fixtures/docx/alignment-left--set-run-bold.docx``.
Thin wrapper around ``scripts.mutations.docx_set_run_bold``. Opens
``fixtures/docx/alignment-left.docx``, flips the first run to bold,
saves. Satisfies ``features/docx/modify-set-run-bold.json``.
"""
from __future__ import annotations
from pathlib import Path
from mutations import docx_set_run_bold
_REPO_ROOT = Path(__file__).resolve().parent.parent
_IN = _REPO_ROOT / "fixtures" / "docx" / "alignment-left.docx"
_OUT = _REPO_ROOT / "fixtures" / "docx" / "alignment-left--set-run-bold.docx"
def main() -> int:
docx_set_run_bold(_IN, _OUT)
print(_OUT)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "docx/modify-set-run-bold",
"title": "Modify: flip first run's bold on an existing .docx",
"format": "docx",
"category": "modify-in-place",
"summary": "Open alignment-left.docx (plain, no bold), set font.bold = True on the first run, save. Assert the run now carries BOTH <w:b/> and <w:bCs/> (per the 2026.05.1 fix), the run's text and rsid attributes are preserved, and adjacent paragraph structure (paragraph-level w:jc alignment, paragraph's w14:paraId, w:sectPr) is intact. Note: python-docx's save-time _mirror_run_formatting_to_paragraph_mark deliberately copies w:b/w:bCs onto the paragraph mark's pPr/rPr so 'keep typing in bold' works in Word; this is documented behaviour and accommodated rather than flagged.",
"roles": [
"authoring"
],
"spec": {
"source": "ecma-376-5-part-1",
"clause": "17.3.2.1",
"element": "w:b",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
"notes": "Word emits <w:b/> AND <w:bCs/> together on bold runs; omitting <w:bCs/> silently drops bold on Arabic/Hebrew/Thai when Word reopens. The 2026.05.1 python-docx fix couples these two setters."
},
"fixtures": {
"machine": "docx/alignment-left--set-run-bold"
},
"modify": {
"input_fixture": "fixtures/docx/alignment-left.docx",
"mutation": "docx_set_run_bold",
"output_fixture": "fixtures/docx/alignment-left--set-run-bold.docx"
},
"generator": {
"python": "scripts/gen_modify_set_run_bold.py"
},
"assertions": [
{
"id": "run-has-w-b",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:r[w:t[normalize-space()='Left aligned paragraph']]/w:rPr/w:b",
"must": "exist",
"description": "The mutated run must carry <w:b/>."
},
{
"id": "run-has-w-bCs",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:r[w:t[normalize-space()='Left aligned paragraph']]/w:rPr/w:bCs",
"must": "exist",
"description": "The mutated run must also carry <w:bCs/> (2026.05.1 fix) — omitting it silently drops bold on complex-script runs when Word reopens the file."
},
{
"id": "run-bold-val-not-false",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:r[w:t[normalize-space()='Left aligned paragraph']]/w:rPr/w:b/@w:val",
"must": "not-match",
"value": "^(0|false|off)$",
"description": "If w:val is present it must not disable bold."
},
{
"id": "run-text-preserved",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:r/w:t[normalize-space()='Left aligned paragraph']",
"must": "exist",
"description": "The run's text must be unchanged."
},
{
"id": "run-rsid-preserved",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:r[w:t[normalize-space()='Left aligned paragraph']]/@w:rsidR",
"must": "equal",
"value": "00000001",
"description": "The run's w:rsidR attribute must be preserved — bold flip must not re-author adjacent attributes."
},
{
"id": "paragraph-alignment-preserved",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:body/w:p[1]/w:pPr/w:jc/@w:val",
"must": "equal",
"value": "left",
"description": "The paragraph-level w:jc alignment — present as a SIBLING of the run we mutated — must be preserved."
},
{
"id": "paragraph-paraId-preserved",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"w14": "http://schemas.microsoft.com/office/word/2010/wordml"
},
"xpath": "//w:body/w:p[1]/@w14:paraId",
"must": "equal",
"value": "81A65E80",
"description": "w14:paraId on the paragraph must be byte-identical to the source."
},
{
"id": "run-count-unchanged",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "count(//w:body/w:p[1]/w:r)",
"must": "match",
"value": "^1(\\.0+)?$",
"description": "Paragraph had 1 run before and must still have 1 run — bold flip must not split or duplicate runs."
},
{
"id": "sectPr-pgSz-preserved",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:body/w:sectPr/w:pgSz/@w:w",
"must": "equal",
"value": "12240",
"description": "Section properties (distant adjacent element) must be untouched."
}
]
}
No rendered reference is available for this case.