dsh-office-kit 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/assets/skills/excel-live-control/SKILL.md +119 -152
  2. package/assets/skills/excel-live-control/domain_guidance/financial_models.md +42 -58
  3. package/assets/skills/excel-live-control/domain_guidance/healthcare.md +36 -42
  4. package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +33 -34
  5. package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +34 -36
  6. package/assets/skills/excel-live-control/references/office-automation.md +127 -153
  7. package/assets/skills/excel-live-control/references/read_only_qna.md +40 -45
  8. package/assets/skills/excel-live-control/style_guidelines.md +56 -64
  9. package/assets/skills/excel-live-control/workflows/create_workflows.md +40 -47
  10. package/assets/skills/excel-live-control/workflows/edit_workflows.md +37 -45
  11. package/assets/skills/office-documents/SKILL.md +24 -60
  12. package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +5 -5
  13. package/assets/skills/office-documents/ooxml/comments.md +8 -8
  14. package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +10 -10
  15. package/assets/skills/office-documents/ooxml/rels_and_content_types.md +7 -7
  16. package/assets/skills/office-documents/ooxml/tracked_changes.md +12 -12
  17. package/assets/skills/office-documents/references/docx-structure.md +11 -13
  18. package/assets/skills/office-documents/references/template-elicitation.md +11 -13
  19. package/assets/skills/office-documents/references/writing-quality.md +20 -20
  20. package/assets/skills/office-documents/scripts/add_tracked_replacements.py +4 -3
  21. package/assets/skills/office-documents/scripts/docx_kit.py +68 -27
  22. package/assets/skills/office-documents/scripts/heading_audit.py +3 -4
  23. package/assets/skills/office-documents/scripts/internal_nav.py +3 -1
  24. package/assets/skills/office-documents/scripts/merge_docx_append.py +104 -0
  25. package/assets/skills/office-documents/scripts/table_geometry.py +13 -3
  26. package/assets/skills/office-documents/scripts/watermark_add.py +6 -5
  27. package/assets/skills/office-documents/scripts/watermark_audit_remove.py +6 -4
  28. package/assets/skills/office-documents/tasks/accessibility_a11y.md +16 -22
  29. package/assets/skills/office-documents/tasks/captions_crossrefs.md +23 -26
  30. package/assets/skills/office-documents/tasks/clean_tracked_changes.md +17 -22
  31. package/assets/skills/office-documents/tasks/comments_manage.md +9 -11
  32. package/assets/skills/office-documents/tasks/compare_diff.md +10 -16
  33. package/assets/skills/office-documents/tasks/create_edit.md +14 -16
  34. package/assets/skills/office-documents/tasks/equations.md +17 -28
  35. package/assets/skills/office-documents/tasks/fields_update.md +14 -16
  36. package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +19 -33
  37. package/assets/skills/office-documents/tasks/footnotes_endnotes.md +12 -14
  38. package/assets/skills/office-documents/tasks/forms_content_controls.md +16 -20
  39. package/assets/skills/office-documents/tasks/headings_numbering.md +17 -22
  40. package/assets/skills/office-documents/tasks/images_figures.md +15 -25
  41. package/assets/skills/office-documents/tasks/multi_doc_merge.md +13 -23
  42. package/assets/skills/office-documents/tasks/navigation_internal_links.md +18 -21
  43. package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +16 -22
  44. package/assets/skills/office-documents/tasks/protection_restrict_editing.md +19 -21
  45. package/assets/skills/office-documents/tasks/read_review.md +9 -20
  46. package/assets/skills/office-documents/tasks/redaction_anonymization.md +17 -18
  47. package/assets/skills/office-documents/tasks/sections_layout.md +16 -23
  48. package/assets/skills/office-documents/tasks/style_lint_normalize.md +18 -22
  49. package/assets/skills/office-documents/tasks/tables_spreadsheets.md +21 -31
  50. package/assets/skills/office-documents/tasks/templates_style_packs.md +17 -21
  51. package/assets/skills/office-documents/tasks/toc_workflow.md +10 -14
  52. package/assets/skills/office-documents/tasks/verify_render.md +11 -23
  53. package/assets/skills/office-documents/tasks/watermarks_background.md +10 -14
  54. package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +6 -6
  55. package/assets/skills/office-documents/troubleshooting/run_splitting.md +12 -12
  56. package/assets/skills/office-pdf/SKILL.md +36 -51
  57. package/assets/skills/office-pdf/references/form-fields.md +20 -20
  58. package/assets/skills/office-pdf/references/geometry.md +10 -10
  59. package/assets/skills/office-pdf/references/layout-quality.md +13 -13
  60. package/assets/skills/office-pdf/references/limits.md +12 -56
  61. package/assets/skills/office-pdf/tasks/annotations.md +5 -5
  62. package/assets/skills/office-pdf/tasks/create.md +6 -9
  63. package/assets/skills/office-pdf/tasks/edit-pages.md +14 -14
  64. package/assets/skills/office-pdf/tasks/forms.md +15 -15
  65. package/assets/skills/office-pdf/tasks/read.md +9 -9
  66. package/assets/skills/office-pdf/tasks/redact.md +18 -18
  67. package/assets/skills/office-pdf/tasks/stamp.md +10 -10
  68. package/assets/skills/office-presentations/SKILL.md +71 -91
  69. package/assets/skills/office-presentations/container_tools/preview_render.py +4 -1
  70. package/assets/skills/office-presentations/container_tools/render_slides.py +1 -1
  71. package/assets/skills/office-presentations/domain_guidance/finance.md +16 -16
  72. package/assets/skills/office-presentations/recipes/README.md +24 -35
  73. package/assets/skills/office-presentations/recipes/autolayout.md +12 -13
  74. package/assets/skills/office-presentations/recipes/connectors.md +11 -12
  75. package/assets/skills/office-presentations/recipes/images.md +11 -12
  76. package/assets/skills/office-presentations/recipes/master-and-layouts.md +18 -18
  77. package/assets/skills/office-presentations/recipes/native-charts.md +8 -8
  78. package/assets/skills/office-presentations/recipes/rich-text-runs.md +13 -13
  79. package/assets/skills/office-presentations/recipes/speaker-notes.md +7 -7
  80. package/assets/skills/office-presentations/recipes/tables.md +14 -14
  81. package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +9 -9
  82. package/assets/skills/office-presentations/recipes/theme-tokens.md +7 -8
  83. package/assets/skills/office-presentations/references/authoring-workflow.md +38 -43
  84. package/assets/skills/office-presentations/references/finalization.md +10 -12
  85. package/assets/skills/office-presentations/references/native-bullets.md +14 -18
  86. package/assets/skills/office-presentations/references/native-evidence.md +18 -32
  87. package/assets/skills/office-presentations/references/style-guidelines.md +27 -32
  88. package/assets/skills/office-presentations/references/templates.md +31 -60
  89. package/assets/skills/office-presentations/routing/google_slides.md +4 -4
  90. package/assets/skills/office-presentations/tasks/apply-annotations.md +17 -17
  91. package/assets/skills/office-presentations/tasks/charts-tables-notes.md +20 -23
  92. package/assets/skills/office-presentations/tasks/create-deck.md +19 -19
  93. package/assets/skills/office-presentations/tasks/edit-deck.md +14 -16
  94. package/assets/skills/office-presentations/tasks/inspect-and-verify.md +31 -34
  95. package/assets/skills/office-presentations/tasks/template-following.md +23 -28
  96. package/assets/skills/office-spreadsheets/SKILL.md +32 -39
  97. package/assets/skills/office-spreadsheets/recipes/charts.md +8 -26
  98. package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +9 -26
  99. package/assets/skills/office-spreadsheets/recipes/data-tables.md +7 -23
  100. package/assets/skills/office-spreadsheets/recipes/named-ranges.md +8 -21
  101. package/assets/skills/office-spreadsheets/recipes/number-formats.md +7 -23
  102. package/assets/skills/office-spreadsheets/recipes/sparklines.md +7 -20
  103. package/assets/skills/office-spreadsheets/references/charts.md +15 -34
  104. package/assets/skills/office-spreadsheets/references/domain/financial-models.md +10 -25
  105. package/assets/skills/office-spreadsheets/references/domain/healthcare.md +8 -19
  106. package/assets/skills/office-spreadsheets/references/domain/marketing.md +8 -22
  107. package/assets/skills/office-spreadsheets/references/domain/research.md +8 -19
  108. package/assets/skills/office-spreadsheets/references/formula-patterns.md +20 -50
  109. package/assets/skills/office-spreadsheets/references/style-guidelines.md +19 -44
  110. package/assets/skills/office-spreadsheets/references/workbook-structure.md +12 -35
  111. package/assets/skills/office-spreadsheets/routing/google-sheets.md +11 -26
  112. package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +10 -25
  113. package/assets/skills/office-spreadsheets/tasks/annotations.md +12 -30
  114. package/assets/skills/office-spreadsheets/tasks/create-workbook.md +5 -5
  115. package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +2 -2
  116. package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +5 -5
  117. package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +9 -9
  118. package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +10 -30
  119. package/assets/skills/shared/annotation-recipes.md +98 -120
  120. package/assets/skills/shared/common-workflow.md +70 -80
  121. package/dist/index.js +1 -1
  122. package/package.json +1 -1
  123. package/python/office_cli.py +38 -4
