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.
- 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/scripts/add_tracked_replacements.py +4 -3
- package/assets/skills/office-documents/scripts/docx_kit.py +68 -27
- package/assets/skills/office-documents/scripts/heading_audit.py +3 -4
- package/assets/skills/office-documents/scripts/internal_nav.py +3 -1
- package/assets/skills/office-documents/scripts/merge_docx_append.py +104 -0
- package/assets/skills/office-documents/scripts/table_geometry.py +13 -3
- package/assets/skills/office-documents/scripts/watermark_add.py +6 -5
- package/assets/skills/office-documents/scripts/watermark_audit_remove.py +6 -4
- 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 +10 -14
- 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/preview_render.py +4 -1
- 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
- package/python/office_cli.py +38 -4
|
@@ -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.
|
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
# Read and review a file
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Read the content
|
|
3
|
+
## Read
|
|
6
4
|
|
|
7
5
|
```bash
|
|
8
6
|
"<python>" "<cli>" docx read --input "<file.docx>"
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Returns the title, author, paragraphs with their styles, headings, tables, image and equation counts, and page dimensions. Use it as the first look, including for files you produced earlier in the session.
|
|
12
|
-
|
|
13
|
-
## Read the structure
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
7
|
"<python>" "<skill>/scripts/docx_kit.py" parts '<file.docx>' # every package part and its size
|
|
17
8
|
"<python>" "<skill>/scripts/docx_kit.py" text '<file.docx>' # paragraphs with style and outline level
|
|
18
9
|
"<python>" "<skill>/scripts/docx_kit.py" xml '<file.docx>' word/document.xml
|
|
19
10
|
```
|
|
20
11
|
|
|
21
|
-
`parts`
|
|
12
|
+
`docx read` gives the title, author, styled paragraphs, headings, table/image/equation counts and page dimensions — the first look, including for files produced earlier in the session. `parts` shows which features exist at all: no `comments.xml`, no comments, whatever the user says. `text` is faster than raw XML when you only need the words and their heading levels.
|
|
22
13
|
|
|
23
|
-
## Audit
|
|
14
|
+
## Audit
|
|
24
15
|
|
|
25
16
|
| Question | Command |
|
|
26
17
|
|---|---|
|
|
@@ -32,19 +23,17 @@ Returns the title, author, paragraphs with their styles, headings, tables, image
|
|
|
32
23
|
| Are there footnotes or endnotes? | `footnotes_report.py` |
|
|
33
24
|
| What comments exist, and on what text? | `comments_extract.py` |
|
|
34
25
|
|
|
35
|
-
|
|
26
|
+
Each audit prints JSON and exits non-zero when it found something: the exit code says whether the document needs attention, the `findings` array says why.
|
|
36
27
|
|
|
37
28
|
## Review for the user
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
1. Read the text, not just the audits. Point out the argument, not only the formatting.
|
|
30
|
+
1. Read the text, not just the audits. Address the argument, not only the formatting.
|
|
42
31
|
2. Separate defects (broken table, skipped heading level, missing alt text) from style preferences.
|
|
43
|
-
3. Cite locations by section heading and paragraph text
|
|
32
|
+
3. Cite locations by section heading and paragraph text: Word has no stable page numbers.
|
|
44
33
|
4. Offer the fix as a concrete next action, with the command that performs it.
|
|
45
34
|
|
|
46
35
|
## Pitfalls
|
|
47
36
|
|
|
48
|
-
- `docx read` shows the body only; headers, footers, footnotes and comments need `docx_kit.py xml` or the
|
|
49
|
-
- Tracked changes
|
|
50
|
-
- A field's displayed text is a cached result
|
|
37
|
+
- `docx read` shows the body only; headers, footers, footnotes and comments need `docx_kit.py xml` or the report scripts.
|
|
38
|
+
- Tracked changes read as plain text; `accept_tracked_changes.py --mode report` shows what is proposed.
|
|
39
|
+
- A field's displayed text is a cached result; `fields_report.py` shows the instruction, which is the truth.
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
# Redaction and anonymisation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Text lives in `w:t` (and `w:delText` for deletions, `w:instrText` for field instructions). A redaction that only rewrites paragraphs via `python-docx` misses headers, footers, footnotes and comments.
|
|
3
|
+
Text lives in `w:t` (plus `w:delText` for deletions and `w:instrText` for field instructions). A redaction that only rewrites paragraphs through python-docx misses headers, footers, footnotes and comments.
|
|
6
4
|
|
|
7
5
|
## Redact
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
8
|
"<python>" "<skill>/scripts/redact_docx.py" "<file.docx>" "<file-redacted.docx>" --emails --phones
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```bash
|
|
11
12
|
"<python>" "<skill>/scripts/redact_docx.py" "<file.docx>" "<file-redacted.docx>" --pattern "ACCT-\d{6}" --replacement "<account>"
|
|
12
13
|
```
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
- `--mask_char *` keeps the shape of the value visible (`***@***.com` style), `--replacement` swaps in a token, `--no_preserve_length` stops padding to the original length.
|
|
16
|
-
- `--include_comments` also rewrites `word/comments.xml`; without it, comments keep the original text.
|
|
17
|
-
- Applied to body, every header and footer, footnotes and endnotes.
|
|
15
|
+
`--pattern` is repeatable — use it for account numbers, case ids and postcodes. It applies to the body, every header and footer, footnotes and endnotes.
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
| Flag | Effect |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `--mask_char *` | masks the shape of the value instead of replacing it (`***@***.com`) |
|
|
20
|
+
| `--replacement` | swap in a token instead of masking |
|
|
21
|
+
| `--no_preserve_length` | stop padding the result to the original length |
|
|
22
|
+
| `--include_comments` | also rewrite `word/comments.xml`; without it comments keep the original text |
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
Matching happens per text run, because merging runs destroys formatting, so a value split across runs is not found. That is acceptable only if you check the result; when formatting is mixed inside the sensitive value, flatten that paragraph first (`tasks/style_lint_normalize.md`).
|
|
22
25
|
|
|
23
|
-
Redaction removes
|
|
26
|
+
Redaction removes a value; anonymisation replaces it consistently. If the same account number appears four times and the argument depends on it matching, replace all occurrences with the same token rather than `[redacted]` every time — per-pattern `--replacement "<account-A>"` does that.
|
|
24
27
|
|
|
25
28
|
## Check the whole file
|
|
26
29
|
|
|
@@ -29,14 +32,10 @@ Redaction removes the value; anonymisation replaces it consistently. If the same
|
|
|
29
32
|
"<python>" "<skill>/scripts/docx_kit.py" xml '<file-redacted.docx>' word/document.xml | grep -c "example.com"
|
|
30
33
|
```
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
## Verify
|
|
35
|
-
|
|
36
|
-
Render, then read the pages the data appeared on. A redaction that changed the line length enough to shift the layout is worth a look; a redaction that left a `<w:t>` intact shows up in the grep, not the page.
|
|
35
|
+
Grepping the dumped XML for each sensitive term across document, header, footer, footnote and comment parts is the honest check. Then render and read the pages the data appeared on: check line-length shifts, and remember an intact `<w:t>` shows in the grep, not on the page.
|
|
37
36
|
|
|
38
37
|
## Pitfalls
|
|
39
38
|
|
|
40
|
-
- Field results and field instructions can both
|
|
41
|
-
- Images are not scanned
|
|
42
|
-
- Charts store category labels in `word/charts/*.xml`; inspect
|
|
39
|
+
- Field results and field instructions can both hold data; the script rewrites `w:t` / `w:delText` only.
|
|
40
|
+
- Images are not scanned. Say so plainly instead of implying the file is clean.
|
|
41
|
+
- Charts store category labels in `word/charts/*.xml`; inspect them when a chart carried the data.
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
# Sections
|
|
1
|
+
# Sections, page setup, headers
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Page setup lives in a `w:sectPr` element. The last one sits at the end of the body; earlier ones sit inside the last paragraph of the section they end (`w:pPr/w:sectPr`). That is why adding a landscape page means adding a section break before and after, not changing one global setting.
|
|
3
|
+
Page setup lives in `w:sectPr`. The last one sits at the end of the body; earlier ones sit inside the last paragraph of the section they end (`w:pPr/w:sectPr`). A landscape page therefore needs a section break before and after it, not one global setting.
|
|
6
4
|
|
|
7
5
|
## Audit
|
|
8
6
|
|
|
@@ -10,45 +8,40 @@ Page setup lives in a `w:sectPr` element. The last one sits at the end of the bo
|
|
|
10
8
|
"<python>" "<skill>/scripts/section_audit.py" "<file.docx>"
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Findings worth fixing: `mixed_page_sizes` (sections that disagree, usually a template artefact), `narrow_text_column` (left plus right margin eats most of the width), `portrait_wider_than_letter`.
|
|
11
|
+
Reports per section: width and height in inches, orientation derived from them, every margin, column count, section start type, header and footer parts referenced, `titlePg`, explicit page-number start. Findings: `mixed_page_sizes`, `narrow_text_column` (left and right margin eat most of the width), `portrait_wider_than_letter`.
|
|
16
12
|
|
|
17
13
|
## Common changes
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
| Different first page header | set `titlePg` on the section and fill `first_page_header` |
|
|
24
|
-
| Continuous section change (e.g. narrower margins) | `w:type val="continuous"` on the new section |
|
|
15
|
+
- New-document defaults: `docx create` spec keys `page_size`, `orientation`, `top_margin` / `bottom_margin` / `left_margin` / `right_margin` (inches).
|
|
16
|
+
- A landscape page inside a portrait document: two section breaks with the landscape section between them.
|
|
17
|
+
- A different first-page header: set `titlePg` and fill `first_page_header`.
|
|
18
|
+
- A continuous change such as narrower margins: `w:type val="continuous"`.
|
|
25
19
|
|
|
26
|
-
Landscape and columns are section properties, so a snippet is often faster than a
|
|
20
|
+
Landscape and columns are section properties, so a snippet is often faster than a script:
|
|
27
21
|
|
|
28
|
-
```
|
|
29
|
-
"<python>" - <<'PY'
|
|
22
|
+
```python
|
|
30
23
|
from docx import Document
|
|
31
24
|
from docx.enum.section import WD_ORIENT, WD_SECTION
|
|
25
|
+
|
|
32
26
|
d = Document(r"<file.docx>")
|
|
33
27
|
s = d.add_section(WD_SECTION.NEW_PAGE)
|
|
34
28
|
s.orientation = WD_ORIENT.LANDSCAPE
|
|
35
29
|
s.page_width, s.page_height = s.page_height, s.page_width
|
|
36
30
|
d.save(r"<file-edited.docx>")
|
|
37
|
-
PY
|
|
38
31
|
```
|
|
39
32
|
|
|
40
|
-
|
|
33
|
+
Setting the orientation alone does not swap the two size values.
|
|
41
34
|
|
|
42
35
|
## Headers and footers
|
|
43
36
|
|
|
44
|
-
|
|
37
|
+
`word/headerN.xml` is referenced from `w:headerReference`. A header inherits from the previous section unless the section sets its own reference. Page numbers in the footer are a `PAGE` field; counts are `NUMPAGES`; `docx_ooxml_patch.py --add-page-numbers` writes one.
|
|
45
38
|
|
|
46
39
|
## Verify
|
|
47
40
|
|
|
48
|
-
Render and flip through every page, including the
|
|
41
|
+
Render and flip through every page, including the pages after a section break: header content, page numbers, orientation, margins. The classic defect is a section break that leaves an empty page — look for a stray empty paragraph carrying it.
|
|
49
42
|
|
|
50
43
|
## Pitfalls
|
|
51
44
|
|
|
52
|
-
- `w:pgSz`
|
|
53
|
-
- `titlePg` without
|
|
54
|
-
- Deleting a section break merges two sections and keeps the earlier
|
|
45
|
+
- `w:pgSz` holds width then height; a "landscape" 11×8.5 still marked portrait means only the size values changed.
|
|
46
|
+
- `titlePg` without a filled first-page header leaves page one blank instead of showing the normal header.
|
|
47
|
+
- Deleting a section break merges two sections and keeps the earlier settings.
|
|
@@ -1,44 +1,40 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Style lint and normalisation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
A paragraph carries a `pStyle` reference; a run carries an `rStyle` plus direct formatting (`w:b`, `w:i`, `w:sz`, `w:color`). Direct formatting wins over the style, which is why a "make all body text 11pt" request fails on the runs someone bolded and resized by hand.
|
|
3
|
+
A paragraph carries `pStyle`; a run carries `rStyle` plus direct formatting (`w:b`, `w:i`, `w:sz`, `w:color`). Direct formatting wins over the style, which is why "make all body text 11pt" fails on hand-formatted runs.
|
|
6
4
|
|
|
7
5
|
## Lint
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
"<python>" "<skill>/scripts/style_lint.py" "<file.docx>" --json "<report.json
|
|
8
|
+
"<python>" "<skill>/scripts/style_lint.py" "<file.docx>" --json "<report.json"
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
Findings: undefined style references, headings carrying their own paragraph or run formatting, empty runs, manual bullets (`•` typed
|
|
14
|
-
|
|
15
|
-
Exit code 1 means findings; the JSON has the counts and examples, which is what you want when the number is large.
|
|
11
|
+
Findings: undefined style references, headings carrying their own paragraph or run formatting, empty runs, manual bullets (`•` typed instead of a list style), double spaces, trailing spaces, body sizes appearing once or twice against the dominant size, tables without a style. Exit 1 means findings; the JSON holds counts and examples.
|
|
16
12
|
|
|
17
|
-
##
|
|
13
|
+
## Normalise
|
|
18
14
|
|
|
19
15
|
```bash
|
|
20
|
-
"<python>" "<skill>/scripts/style_normalize.py" "<file.docx>" --out "<file-clean.docx
|
|
21
|
-
--clear_paragraph_format --enforce_heading_spacing
|
|
16
|
+
"<python>" "<skill>/scripts/style_normalize.py" "<file.docx>" --out "<file-clean.docx" --clear_paragraph_format --enforce_heading_spacing
|
|
22
17
|
```
|
|
23
18
|
|
|
24
|
-
- `--clear_paragraph_format` strips everything from each run's `rPr` except `rStyle`, `rtl` and `lang`, and drops the paragraph-mark run properties
|
|
25
|
-
- `--enforce_heading_spacing` sets space-before
|
|
19
|
+
- `--clear_paragraph_format` strips everything from each run's `rPr` except `rStyle`, `rtl` and `lang`, and drops the paragraph-mark run properties, so the style controls the look.
|
|
20
|
+
- `--enforce_heading_spacing` sets space-before and space-after per heading level plus `keepNext`.
|
|
26
21
|
|
|
27
|
-
Run
|
|
22
|
+
Run it before applying a template: a template cannot win against direct formatting (`tasks/templates_style_packs.md`).
|
|
28
23
|
|
|
29
|
-
##
|
|
24
|
+
## Correct look
|
|
30
25
|
|
|
31
|
-
- Body text 11–12pt from `Normal
|
|
32
|
-
-
|
|
33
|
-
-
|
|
26
|
+
- Body text 11–12pt from `Normal`.
|
|
27
|
+
- Headings from `Heading 1`–`Heading 3`, with no direct bold or size.
|
|
28
|
+
- Bold for in-sentence emphasis via a character style or one `w:b` run, never for whole paragraphs that are really headings.
|
|
29
|
+
- Lists from `List Bullet` / `List Number`, never typed bullets.
|
|
34
30
|
- One table style per document, usually `Table Grid`.
|
|
35
31
|
|
|
36
32
|
## Verify
|
|
37
33
|
|
|
38
|
-
Re-run `style_lint.py`
|
|
34
|
+
Re-run `style_lint.py` and watch the counts drop, then render. Clearing direct formatting can change the hierarchy when a document faked emphasis with size; compare against the original before claiming the change was neutral.
|
|
39
35
|
|
|
40
36
|
## Pitfalls
|
|
41
37
|
|
|
42
|
-
- Clearing `rPr` removes field-result formatting
|
|
43
|
-
- A style referenced but not defined falls back to `Normal` silently — the lint is the only warning
|
|
44
|
-
- `w:rsid` and proofing attributes are noise,
|
|
38
|
+
- Clearing `rPr` also removes field-result formatting; re-run the field scripts if a number must stay bold.
|
|
39
|
+
- A style that is referenced but not defined falls back to `Normal` silently — the lint is the only warning.
|
|
40
|
+
- `w:rsid` and proofing attributes are noise, removed by `privacy_scrub.py` when the file leaves (`tasks/privacy_scrub_metadata.md`).
|