dsh-office-kit 0.4.2 → 0.4.3
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.
- package/assets/skills/excel-live-control/SKILL.md +119 -152
- package/assets/skills/excel-live-control/domain_guidance/financial_models.md +42 -58
- package/assets/skills/excel-live-control/domain_guidance/healthcare.md +36 -42
- package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +33 -34
- package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +34 -36
- package/assets/skills/excel-live-control/references/office-automation.md +127 -153
- package/assets/skills/excel-live-control/references/read_only_qna.md +40 -45
- package/assets/skills/excel-live-control/style_guidelines.md +56 -64
- package/assets/skills/excel-live-control/workflows/create_workflows.md +40 -47
- package/assets/skills/excel-live-control/workflows/edit_workflows.md +37 -45
- package/assets/skills/office-documents/SKILL.md +24 -60
- package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +5 -5
- package/assets/skills/office-documents/ooxml/comments.md +8 -8
- package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +10 -10
- package/assets/skills/office-documents/ooxml/rels_and_content_types.md +7 -7
- package/assets/skills/office-documents/ooxml/tracked_changes.md +12 -12
- package/assets/skills/office-documents/references/docx-structure.md +11 -13
- package/assets/skills/office-documents/references/template-elicitation.md +11 -13
- package/assets/skills/office-documents/references/writing-quality.md +20 -20
- package/assets/skills/office-documents/tasks/accessibility_a11y.md +16 -22
- package/assets/skills/office-documents/tasks/captions_crossrefs.md +23 -26
- package/assets/skills/office-documents/tasks/clean_tracked_changes.md +17 -22
- package/assets/skills/office-documents/tasks/comments_manage.md +9 -11
- package/assets/skills/office-documents/tasks/compare_diff.md +10 -16
- package/assets/skills/office-documents/tasks/create_edit.md +14 -16
- package/assets/skills/office-documents/tasks/equations.md +17 -28
- package/assets/skills/office-documents/tasks/fields_update.md +14 -16
- package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +19 -33
- package/assets/skills/office-documents/tasks/footnotes_endnotes.md +12 -14
- package/assets/skills/office-documents/tasks/forms_content_controls.md +16 -20
- package/assets/skills/office-documents/tasks/headings_numbering.md +17 -22
- package/assets/skills/office-documents/tasks/images_figures.md +15 -25
- package/assets/skills/office-documents/tasks/multi_doc_merge.md +13 -23
- package/assets/skills/office-documents/tasks/navigation_internal_links.md +18 -21
- package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +16 -22
- package/assets/skills/office-documents/tasks/protection_restrict_editing.md +19 -21
- package/assets/skills/office-documents/tasks/read_review.md +9 -20
- package/assets/skills/office-documents/tasks/redaction_anonymization.md +17 -18
- package/assets/skills/office-documents/tasks/sections_layout.md +16 -23
- package/assets/skills/office-documents/tasks/style_lint_normalize.md +18 -22
- package/assets/skills/office-documents/tasks/tables_spreadsheets.md +21 -31
- package/assets/skills/office-documents/tasks/templates_style_packs.md +17 -21
- package/assets/skills/office-documents/tasks/toc_workflow.md +10 -14
- package/assets/skills/office-documents/tasks/verify_render.md +11 -23
- package/assets/skills/office-documents/tasks/watermarks_background.md +11 -15
- package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +6 -6
- package/assets/skills/office-documents/troubleshooting/run_splitting.md +12 -12
- package/assets/skills/office-pdf/SKILL.md +36 -51
- package/assets/skills/office-pdf/references/form-fields.md +20 -20
- package/assets/skills/office-pdf/references/geometry.md +10 -10
- package/assets/skills/office-pdf/references/layout-quality.md +13 -13
- package/assets/skills/office-pdf/references/limits.md +12 -56
- package/assets/skills/office-pdf/tasks/annotations.md +5 -5
- package/assets/skills/office-pdf/tasks/create.md +6 -9
- package/assets/skills/office-pdf/tasks/edit-pages.md +14 -14
- package/assets/skills/office-pdf/tasks/forms.md +15 -15
- package/assets/skills/office-pdf/tasks/read.md +9 -9
- package/assets/skills/office-pdf/tasks/redact.md +18 -18
- package/assets/skills/office-pdf/tasks/stamp.md +10 -10
- package/assets/skills/office-presentations/SKILL.md +71 -91
- package/assets/skills/office-presentations/container_tools/render_slides.py +1 -1
- package/assets/skills/office-presentations/domain_guidance/finance.md +16 -16
- package/assets/skills/office-presentations/recipes/README.md +24 -35
- package/assets/skills/office-presentations/recipes/autolayout.md +12 -13
- package/assets/skills/office-presentations/recipes/connectors.md +11 -12
- package/assets/skills/office-presentations/recipes/images.md +11 -12
- package/assets/skills/office-presentations/recipes/master-and-layouts.md +18 -18
- package/assets/skills/office-presentations/recipes/native-charts.md +8 -8
- package/assets/skills/office-presentations/recipes/rich-text-runs.md +13 -13
- package/assets/skills/office-presentations/recipes/speaker-notes.md +7 -7
- package/assets/skills/office-presentations/recipes/tables.md +14 -14
- package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +9 -9
- package/assets/skills/office-presentations/recipes/theme-tokens.md +7 -8
- package/assets/skills/office-presentations/references/authoring-workflow.md +38 -43
- package/assets/skills/office-presentations/references/finalization.md +10 -12
- package/assets/skills/office-presentations/references/native-bullets.md +14 -18
- package/assets/skills/office-presentations/references/native-evidence.md +18 -32
- package/assets/skills/office-presentations/references/style-guidelines.md +27 -32
- package/assets/skills/office-presentations/references/templates.md +31 -60
- package/assets/skills/office-presentations/routing/google_slides.md +4 -4
- package/assets/skills/office-presentations/tasks/apply-annotations.md +17 -17
- package/assets/skills/office-presentations/tasks/charts-tables-notes.md +20 -23
- package/assets/skills/office-presentations/tasks/create-deck.md +19 -19
- package/assets/skills/office-presentations/tasks/edit-deck.md +14 -16
- package/assets/skills/office-presentations/tasks/inspect-and-verify.md +31 -34
- package/assets/skills/office-presentations/tasks/template-following.md +23 -28
- package/assets/skills/office-spreadsheets/SKILL.md +32 -39
- package/assets/skills/office-spreadsheets/recipes/charts.md +8 -26
- package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +9 -26
- package/assets/skills/office-spreadsheets/recipes/data-tables.md +7 -23
- package/assets/skills/office-spreadsheets/recipes/named-ranges.md +8 -21
- package/assets/skills/office-spreadsheets/recipes/number-formats.md +7 -23
- package/assets/skills/office-spreadsheets/recipes/sparklines.md +7 -20
- package/assets/skills/office-spreadsheets/references/charts.md +15 -34
- package/assets/skills/office-spreadsheets/references/domain/financial-models.md +10 -25
- package/assets/skills/office-spreadsheets/references/domain/healthcare.md +8 -19
- package/assets/skills/office-spreadsheets/references/domain/marketing.md +8 -22
- package/assets/skills/office-spreadsheets/references/domain/research.md +8 -19
- package/assets/skills/office-spreadsheets/references/formula-patterns.md +20 -50
- package/assets/skills/office-spreadsheets/references/style-guidelines.md +19 -44
- package/assets/skills/office-spreadsheets/references/workbook-structure.md +12 -35
- package/assets/skills/office-spreadsheets/routing/google-sheets.md +11 -26
- package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +10 -25
- package/assets/skills/office-spreadsheets/tasks/annotations.md +12 -30
- package/assets/skills/office-spreadsheets/tasks/create-workbook.md +5 -5
- package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +2 -2
- package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +5 -5
- package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +9 -9
- package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +10 -30
- package/assets/skills/shared/annotation-recipes.md +98 -120
- package/assets/skills/shared/common-workflow.md +70 -80
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Fixtures
|
|
1
|
+
# Fixtures for edge cases
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
18
|
+
`--only tracked|watermark|comments|edge` builds a single file. These are test inputs, not templates: never ship them.
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
## Cases that break naive edits
|
|
23
21
|
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5
|
+
## Insert
|
|
6
6
|
|
|
7
|
-
|
|
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]]
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
36
|
-
- Copying a paragraph with
|
|
37
|
-
- Deleting note text without
|
|
38
|
-
- Field instructions are not allowed in the note separator;
|
|
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
|
-
#
|
|
1
|
+
# Content controls (form fields)
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
36
|
-
- Tag
|
|
37
|
-
- A choice control lists
|
|
38
|
-
- Leave
|
|
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
|
|
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
|
|
47
|
-
- Two controls with the same tag make `--set` ambiguous
|
|
48
|
-
- A
|
|
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
|
-
|
|
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` —
|
|
16
|
-
- `empty_heading` — a
|
|
17
|
-
- `manual_formatting` — a real heading
|
|
18
|
-
- `heading_not_styled` —
|
|
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
|
|
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
|
|
23
|
+
"<python>" "<skill>/scripts/style_normalize.py" "<file.docx>" --out "<file-fixed.docx" --clear_paragraph_format --enforce_heading_spacing
|
|
26
24
|
```
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
To promote bold text to a real heading:
|
|
29
27
|
|
|
30
|
-
```
|
|
31
|
-
"<
|
|
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(
|
|
38
|
-
PY
|
|
33
|
+
d.save("<file-fixed.docx")
|
|
39
34
|
```
|
|
40
35
|
|
|
41
36
|
## Numbered headings
|
|
42
37
|
|
|
43
|
-
Numbering is a
|
|
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
|
|
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`
|
|
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
|
|
54
|
-
- A heading inside a table cell still counts for the outline, which is rarely wanted
|
|
55
|
-
- Copying a heading
|
|
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.
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# Images and figures
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
An inline picture is a `w:drawing` containing `wp:inline` (or `wp:anchor` for a floating one), with the picture relationship in `a:blip r:embed` and the display size in `wp:extent` (EMU). Alt text lives in `wp:docPr @descr`.
|
|
3
|
+
An inline picture is a `w:drawing` containing `wp:inline` (`wp:anchor` for floating), the picture relationship in `a:blip r:embed`, the display size in `wp:extent` (EMU), and the alt text in `wp:docPr @descr`.
|
|
6
4
|
|
|
7
5
|
## Audit
|
|
8
6
|
|
|
@@ -10,54 +8,46 @@ An inline picture is a `w:drawing` containing `wp:inline` (or `wp:anchor` for a
|
|
|
10
8
|
"<python>" "<skill>/scripts/images_audit.py" "<file.docx>" --max_rows 20
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
Reports each picture's part, name, media file, size in inches and alt text, plus `missing_alt` and `oversized` (wider than the text block). The real test is whether the alt text describes what a reader loses without the picture.
|
|
14
12
|
|
|
15
13
|
## Fix alt text
|
|
16
14
|
|
|
17
15
|
```bash
|
|
18
|
-
"<python>" "<skill>/scripts/a11y_audit.py" "<file.docx>" --fix_image_alt from_filename --out "<file-fixed.docx
|
|
16
|
+
"<python>" "<skill>/scripts/a11y_audit.py" "<file.docx>" --fix_image_alt from_filename --out "<file-fixed.docx"
|
|
19
17
|
```
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
A file name is better than nothing but not as good as a real description. For real text, patch `wp:docPr` directly:
|
|
22
20
|
|
|
23
|
-
```
|
|
24
|
-
"<python>" - <<'PY'
|
|
25
|
-
import sys
|
|
26
|
-
from pathlib import Path
|
|
21
|
+
```python
|
|
27
22
|
sys.path.insert(0, r"<skill>/scripts")
|
|
28
23
|
import docx_kit as kit
|
|
29
24
|
d = kit.Docx(r"<file.docx>")
|
|
30
25
|
for doc_pr in d.xml("word/document.xml").iter(kit.q("docPr", kit.WP)):
|
|
31
26
|
if doc_pr.get("name") == "Picture 3":
|
|
32
27
|
doc_pr.set("descr", "Bar chart: revenue by region, EMEA ahead of APAC.")
|
|
33
|
-
|
|
34
|
-
d.save(out)
|
|
35
|
-
print("saved", out)
|
|
36
|
-
PY
|
|
28
|
+
d.save("<file.docx>")
|
|
37
29
|
```
|
|
38
30
|
|
|
31
|
+
Decorative images get an empty description plus a `title`; informative images get a sentence.
|
|
32
|
+
|
|
39
33
|
## Insert
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
"<python>" "<cli>" docx create --spec "<spec.json>" --out "<out.docx>"
|
|
43
|
-
```
|
|
35
|
+
`docx create --spec` block:
|
|
44
36
|
|
|
45
37
|
```json
|
|
46
38
|
{ "type": "image", "path": "<absolute.png>", "width": 5.5 }
|
|
47
39
|
```
|
|
48
40
|
|
|
49
|
-
Width is in inches
|
|
50
|
-
|
|
51
|
-
## Figures
|
|
41
|
+
Width is in inches and height follows the aspect ratio. A picture wider than the text block is clipped or shrinks unpredictably; 5.5in is a safe full-width figure on Letter with 1in margins.
|
|
52
42
|
|
|
53
|
-
|
|
43
|
+
Figures get captions with `captions_and_crossrefs.py --figures` (`tasks/captions_crossrefs.md`), which adds `Figure N` with a `SEQ` field and a `figN` bookmark. Keep the caption in the same block so picture and caption do not separate.
|
|
54
44
|
|
|
55
45
|
## Verify
|
|
56
46
|
|
|
57
|
-
Render
|
|
47
|
+
Render: not stretched, not blurry beyond the source resolution, not clipped at the margin, caption underneath. Alt text is a separate check the render cannot show.
|
|
58
48
|
|
|
59
49
|
## Pitfalls
|
|
60
50
|
|
|
61
|
-
- Cropping
|
|
62
|
-
- A floating
|
|
63
|
-
- Replacing
|
|
51
|
+
- Cropping changes the displayed extent; alt text and size checks still apply.
|
|
52
|
+
- A floating `wp:anchor` image can overlap text at any zoom — prefer inline for documents that will be edited.
|
|
53
|
+
- Replacing a file in `word/media` keeps the relationship but not the dimensions: the extent is stored separately.
|
|
@@ -1,43 +1,33 @@
|
|
|
1
1
|
# Merge documents
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Concatenating packages by hand fails on the details: style ids collide, `numId` values point at the wrong list, media parts need new names and relationships, and `r:id` values are document-scoped. Word also requires one `sectPr` at the end of the body, so the appended content has to land before it.
|
|
3
|
+
Concatenating packages by hand fails: style ids collide, `numId` values point at the wrong lists, media parts need new names and relationships, `r:id` values are document-scoped, and Word requires one `sectPr` at the end of the body, so appended content must land before it.
|
|
6
4
|
|
|
7
5
|
## Append
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
"<python>" "<skill>/scripts/merge_docx_append.py" "<base.docx>" "<addition.docx>" --out "<merged.docx
|
|
8
|
+
"<python>" "<skill>/scripts/merge_docx_append.py" "<base.docx>" "<addition.docx>" --out "<merged.docx"
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Drawings are refused by default: a floating image or a text box carries anchor geometry tied to the original page. Pass `--allow_drawings` when the user accepts that the layout may shift, then check that page carefully.
|
|
16
|
-
|
|
17
|
-
## Merge into a template
|
|
11
|
+
The script carries the body content minus the addition's final `sectPr`, renames media parts under `word/media` with fresh relationships and content types, copies style definitions referenced by the copied content (following `basedOn`/`next`/`link`), rebuilds numbering definitions with fresh ids and rewrites every `w:numId`, and recreates hyperlink relationships as external relationships.
|
|
18
12
|
|
|
19
|
-
|
|
13
|
+
Drawings are refused by default: a floating image or text box carries anchor geometry tied to its original page. Use `--allow_drawings` when the user accepts a possible layout shift, then check that page.
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
"<python>" "<skill>/scripts/apply_template_styles.py" --template "<house.dotx>" --target "<draft.docx>" --out "<draft-styled.docx>"
|
|
23
|
-
"<python>" "<skill>/scripts/merge_docx_append.py" "<draft-styled.docx>" "<section2.docx>" --out "<merged.docx>"
|
|
24
|
-
```
|
|
15
|
+
## Into a template
|
|
25
16
|
|
|
26
|
-
|
|
17
|
+
Apply the template first, then append, so content inherits the template's style ids:
|
|
27
18
|
|
|
28
19
|
```bash
|
|
29
|
-
"<python>" "<skill>/scripts/
|
|
20
|
+
"<python>" "<skill>/scripts/apply_template_styles.py" --template "<house.dotx>" --target "<draft.docx>" --out "<draft-styled.docx"
|
|
21
|
+
"<python>" "<skill>/scripts/merge_docx_append.py" "<draft-styled.docx>" "<section2.docx>" --out "<merged.docx"
|
|
22
|
+
"<python>" "<skill>/scripts/style_normalize.py" "<merged.docx>" --out "<merged-final.docx" --clear_paragraph_format
|
|
30
23
|
```
|
|
31
24
|
|
|
32
25
|
## Verify
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
- Headers and footers come from the base document; say so if the addition had its own.
|
|
36
|
-
- Render and read the join: the page where the second document starts often shows a stray page break or a heading that lost its style.
|
|
37
|
-
- `style_lint.py` on the merged file catches undefined style references imported from the addition.
|
|
27
|
+
Page count equals the sum of the inputs unless a page break changed. Headers and footers come from the base document — say so if the addition had its own. Render the join to catch a stray page break or a heading that lost its style. `style_lint.py` catches undefined style references imported from the addition.
|
|
38
28
|
|
|
39
29
|
## Pitfalls
|
|
40
30
|
|
|
41
|
-
- Footnotes and comments in the addition are not merged
|
|
42
|
-
- `w:id` values
|
|
43
|
-
- A landscape section in the addition is not carried over
|
|
31
|
+
- Footnotes and comments in the addition are not merged (the port is deliberate).
|
|
32
|
+
- Bookmark and comment `w:id` values can collide; the merge keeps ids and names.
|
|
33
|
+
- A landscape section in the addition is not carried over: section properties are dropped with the `sectPr`.
|
|
@@ -1,38 +1,35 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Navigation and internal links
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An internal link is `w:hyperlink w:anchor="bookmark"`; the target is a `w:bookmarkStart`/`w:bookmarkEnd` pair. Both live in the body, so PDF export keeps the links.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Build a navigation set
|
|
5
|
+
## Build navigation
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
"<python>" "<skill>/scripts/internal_nav.py" "<file.docx>" --out "<file-nav.docx>" --levels 1 2
|
|
11
|
-
--toc_title "Contents"
|
|
8
|
+
"<python>" "<skill>/scripts/internal_nav.py" "<file.docx>" --out "<file-nav.docx>" --levels 1 2 --toc_title "Contents"
|
|
12
9
|
```
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
The script:
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
- bookmarks every heading `nav_h1`, `nav_h2`, … by outline level;
|
|
14
|
+
- inserts a `Contents` heading and a quick-link bar at the top;
|
|
15
|
+
- indents contents entries 360 twips per level;
|
|
16
|
+
- appends a `Back to contents` link after each heading;
|
|
17
|
+
- bookmarks the top, the contents and the bottom as `nav_top`, `nav_toc`, `nav_bottom`.
|
|
21
18
|
|
|
22
|
-
Flags: `--no_quicklinks
|
|
19
|
+
Flags: `--no_quicklinks`, `--no_back_to_toc`, `--no_top_bottom`.
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
It reports `missing` when a target could not be created. Read that list: a dead link looks fine in a render.
|
|
25
22
|
|
|
26
|
-
## Static versus field
|
|
23
|
+
## Static versus field
|
|
27
24
|
|
|
28
|
-
`internal_nav.py` writes literal entries:
|
|
25
|
+
`internal_nav.py` writes literal entries: rebuild when headings change, and every renderer shows the same result. `insert_toc.py` writes a live `TOC` field: self-updating, but it needs recalculation. A repeatedly edited document wants the field; a PDF export wants the static list.
|
|
29
26
|
|
|
30
27
|
## Verify
|
|
31
28
|
|
|
32
|
-
Render
|
|
29
|
+
Render and click through: entry indentation, the contents heading styled as a heading, and `Back to contents` not pushing a paragraph onto a new page. Dead anchors do not show in a screenshot — when the script reports a miss, check `bookmarkStart` names against the `w:anchor` values.
|
|
33
30
|
|
|
34
31
|
## Pitfalls
|
|
35
32
|
|
|
36
|
-
- Bookmarks must be unique per document
|
|
37
|
-
-
|
|
38
|
-
- Link styling comes from the `Hyperlink` character style
|
|
33
|
+
- Bookmarks must be unique per document. Re-running on its own output duplicates `nav_h1`, so start from the original.
|
|
34
|
+
- Hyperlink anchors are case-sensitive.
|
|
35
|
+
- Link styling comes from the `Hyperlink` character style.
|
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
# Privacy
|
|
1
|
+
# Privacy and document metadata
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A `.docx` stores more than text: `docProps/core.xml` (author, last modifier, title, keywords, description), `docProps/app.xml` (company, manager, template), `docProps/custom.xml`, `word/comments.xml`, revision data and per-author `w:rsid` stamps.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Scrub metadata
|
|
5
|
+
## Scrub
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
"<python>" "<skill>/scripts/privacy_scrub.py" "<file.docx>" --out "<file-clean.docx
|
|
8
|
+
"<python>" "<skill>/scripts/privacy_scrub.py" "<file.docx>" --out "<file-clean.docx"
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
Clears creator, last-modified-by, keywords, description, subject, title, category, company, manager and application; resets the revision counter; drops `docProps/custom.xml`
|
|
14
|
-
|
|
15
|
-
`--keep_title` leaves the title in place when the title is real content the user wants.
|
|
11
|
+
Clears creator, last-modified-by, keywords, description, subject, title, category, company, manager and application; resets the revision counter; drops `docProps/custom.xml` with its relationships and content-type override; strips `w:rsid*`, `w:proofErr` and `w:lastRenderedPageBreak` across the Word parts. `--keep_title` preserves a real title.
|
|
16
12
|
|
|
17
|
-
##
|
|
13
|
+
## Other residue
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
| Watermarks naming the old client | `watermark_audit_remove.py --mode remove` |
|
|
24
|
-
| Emails and phone numbers in the text | `redact_docx.py` |
|
|
15
|
+
- Comments and their authors → `comments_strip.py`.
|
|
16
|
+
- Tracked changes and reviewer names → `accept_tracked_changes.py --mode accept` (or reject).
|
|
17
|
+
- A watermark naming the old client → `watermark_audit_remove.py --mode remove`.
|
|
18
|
+
- Emails and phone numbers → `redact_docx.py`.
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
Order matters: scrub metadata, resolve markup, then redact the surviving text.
|
|
27
21
|
|
|
28
22
|
## Check before delivery
|
|
29
23
|
|
|
@@ -32,14 +26,14 @@ Run them in that order — scrub metadata, resolve markup, then redact the text
|
|
|
32
26
|
"<python>" "<skill>/scripts/docx_kit.py" xml '<file-clean.docx>' docProps/core.xml
|
|
33
27
|
```
|
|
34
28
|
|
|
35
|
-
`parts` shows whether `
|
|
29
|
+
`parts` shows whether `custom.xml` and `comments.xml` still exist; `core` shows what a recipient sees under File > Info.
|
|
36
30
|
|
|
37
31
|
## Verify
|
|
38
32
|
|
|
39
|
-
Metadata
|
|
33
|
+
Metadata never appears in a render, so use those two commands. Render afterwards to confirm the document still looks right: clearing `w:lastRenderedPageBreak` can change where a viewer breaks a long line.
|
|
40
34
|
|
|
41
35
|
## Pitfalls
|
|
42
36
|
|
|
43
|
-
- Author names also appear in comment and revision entries
|
|
44
|
-
- `docProps/app.xml` names the editing application
|
|
45
|
-
- Removing revision ids does not undo content
|
|
37
|
+
- Author names also appear in comment and revision entries.
|
|
38
|
+
- `docProps/app.xml` names the editing application and is regenerated on the next save.
|
|
39
|
+
- Removing revision ids does not undo content.
|
|
@@ -1,41 +1,39 @@
|
|
|
1
1
|
# Restrict editing
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Restrictions live in `w:documentProtection` inside `word/settings.xml`. Modes: `readOnly`, `comments`, `trackedChanges`, `forms`, each with `enforcement`. This is a guardrail against accidental edits, not encryption: a determined reader can remove it.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Set a mode
|
|
5
|
+
## Set
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
"<python>" "<skill>/scripts/set_protection.py" "<file.docx>" --mode comments --out "<file-review.docx
|
|
11
|
-
"<python>" "<skill>/scripts/set_protection.py" "<file.docx>" --mode readOnly --no_enforce --out "<file-open.docx
|
|
8
|
+
"<python>" "<skill>/scripts/set_protection.py" "<file.docx>" --mode comments --out "<file-review.docx"
|
|
9
|
+
"<python>" "<skill>/scripts/set_protection.py" "<file.docx>" --mode readOnly --no_enforce --out "<file-open.docx"
|
|
12
10
|
```
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
Mode aliases: `off` / `none`, `readOnly` / `read_only`, `comments` / `comment`, `trackedChanges` / `tracked_changes`, `forms` / `form`. `--no_enforce` writes the setting without switching it on, which avoids the dialog on open.
|
|
15
13
|
|
|
16
|
-
`--hash` also writes a password hash (SHA-512, 100000 spins; `--salt
|
|
14
|
+
`--hash` also writes a password hash (SHA-512, 100000 spins; `--salt` and `--spin_count` change the parameters). Say plainly that it deters casual edits, is not encryption, and that losing the password means stripping the restriction from the XML.
|
|
17
15
|
|
|
18
|
-
##
|
|
16
|
+
## Choose a mode
|
|
19
17
|
|
|
20
|
-
|
|
|
18
|
+
| Requirement | Mode |
|
|
21
19
|
|---|---|
|
|
22
|
-
|
|
|
23
|
-
| All edits
|
|
24
|
-
| Fill a form
|
|
20
|
+
| Comments only | `comments` |
|
|
21
|
+
| All edits tracked | `trackedChanges` |
|
|
22
|
+
| Fill a form | `forms` |
|
|
25
23
|
| View only | `readOnly` |
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
Remove the restriction with `--mode off --out "<file-open.docx"`.
|
|
26
|
+
|
|
27
|
+
## Verify
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
"<python>" "<skill>/scripts/
|
|
30
|
+
"<python>" "<skill>/scripts/docx_kit.py" xml '<file.docx>' word/settings.xml | grep documentProtection
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
`docx_kit.py xml '<file.docx>' word/settings.xml | grep documentProtection` shows the element and its enforcement attribute. Then open behaviour matters, not the page — render will not show a restriction. Say which mode you applied, and that removing it needs the same script or Word's Review pane.
|
|
33
|
+
The element and its enforcement setting appear in the output. Open behaviour is what matters, not the page: a render never shows a restriction. State the mode you applied and that removing it needs the same script or Word's Review pane.
|
|
36
34
|
|
|
37
35
|
## Pitfalls
|
|
38
36
|
|
|
39
|
-
-
|
|
40
|
-
- `forms`
|
|
41
|
-
-
|
|
37
|
+
- A protected document that also holds tracked changes or comments often prompts on open; apply the restriction last.
|
|
38
|
+
- `forms` without form fields leaves a document nobody can use.
|
|
39
|
+
- A hashed restriction cannot be turned off in Word without the password, yet `--mode off` clears it — which is why this is a guardrail, not security.
|