@@ -1,8 +1,6 @@
1
1
  # Comments
2
2
 
3
- **Goal.** Add a review comment, read the comments someone else left, reply or resolve them, and remove comments before the document goes out.
4
-
5
- Comments are not text: each one is a `w:comment` in `word/comments.xml`, anchored in the body by a `w:commentRangeStart` / `w:commentRangeEnd` pair plus a `w:commentReference` run. Editing the anchor without the comments part (or the reverse) leaves a document Word reports as corrupt.
3
+ Add, read, reply to, resolve or remove review comments. A comment is not text: each is a `w:comment` in `word/comments.xml`, anchored by a `w:commentRangeStart` / `w:commentRangeEnd` pair plus a `w:commentReference` run. Editing the anchor without the comments part, or the reverse, leaves a file Word reports as corrupt.
6
4
 
7
5
  ## Add
8
6
 
@@ -11,7 +9,7 @@ Comments are not text: each one is a `w:comment` in `word/comments.xml`, anchore
11
9
  --author "Review" --add "Renewals=Check the source of this claim" --add "42 new accounts=Who verified this?"
12
10
  ```
13
11
 
14
- Each `--add` is `NEEDLE=COMMENT`. The needle is matched inside a paragraph; the anchored range covers exactly the matched text, so pick a distinctive phrase. `--ignore_case` relaxes matching, `--require_all` writes nothing and exits 1 if any needle is missing — use it when the comment set must be complete.
12
+ Each `--add` is `NEEDLE=COMMENT`, matched inside a paragraph; the anchored range covers exactly the matched text, so pick a distinctive phrase. `--ignore_case` relaxes matching; `--require_all` writes nothing and exits 1 if any needle is missing — for comment sets that must be complete.
15
13
 
16
14
  ## Extract
17
15
 
@@ -19,7 +17,7 @@ Each `--add` is `NEEDLE=COMMENT`. The needle is matched inside a paragraph; the
19
17
  "<python>" "<skill>/scripts/comments_extract.py" "<file.docx>" --out "<comments.json>"
20
18
  ```
21
19
 
22
- Returns `id`, `author`, `initials`, `date`, `text`, `anchored_text`, `part` and `resolved`. An empty array means the file has no comments — do not assume comments exist because the user mentioned them.
20
+ Returns `id`, `author`, `initials`, `date`, `text`, `anchored_text`, `part` and `resolved`. An empty array means the file has no comments; do not assume they exist because the user mentioned them.
23
21
 
24
22
  ## Patch or resolve
25
23
 
@@ -45,15 +43,15 @@ Returns `id`, `author`, `initials`, `date`, `text`, `anchored_text`, `part` and
45
43
  "<python>" "<skill>/scripts/comments_strip.py" "<file.docx>" --out "<file-clean.docx>"
46
44
  ```
47
45
 
48
- Removes every anchor, drops `word/comments.xml`, its relationship and content-type override. Removing comments is required before sharing a file with a customer; check `privacy_scrub_metadata.md` too.
46
+ Removes every anchor and drops `word/comments.xml` with its relationship and content-type override. Required before sharing a file with a customer; see also `privacy_scrub_metadata.md`.
49
47
 
50
48
  ## Verify
51
49
 
52
- `comments_extract.py` on the output (count and anchors), then render the page the comment sat on — a stripped anchor that leaves an empty run can show as a stray space.
50
+ `comments_extract.py` on the output (count and anchors), then render the page the comment sat on: a stripped anchor that leaves an empty run can show as a stray space.
53
51
 
54
52
  ## Pitfalls
55
53
 
56
- - A comment anchored to a paragraph mark rather than text shows no `anchored_text`; that is normal for "comment on the whole paragraph".
57
- - Two comments cannot share an id. `comments_add.py` continues from the highest existing id.
58
- - The comment body is ordinary paragraphs, so a long comment can contain several; `comments_extract.py` joins them with newlines.
59
- - Replying in-thread needs Word's `commentsExtended` part. Anchoring a new comment on the same phrase is the portable equivalent.
54
+ - A comment anchored to a paragraph mark rather than to text shows no `anchored_text` — normal for "comment on the whole paragraph".
55
+ - Two comments cannot share an id; `comments_add.py` continues from the highest existing id.
56
+ - The body is ordinary paragraphs, so a long comment can hold several; `comments_extract.py` joins them with newlines.
57
+ - Threaded replies need Word's `commentsExtended` part; anchoring a new comment on the same phrase is the portable equivalent.
@@ -1,40 +1,34 @@
1
1
  # Compare two versions
2
2
 
3
- **Goal.** Say what actually changed between two files, with evidence — not "the text differs somewhere".
3
+ Two comparisons answer different questions: text-level (which words changed) and page-level (what the reader sees). A style-definition change can leave every word identical and move every paragraph.
4
4
 
5
- Two useful comparisons: text-level (what words changed) and page-level (what the reader sees changed). Use both; a change in a style definition can leave the text identical and move every paragraph.
6
-
7
- ## Page-level diff
5
+ ## Page-level
8
6
 
9
7
  ```bash
10
8
  "<python>" "<skill>/scripts/render_and_diff.py" "<before.docx>" "<after.docx>" --outdir "<render-dir>" --dpi 120
11
9
  ```
12
10
 
13
- Renders both through the helper CLI, pairs the pages, and writes `diff-NN.png` coloured by pixel difference. Reports which pages changed, the fraction of pixels differing, and a page-count delta.
14
-
15
- The exit code is 3 when no renderer is available: render both files another way, or compare text only and say which comparison you could not run.
11
+ Renders both files through the helper CLI, pairs pages, writes `diff-NN.png` coloured by pixel difference, and reports changed pages, the fraction of differing pixels and the page-count delta. Exit code 3 means no renderer: render another way, or compare text only and say which comparison you could not run.
16
12
 
17
- ## Text-level diff
13
+ ## Text-level
18
14
 
19
15
  ```bash
20
16
  "<python>" "<skill>/scripts/docx_kit.py" text '<before.docx>' > before.json
21
17
  "<python>" "<skill>/scripts/docx_kit.py" text '<after.docx>' > after.json
22
18
  ```
23
19
 
24
- Diff the two paragraph dumps (any diff tool, or a few lines of Python). This is the reliable way to separate content changes from layout changes.
20
+ Then diff the paragraph dumps with any diff tool or a few lines of Python. This is the reliable way to separate content from layout changes.
25
21
 
26
22
  ## Review mode versus diff mode
27
23
 
28
- If the user wants the *document* to carry the changes, use tracked changes (`tasks/clean_tracked_changes.md`) rather than a report: the reviewer accepts or rejects inside Word. A diff is evidence for a conversation; tracked changes are the artefact.
29
-
30
- For a version that arrived with markup, `accept_tracked_changes.py --mode report` is the change list, with reviewer names and where each edit sits.
24
+ If the document should carry the changes, use tracked changes (`tasks/clean_tracked_changes.md`): a diff is evidence for a conversation, tracked changes are the artefact. For a version that arrived with markup, `accept_tracked_changes.py --mode report` is the change list with reviewer names and positions.
31
25
 
32
26
  ## Verify
33
27
 
34
- Open the diff PNGs with `read_image` and look at them: a coloured block on a page that should not have changed usually means a font substitution or a margin change, not a content change. Cross-check with the text diff before writing a conclusion.
28
+ Open the diff PNGs with `read_image`. A coloured block on a page that should not change usually means font substitution or a margin change — cross-check the text diff before concluding.
35
29
 
36
30
  ## Pitfalls
37
31
 
38
- - Different renderers paginate differently; compare renders made by the same renderer and the same settings.
39
- - Metadata and revision ids differ between saves and never appear on a page — a text diff of `word/document.xml` is noisy for that reason.
40
- - An image replaced in place keeps the same relationship and page position; only a pixel comparison notices.
32
+ - Different renderers paginate differently; compare with the same renderer and settings.
33
+ - Metadata and revision ids differ between saves and never appear on a page, so a raw `word/document.xml` diff is mostly noise.
34
+ - An image replaced in place keeps its relationship and position — only a pixel comparison notices.
@@ -1,6 +1,6 @@
1
1
  # Create or edit a document
2
2
 
3
- **Goal.** Produce a `.docx` that opens cleanly in Word, with real heading styles, real lists and real tables, then change it later without destroying what is already there.
3
+ Produce a `.docx` that opens cleanly in Word with real heading styles, lists and tables — and change it later without destroying what is there.
4
4
 
5
5
  ## Create
6
6
 
@@ -35,7 +35,7 @@ The command prints counts for headings, paragraphs, list items, tables, images a
35
35
 
36
36
  ## Edit
37
37
 
38
- The helper CLI does not mutate documents. Every edit is a script that reads the original, writes a copy, and refuses to overwrite its input. Omit `--out` and the copy lands next to the source as `<name>-edited.docx`.
38
+ The helper CLI never mutates a document: every edit reads the original, writes a copy, and refuses to overwrite its input. Without `--out` the copy lands next to the source as `<name>-edited.docx`.
39
39
 
40
40
  Replace text — body, tables, and every header and footer:
41
41
 
@@ -44,7 +44,7 @@ Replace text — body, tables, and every header and footer:
44
44
  --find "18%" --replace "21%" --find "Hardware flat" --replace "Hardware steady"
45
45
  ```
46
46
 
47
- `--find` / `--replace` are paired in order; `--spec edits.json` takes `{"replace": [{"find": "...", "replace": "..."}]}` instead. The command prints a per-pair count. A match inside one run keeps that run's formatting; a match that straddles runs collapses the paragraph into its first run, so keep needles inside a single styled span when mixed formatting matters. `--require-all` exits 1 if a pair matched nothing, which is the check you want for a scripted edit.
47
+ `--find` / `--replace` pair up in order; `--spec edits.json` takes `{"replace": [{"find": "...", "replace": "..."}]}` instead. The command prints a count per pair. A match inside one run keeps that run's formatting; a match straddling runs collapses the paragraph into its first run, so keep needles inside a single styled span when formatting matters. `--require-all` exits 1 if a pair matched nothing — the check for a scripted edit.
48
48
 
49
49
  Append blocks — the same block types `docx create` accepts:
50
50
 
@@ -56,26 +56,24 @@ Append blocks — the same block types `docx create` accepts:
56
56
  [{ "type": "heading", "level": 1, "text": "Appendix" }, { "type": "table", "headers": ["Region", "Revenue"], "rows": [["EMEA", "4.1M"]] }]
57
57
  ```
58
58
 
59
- `--spec` takes a full document spec and appends its `blocks` array. The builder is the CLI's own, so anything that works in `docx create` works here, including images (`path`, `width`) and equations (`latex`).
59
+ `--spec` takes a full document spec and appends its `blocks` array. The builder is the CLI's own, so anything that works in `docx create` works here, images (`path`, `width`) and equations (`latex`) included.
60
60
 
61
61
  Set properties:
62
62
 
63
63
  ```bash
64
- "<python>" "<skill>/scripts/core_properties.py" --input "<original.docx>" --out "<original-edited.docx>" \
64
+ "<python>" "<skill>/scripts/core_properties.py" --input "<original.docx>" --out "<original-edited.docx" \
65
65
  --title "Regional revenue review (final)" --author "Finance" --keywords "revenue, FY26"
66
66
  ```
67
67
 
68
- `--show` reports the current properties without writing; the same names work in `--spec core.json` under a `core` object.
69
-
70
- Three independent scripts means a pipeline writes an intermediate copy (append, then properties). That is deliberate: each step is re-runnable and its output is a valid document you can inspect.
68
+ `--show` reports the current properties without writing; the same names work in `--spec core.json` under a `core` object. Each step writes a valid document, so a pipeline is append, then properties, and every stage is re-runnable and inspectable.
71
69
 
72
70
  ## When the CLI is not enough
73
71
 
74
72
  | Need | Route |
75
73
  |---|---|
76
- | Edit inside a header, footer, footnote or comment | `scripts/docx_kit.py xml <file> <part>` to inspect, then the snippet below or a purpose script |
77
- | Mark edits as tracked changes | `scripts/add_tracked_replacements.py`, `tasks/clean_tracked_changes.md` |
78
- | Header date, footer page numbers, first hyperlink | `scripts/docx_ooxml_patch.py` |
74
+ | Edit inside a header, footer, footnote or comment | `docx_kit.py xml <file> <part>` to inspect, then the snippet below |
75
+ | Mark edits as tracked changes | `add_tracked_replacements.py` (`tasks/clean_tracked_changes.md`) |
76
+ | Header date, footer page numbers, first hyperlink | `docx_ooxml_patch.py` |
79
77
  | Replace a paragraph but keep its formatting | `python-docx` snippet below |
80
78
 
81
79
  ```bash
@@ -93,15 +91,15 @@ d.save(r"<file-edited.docx>")
93
91
  PY
94
92
  ```
95
93
 
96
- Editing `w:t` text directly (via `docx_kit`) is safe for plain word swaps; anything that changes paragraph structure should go through `python-docx` so the surrounding XML stays consistent.
94
+ Plain word swaps can edit `w:t` through `docx_kit` directly; anything that changes paragraph structure goes through `python-docx` so the surrounding XML stays consistent.
97
95
 
98
96
  ## Verify
99
97
 
100
- `docx read` to confirm text and counts, then render and look at every page (`tasks/verify_render.md`). A spec that produces three pages instead of two means the table or margins need work, not that the renderer is wrong.
98
+ `docx read` to confirm text and counts, then render every page (`tasks/verify_render.md`). A spec that produces three pages instead of two means the table or margins need work, not that the renderer is wrong.
101
99
 
102
100
  ## Pitfalls
103
101
 
104
- - Deleting and retyping a heading loses its outline level; edit the text, keep the style.
102
+ - Deleting and retyping a heading loses its outline level; edit the text and keep the style.
105
103
  - `append_blocks.py` inserts before the final section properties, so a trailing landscape section or page setup keeps working.
106
- - Replacing text that lives in a field result changes only the cached value; the field recalculates on open in Word.
107
- - Copying content between documents by hand drops styles and relationships — use `scripts/merge_docx_append.py`.
104
+ - Replacing text in a field result changes only the cached value; the field recalculates on open in Word.
105
+ - Copying content between documents by hand drops styles and relationships — use `merge_docx_append.py`.
@@ -1,59 +1,48 @@
1
1
  # Equations
2
2
 
3
- **Goal.** Displayed mathematics that Word treats as maths: selectable, reflowable, editable — not a picture and not italic text.
4
-
5
- Word stores native equations as OMML inside `m:oMathPara`/`m:oMath`. The helper CLI converts LaTeX to OMML on creation; a picture of an equation cannot be edited by the user and scales badly.
3
+ Word stores native equations as OMML inside `m:oMathPara`/`m:oMath`. The CLI converts LaTeX to OMML on creation. A picture of an equation cannot be edited and scales badly.
6
4
 
7
5
  ## Create
8
6
 
9
- ```bash
10
- "<python>" "<cli>" docx create --spec "<spec.json>" --out "<out.docx>"
11
- ```
7
+ `docx create --spec` block:
12
8
 
13
9
  ```json
14
- {
15
- "blocks": [
16
- { "type": "heading", "level": 1, "text": "Model" },
17
- { "type": "equation", "latex": "E = mc^2" },
18
- { "type": "equation", "latex": "\\frac{a^2+b^2}{c^2}" }
19
- ]
20
- }
10
+ { "type": "equation", "latex": "\\frac{a^2+b^2}{c^2}" }
21
11
  ```
22
12
 
23
- The command reports `equations` and `equation_fallbacks`. Non-zero fallbacks mean some expression was emitted as italic text because the converter could not handle it — check the counter, then simplify the LaTeX (the converter covers fractions, superscripts, subscripts, roots, sums, Greek letters and common operators).
13
+ Supported: fractions, superscripts, subscripts, roots, sums, Greek letters and common operators. The command reports `equations` and `equation_fallbacks`. A non-zero `equation_fallbacks` means the expression was emitted as italic text: check the counter, then simplify the LaTeX.
24
14
 
25
- Displayed equations belong in their own paragraph; inline maths is ordinary runs plus OMML, which is harder to place and easy to break.
15
+ Displayed equations belong in their own paragraph. Inline maths is ordinary runs plus OMML, harder to place and easy to break.
26
16
 
27
- ## Check what a file contains
17
+ ## Check the contents
28
18
 
29
19
  ```bash
30
20
  "<python>" "<cli>" docx read --input "<file.docx>"
31
21
  "<python>" "<skill>/scripts/docx_kit.py" xml '<file.docx>' word/document.xml | grep -c "oMath"
32
22
  ```
33
23
 
34
- `oMath` in the XML is the honest test. Text that merely looks mathematical (italic Times, a slash) is not an equation and will reflow badly.
24
+ `oMath` in the XML is the honest test; lookalike italic Times text is not an equation.
35
25
 
36
26
  ## When the converter cannot express it
37
27
 
38
- Build the OMML by hand for the specific construct, or split the expression: a matrix or a piecewise definition is often clearer as a small table than as one enormous formula. If neither works, render the expression as an image with a caption and tell the user it is no longer editable.
28
+ - Build the OMML by hand for that construct.
29
+ - Split the expression: a matrix or piecewise definition is often clearer as a small table.
30
+ - Otherwise render it as an image with a caption and tell the user it is no longer editable.
39
31
 
40
- The runtime ships the `latex2mathml` and `mathml2omml` packages, so a custom snippet can produce OMML directly:
32
+ The runtime ships `latex2mathml` and `mathml2omml`, so a custom snippet can produce OMML:
41
33
 
42
- ```bash
43
- "<python>" - <<'PY'
34
+ ```python
44
35
  from latex2mathml.converter import convert
45
36
  from mathml2omml import convert as to_omml
46
- mathml = convert(r"\sum_{i=1}^{n} x_i")
47
- print(to_omml(mathml)[:200])
48
- PY
37
+ print(convert(r"\sum_{i=1}^{n} x_i")[:200])
49
38
  ```
50
39
 
51
40
  ## Verify
52
41
 
53
- Render the page and look at the equation: fractions stacked, limits above and below the operator, Greek letters as glyphs not boxes, and the equation not clipped at the right margin. A fallback shows up as italic Latin text where a symbol should be.
42
+ Render: fractions stacked, limits above and below, Greek glyphs instead of boxes, nothing clipped at the right margin. A fallback shows as italic Latin text.
54
43
 
55
44
  ## Pitfalls
56
45
 
57
- - An equation inside a table cell or a caption is legal but almost always the wrong layout decision.
58
- - Copying OMML between documents is safe; copying a picture of it is not.
59
- - Numeric labels belong in the text (`(3)` right-aligned), not inside the equation object, if the user will renumber later.
46
+ - An equation in a table cell or caption is legal but usually wrong.
47
+ - Copying OMML between documents is safe; copying a picture is not.
48
+ - Numeric labels belong in the text (`(3)` right-aligned) if the user will renumber.
@@ -1,8 +1,6 @@
1
- # Fields and field updates
1
+ # Update or freeze fields
2
2
 
3
- **Goal.** Find out what a document computes, and decide whether to keep it live or freeze it.
4
-
5
- A field is an instruction the reader's Word evaluates: `SEQ` numbers captions, `REF` repeats a bookmark, `PAGE`/`NUMPAGES` count pages, `TOC` builds the contents list, `DATE` prints today. Until Word recalculates it, the file shows a cached result — which is why a TOC in a freshly edited file can look stale.
3
+ A field is an instruction Word evaluates: `SEQ` numbers captions, `REF` repeats a bookmark, `PAGE`/`NUMPAGES` count pages, `TOC` builds contents, `DATE` prints today. The reader sees a cached result until something recalculates.
6
4
 
7
5
  ## Inspect
8
6
 
@@ -10,35 +8,35 @@ A field is an instruction the reader's Word evaluates: `SEQ` numbers captions, `
10
8
  "<python>" "<skill>/scripts/fields_report.py" "<file.docx>" --max-examples 5
11
9
  ```
12
10
 
13
- Reports the instruction, whether the field is a simple (`w:fldSimple`, one attribute) or complex field (begin/separate/end runs), and the cached result. `docx_kit.py xml` shows the raw runs when the report is not enough:
11
+ The report gives each instruction, whether the field is simple (`w:fldSimple`, one attribute) or complex (begin/separate/end runs), and the cached result. To read the raw runs yourself, dump `docx_kit.py xml` for `word/document.xml` and filter `instrText`:
14
12
 
15
13
  ```bash
16
14
  "<python>" "<skill>/scripts/docx_kit.py" xml '<file.docx>' word/document.xml | grep -o 'instrText[^<]*' | head
17
15
  ```
18
16
 
19
- ## Freeze a field to its current value
17
+ ## Freeze
20
18
 
21
19
  ```bash
22
20
  "<python>" "<skill>/scripts/fields_materialize.py" "<file.docx>" --out "<file-static.docx>" --only SEQ REF
23
- "<python>" "<skill>/scripts/flatten_ref_fields.py" "<file.docx>" --out "<file-static.docx>"
21
+ "<python>" "<skill>/scripts/flatten_ref_fields.py" "<file.docx>" --out "<file-static.docx"
24
22
  ```
25
23
 
26
- Materialising replaces the field span with one run holding the cached text, keeping the run formatting. `flatten_ref_fields.py` is the narrow version for `REF` and `PAGEREF` only.
24
+ Materialising replaces the field span with one run holding the cached text and keeps the run formatting. `flatten_ref_fields.py` is the narrower tool for `REF`/`PAGEREF` only.
25
+
26
+ Freeze when a reader must see exactly this number, when the target bookmark is about to be deleted, or when a downstream tool mangles fields. Keep fields live when the document will be edited again: a frozen caption number stops following its table.
27
27
 
28
- Freeze when: the document is going to a reader who must see exactly this number, the target bookmark is being deleted, or a downstream tool mangles fields. Keep it live when the document will be edited again — a frozen caption number stops following its table.
28
+ ## Keep fields live
29
29
 
30
- ## Keep it live
30
+ Set `w:updateFields val="true"` in `word/settings.xml`; `insert_toc.py` does it for a new TOC, and you add the element yourself otherwise — the first open then prompts the user. LibreOffice and the renderer also refresh fields, so a stale cached value usually means the document was never recalculated, not that fields are broken.
31
31
 
32
- - Word recalculates on open when `w:updateFields val="true"` is set in `word/settings.xml`. `insert_toc.py` sets it for a new TOC; if you need it for an existing file, add the element yourself and tell the user the first open will prompt.
33
- - LibreOffice and the renderer also refresh fields; a stale cached value in a preview usually means the field was never recalculated, not that the field is broken.
34
- - `insert_ref_fields.py` converts `[[REF:bookmark]]` placeholders into real `REF` fields, which is the reliable way to build cross-references after restructuring.
32
+ `insert_ref_fields.py` converts `[[REF:bookmark]]` placeholders into real REF fields — the reliable way to build cross-references after a restructure.
35
33
 
36
34
  ## Verify
37
35
 
38
- `fields_report.py` after the change: frozen fields disappear from the report, live ones keep their instruction. Render and read the page to confirm the number the reader sees.
36
+ Run `fields_report.py` again: frozen fields disappear from the report, live ones keep their instruction. Then render or read the page.
39
37
 
40
38
  ## Pitfalls
41
39
 
42
40
  - A field split across paragraphs is invalid; keep begin/separate/end together.
43
- - `w:fldSimple` and complex fields both appear in the report — do not assume the absence of `w:fldSimple` means there are no fields.
44
- - Copying a field to another document copies the instruction but not the bookmark it refers to; recreate it there.
41
+ - Both `w:fldSimple` and complex fields appear in the report; handle both.
42
+ - Copying a field copies the instruction but not its bookmark.
@@ -1,8 +1,8 @@
1
- # Fixtures and edge cases
1
+ # Fixtures for edge cases
2
2
 
3
- **Goal.** Have a file that contains the awkward cases *before* you are asked to handle them, so a fix is tested against a 1600-word paragraph and not only against clean prose.
3
+ Build a file containing the awkward cases before being asked to handle them: a fix proven on clean prose may still mangle a 1600-word paragraph.
4
4
 
5
- ## Build the fixture set
5
+ ## Build
6
6
 
7
7
  ```bash
8
8
  "<python>" "<skill>/scripts/make_fixtures.py" --outdir "<work-dir>" --only all
@@ -10,41 +10,27 @@
10
10
 
11
11
  Writes:
12
12
 
13
- | File | Contains |
14
- |---|---|
15
- | `tracked.docx` | A replacement as real `w:del`+`w:ins` markup, tracking switched on |
16
- | `watermark.docx` | A DRAFT watermark in the header |
17
- | `comments.docx` | One anchored comment in `word/comments.xml` |
18
- | `edge_cases.docx` | Empty paragraph, H1 to H3 jump, 1600-word paragraph, email and phone, 2×10 unstyled table, image with no alt text |
13
+ - `tracked.docx` — a replacement as real `w:del`+`w:ins`, tracking on.
14
+ - `watermark.docx` — a DRAFT watermark in the header.
15
+ - `comments.docx` — one anchored comment in `word/comments.xml`.
16
+ - `edge_cases.docx` — an empty paragraph, an H1→H3 jump, a 1600-word paragraph, an email and a phone number, a 2×10 unstyled table, and an image with no alt text.
19
17
 
20
- `--only tracked|watermark|comments|edge` builds one.
18
+ `--only tracked|watermark|comments|edge` builds a single file. These are test inputs, not templates: never ship them.
21
19
 
22
- These are inputs for testing a change, not templates. Do not ship them.
20
+ ## Cases that break naive edits
23
21
 
24
- ## The cases that break naive edits
22
+ - **Long paragraphs.** A replacement that rebuilds the paragraph loses run formatting over thousands of characters. Check with `read_image`, not only a text dump.
23
+ - **Nested tables.** Text inside `table > cell > table > cell` is missed by `document.paragraphs`; walk `cell.tables` or use the pack scripts.
24
+ - **Header and footer text.** Body find-and-replace never sees it.
25
+ - **Tables without a style.** Borders come from the renderer default and change elsewhere: apply `Table Grid` or explicit `#D9D9D9` borders.
26
+ - **Images with no alt text.** An accessibility finding and a sign of pasted content.
27
+ - **Manual spacing.** Empty paragraphs and spaces render differently; normalize first (`tasks/style_lint_normalize.md`).
28
+ - **Mixed page sizes.** A landscape section in a portrait document confuses headers and footers; `section_audit.py` reports it.
25
29
 
26
- - **Long paragraphs** — a replacement that rebuilds the paragraph loses run formatting across thousands of characters; check the result with `read_image`, not only with a text dump.
27
- - **Nested tables** — text inside `table > cell > table > cell` is missed by `document.paragraphs`; walk `cell.tables` or use the pack scripts, which do.
28
- - **Header/footer text** — a find-and-replace over the body never sees it.
29
- - **Tables without a style** — borders come from the renderer default and change when the file is opened elsewhere; apply `Table Grid` or set explicit `#D9D9D9` borders.
30
- - **Images with no alt text** — an accessibility finding, and also a sign the image was pasted rather than placed.
31
- - **Manual spacing** — a document padded with empty paragraphs and spaces looks right in one renderer and wrong in the next; normalize instead (`tasks/style_lint_normalize.md`).
32
- - **Mixed page sizes** — a landscape section inside a portrait document confuses footers and headers; `section_audit.py` reports it.
30
+ ## Regression pass
33
31
 
34
- ## Regression pass before delivery
35
-
36
- ```bash
37
- for f in "<work-dir>"/*.docx; do
38
- echo "== $f"
39
- "<python>" "<skill>/scripts/heading_audit.py" "$f" || true
40
- "<python>" "<skill>/scripts/style_lint.py" "$f" || true
41
- "<python>" "<skill>/scripts/a11y_audit.py" "$f" || true
42
- "<python>" "<skill>/scripts/section_audit.py" "$f" || true
43
- done
44
- ```
45
-
46
- An audit that now reports nothing where it previously reported a real problem is the signal that a fix regressed. Keep the fixture out of the user's workspace; put it under the temp directory.
32
+ Loop over `"<work-dir>"/*.docx` running `heading_audit.py`, `style_lint.py`, `a11y_audit.py` and `section_audit.py`, each with `|| true`. An audit that now reports nothing where it used to report a real problem is a regression. Keep fixtures under the temp directory, out of the user's workspace.
47
33
 
48
34
  ## Verify
49
35
 
50
- Whatever the script produced, render it. Fixtures exist to be looked at: a 1600-word paragraph and a table that is too wide both fail visually first.
36
+ Render whatever the script produced — fixtures exist to be looked at. The long paragraph and the too-wide table fail visually first.
@@ -1,18 +1,16 @@
1
1
  # Footnotes and endnotes
2
2
 
3
- **Goal.** Attach a reference note at exactly one place in the text, and keep the note body in the notes part rather than in the body.
3
+ Notes live outside the text flow: `word/footnotes.xml` (or `word/endnotes.xml`) holds the note text, a `w:footnoteReference w:id="N"` run in the body points at it, and the part needs the separator notes (ids -1 and 0) or Word repairs the file.
4
4
 
5
- Notes live outside the flow: `word/footnotes.xml` (or `word/endnotes.xml`) holds the text, and a `w:footnoteReference w:id="N"` run in the body points at it. The part also needs the separator notes (ids -1 and 0) or Word repairs the file.
5
+ ## Insert
6
6
 
7
- ## Insert a note
8
-
9
- Put a marker where the reference belongs, then convert it:
7
+ Put a marker where the reference belongs, then run:
10
8
 
11
9
  ```bash
12
- "<python>" "<skill>/scripts/insert_note.py" "<file.docx>" --kind footnote --text "Source: ledger extract, June." --out "<file-notes.docx>"
10
+ "<python>" "<skill>/scripts/insert_note.py" "<file.docx>" --kind footnote --text "Source: ledger extract, June." --out "<file-notes.docx"
13
11
  ```
14
12
 
15
- The marker defaults to `[[NOTE]]`; change it with `--marker`. The script creates the notes part (with separator and continuation-separator entries) if it does not exist, allocates the next free id, splits the surrounding run so only the marker is replaced by the reference, and styles the note text with `FootnoteText` and the reference with `FootnoteReference`. `--kind endnote` writes to the endnotes part instead.
13
+ The marker defaults to `[[NOTE]]` (`--marker`). The script creates the notes part with separator and continuation-separator entries when they are missing, allocates the next free id, splits the surrounding run so only the marker is replaced, and styles the note text `FootnoteText` and the reference `FootnoteReference`. `--kind endnote` writes the endnotes part instead.
16
14
 
17
15
  ## Report
18
16
 
@@ -20,19 +18,19 @@ The marker defaults to `[[NOTE]]`; change it with `--marker`. The script creates
20
18
  "<python>" "<skill>/scripts/footnotes_report.py" "<file.docx>"
21
19
  ```
22
20
 
23
- Lists each note's text, how many references point at it, and which references dangle or which notes are orphaned. Orphans appear when content is copied between documents; dangling references appear when a note is deleted but its marker stays.
21
+ Lists each note's text and reference count, dangling references (note deleted, marker kept) and orphaned notes (no reference — usually copied content).
24
22
 
25
23
  ## Renumbering
26
24
 
27
- Note ids are numbers, but Word displays them sequentially by position, so inserting a note in the middle renumbers the rest automatically. Do not try to keep ids in display order — keep them unique.
25
+ Ids are numbers, but Word displays notes sequentially by position, so inserting in the middle renumbers automatically. Keep ids unique, not in display order.
28
26
 
29
27
  ## Verify
30
28
 
31
- Render the page with the note and read the note area: the marker should sit immediately after the word it annotates, with no space before it, and the note text should match. Also check the last page — an endnote section that lands after the signature block is a layout defect.
29
+ Render the page with the note. The marker sits immediately after the word it annotates, with no space before it, and the note text matches. Check the last page too: an endnote section after the signature block is a layout defect.
32
30
 
33
31
  ## Pitfalls
34
32
 
35
- - A footnote reference inside a table cell is allowed; inside a header it is not.
36
- - Copying a paragraph with `python-docx` copies the reference but not the note body — recreate the note in the target document.
37
- - Deleting note text without deleting the reference leaves a broken document that opens with a repair prompt.
38
- - Field instructions are not allowed in the note separator; keep the built-in separator notes untouched.
33
+ - A footnote reference is allowed in a table cell, not in a header.
34
+ - Copying a paragraph with python-docx copies the reference but not the note body.
35
+ - Deleting note text without its reference leaves a repair prompt.
36
+ - Field instructions are not allowed in the note separator; leave the built-in separators untouched.
@@ -1,48 +1,44 @@
1
- # Forms and content controls
1
+ # Content controls (form fields)
2
2
 
3
- **Goal.** Fill or create a structured field — a name, a date, a choice — as a real content control, so the value is labelled in the XML instead of being plain text between two labels.
3
+ A content control is a `w:sdt` with a `w:sdtPr` (tag, alias, id, type) and a `w:sdtContent`. The tag is the machine name; the alias is what Word shows in the properties panel.
4
4
 
5
- A content control is `w:sdt` with `w:sdtPr` (tag, alias, id, type) and `w:sdtContent`. The tag is the machine name; the alias is what Word shows in the properties panel.
6
-
7
- ## List what exists
5
+ ## List
8
6
 
9
7
  ```bash
10
8
  "<python>" "<skill>/scripts/content_controls.py" list "<file.docx>"
11
9
  ```
12
10
 
13
- Reports each control's part, tag, alias, id, type and current text, across the body, headers and footers. `--no_headers_footers` limits it to the body.
11
+ Reports part, tag, alias, id, type and current text across the body, headers and footers. `--no_headers_footers` limits the scan to the body.
14
12
 
15
13
  ## Create
16
14
 
17
15
  ```bash
18
- "<python>" "<skill>/scripts/content_controls.py" create "<file.docx>" \
19
- --placeholder "Client name" --tag client_name --alias "Client name" --out "<file-form.docx>"
16
+ "<python>" "<skill>/scripts/content_controls.py" create "<file.docx>" --placeholder "Client name" --tag client_name --alias "Client name" --out "<file-form.docx"
20
17
  ```
21
18
 
22
- The script wraps the paragraph containing the placeholder text (or appends a new one) in a `w:sdt`, so the field is a drop-in replacement for a typed placeholder line. `--json` prints the created control instead of a summary.
19
+ Wraps the paragraph containing the placeholder text (or appends one) in a `w:sdt`, so the field is a drop-in replacement for a typed placeholder line. `--json` prints the created control.
23
20
 
24
21
  ## Fill
25
22
 
26
23
  ```bash
27
- "<python>" "<skill>/scripts/content_controls.py" set "<file-form.docx>" \
28
- --set client_name="Northwind" --set period="Q2 FY26" --out "<file-filled.docx>"
24
+ "<python>" "<skill>/scripts/content_controls.py" set "<file-form.docx>" --set client_name="Northwind" --set period="Q2 FY26" --out "<file-filled.docx"
29
25
  ```
30
26
 
31
- `--set TAG=VALUE` matches on the tag. The command exits 1 and lists `missing_tags` when a tag is not present — that is the check for a scripted fill, so a typo does not silently write nothing.
27
+ `--set TAG=VALUE` matches on the tag. It exits 1 and lists `missing_tags` when a tag is absent — the check for a scripted fill.
32
28
 
33
29
  ## Design
34
30
 
35
- - One control per value; do not put a whole table into one control.
36
- - Tag in `snake_case`, alias in the user's words.
37
- - A choice control lists its options in `w:sdtPr`; a date control has `w:date` with a format. Plain text is the default and the safest thing to generate.
38
- - Leave the surrounding labels in the body text; the alias is visible only in Word's properties panel.
31
+ - One control per value, not one per table.
32
+ - Tag `snake_case`, alias in the user's words.
33
+ - A choice control lists options in `w:sdtPr`; a date control has `w:date` with a format; plain text is the default and safest.
34
+ - Leave surrounding labels in body text: the alias is visible only in Word's properties panel.
39
35
 
40
36
  ## Verify
41
37
 
42
- `content_controls.py list` after filling: tags present with the expected text. Then render — a control's content is normal text on the page, so the visual check is that nothing shifted and the filled value fits its line.
38
+ `content_controls.py` `list` after filling (tags present with the expected text), then render. A control is ordinary text on the page, so check that nothing shifted and the filled value fits its line.
43
39
 
44
40
  ## Pitfalls
45
41
 
46
- - Deleting an `sdt` wrapper while trying to "clear" a field deletes the value and the structure; set the text instead.
47
- - Two controls with the same tag make `--set` ambiguous; the script updates the first and reports the rest.
48
- - A content control cannot span two paragraphs without `w:sdtContent` holding both — keep one value per paragraph when generating.
42
+ - Deleting the `sdt` wrapper to clear a field deletes value and structure; set the text instead.
43
+ - Two controls with the same tag make `--set` ambiguous: it updates the first and reports the rest.
44
+ - A control cannot span two paragraphs unless `w:sdtContent` holds both, so generate one value per paragraph.
@@ -1,8 +1,6 @@
1
1
  # Headings and numbering
2
2
 
3
- **Goal.** A single outline a reader can follow and Word can turn into a TOC: no skipped levels, no headings faked with bold text, consistent spacing.
4
-
5
- Heading level comes from the paragraph style (`Heading 1`…`Heading 9`, `Title` uses outline level 0), not from size or weight. `heading_audit.py` reads that structure and flags the common breakages.
3
+ A heading level comes from the paragraph style (`Heading 1` … `Heading 9`, `Title` = outline level 0), not from size or weight.
6
4
 
7
5
  ## Audit
8
6
 
@@ -12,44 +10,41 @@ Heading level comes from the paragraph style (`Heading 1`…`Heading 9`, `Title`
12
10
 
13
11
  Findings:
14
12
 
15
- - `skipped_level` — a level 3 heading directly under a level 1. Fix by inserting the missing level or by promoting the deeper heading.
16
- - `empty_heading` — a heading with no text; a stale outline entry that will appear in the TOC as a blank line.
17
- - `manual_formatting` — a real heading that also carries direct bold or a large font size; the style already does that, so the extra formatting fights restyling.
18
- - `heading_not_styled` — body text that looks like a heading (bold, 13pt+) but is styled `Normal`. This is the expensive one: the TOC misses it and every later style change misses it.
13
+ - `skipped_level` — L3 directly under L1: insert the missing level or promote the heading.
14
+ - `empty_heading` — a blank line in the TOC.
15
+ - `manual_formatting` — a real heading carrying direct bold or a large size, which fights restyling.
16
+ - `heading_not_styled` — bold 13pt+ text in `Normal`: the TOC and every later style change miss it.
19
17
 
20
18
  ## Fix
21
19
 
22
- Prefer styles over direct formatting. `style_normalize.py --clear_paragraph_format --enforce_heading_spacing` strips direct run formatting and sets standard space-before/after on headings:
20
+ Prefer real styles. To strip direct run formatting and set standard heading spacing:
23
21
 
24
22
  ```bash
25
- "<python>" "<skill>/scripts/style_normalize.py" "<file.docx>" --out "<file-fixed.docx>" --clear_paragraph_format --enforce_heading_spacing
23
+ "<python>" "<skill>/scripts/style_normalize.py" "<file.docx>" --out "<file-fixed.docx" --clear_paragraph_format --enforce_heading_spacing
26
24
  ```
27
25
 
28
- For a heading that is only bold text, promote it to the right style rather than making it bolder:
26
+ To promote bold text to a real heading:
29
27
 
30
- ```bash
31
- "<python>" - <<'PY'
32
- from docx import Document
33
- d = Document(r"<file.docx>")
28
+ ```python
29
+ d = Document("<file.docx>")
34
30
  for p in d.paragraphs:
35
31
  if p.text.strip() == "<exact heading text>":
36
32
  p.style = d.styles["Heading 1"]
37
- d.save(r"<file-fixed.docx>")
38
- PY
33
+ d.save("<file-fixed.docx")
39
34
  ```
40
35
 
41
36
  ## Numbered headings
42
37
 
43
- Numbering is a numbering definition attached to the heading style, not text like `1.2`. Two consequences: a typed number keeps lying after an insert, and a numbered outline needs `numPr` on the style with an `abstractNum` that uses `%1.%2` level text. When a document must have numbered sections and the original numbering is lost, restore the style's `numPr` rather than typing numbers into the headings.
38
+ Numbering is a definition attached to the heading style, not text such as `1.2`: a typed number lies after the next insert. A numbered outline needs `numPr` on the style, with an `abstractNum` whose level text is `%1.%2`. Restore the style's `numPr` instead of typing numbers.
44
39
 
45
- Lists are the same machinery at body level: `List Number` restarts only when the numbering definition says so, which is why two separate numbered lists sometimes continue each other.
40
+ Lists are the same machinery at body level. `List Number` restarts only when the numbering definition says so, which is why two separate lists sometimes continue each other.
46
41
 
47
42
  ## Verify
48
43
 
49
- `heading_audit.py` should exit 0, then render and read the page: heading hierarchy visible, no orphaned heading at the bottom of a page (`keepNext`), and the same spacing before every heading of a level.
44
+ `heading_audit.py` exits 0 on a clean file. Render, then check that no heading is orphaned at the bottom of a page (`keepNext`) and that headings of one level share their spacing.
50
45
 
51
46
  ## Pitfalls
52
47
 
53
- - Changing a heading's style does not change its text, but it can change its size and colour — re-render.
54
- - A heading inside a table cell still counts for the outline, which is rarely wanted; use a bold paragraph there.
55
- - Copying a heading from another document brings a direct-formatting payload with it; run the audit afterwards.
48
+ - Changing a heading's style leaves its text alone but can change size and colour: re-render.
49
+ - A heading inside a table cell still counts for the outline, which is rarely wanted — use a bold paragraph.
50
+ - Copying a heading brings a direct-formatting payload, so run the audit afterwards.