tools-update-cron: sync 2026-08-16 — 16 skill(s) updated
This commit is contained in:
+163
-94
@@ -1,127 +1,196 @@
|
||||
---
|
||||
name: docx
|
||||
description: "Create, read, edit Word .docx documents and templates."
|
||||
version: 1.0.0
|
||||
author: Anthropic (adapted by Nous Research)
|
||||
license: Proprietary. LICENSE.txt has complete terms
|
||||
description: Create, read, edit, template, and review Word .docx files.
|
||||
version: 1.1.0
|
||||
author: Nous Research
|
||||
license: MIT
|
||||
platforms: [linux, macos, windows]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [Word, DOCX, Documents, Office, Productivity]
|
||||
tags: [word, docx, documents, office, templates, revisions, comments]
|
||||
category: productivity
|
||||
related_skills: [pdf, xlsx, powerpoint, ocr-and-documents]
|
||||
related_skills: [pdf, xlsx, powerpoint]
|
||||
---
|
||||
|
||||
# DOCX Skill
|
||||
# Docx Skill
|
||||
|
||||
Create, read, and edit Word documents — reports, memos, letters, letterheads, tables of contents, tracked changes (redlining), and comments. A `.docx` is a ZIP archive of XML files; this skill covers both the high-level creation path and surgical XML editing.
|
||||
Create, read, edit, and template Microsoft Word `.docx` files with
|
||||
python-docx via small CLIs. It handles text, styles, lists, tables,
|
||||
images, headers/footers, `{{token}}` templating, tracked changes
|
||||
(list/accept/reject), comments (list/add/delete), TOC and page-number
|
||||
fields, and package health checks. It does not render documents itself
|
||||
(PDF needs LibreOffice — see Converting to PDF) or edit legacy `.doc`.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx) or Word templates (.dotx). Triggers include: any mention of "Word doc", ".docx", ".dotx", or requests for a "report", "memo", "letter", or similar deliverable as a Word file; extracting or reorganizing content from .docx files; find-and-replace in Word files; inserting images; tracked changes or comments. Do NOT use for PDFs (see the `pdf` skill), spreadsheets (`xlsx`), or presentations (`powerpoint`).
|
||||
- The user asks to generate a Word document (report, letter, contract).
|
||||
- You need the text, outline, styles, or embedded images of a `.docx`.
|
||||
- You must change an existing `.docx`: replace text, edit table cells,
|
||||
insert/delete paragraphs, apply styles, merge fragmented runs.
|
||||
- You have a `.docx` template with `{{placeholders}}` to fill from data.
|
||||
- The document has tracked changes to review, accept, or reject.
|
||||
- You need to read reviewers' comments, or add/delete comments.
|
||||
- A `.docx` won't open or behaves oddly and you need corruption triage.
|
||||
- The document needs a table of contents or "Page X of Y" footers.
|
||||
- Not for: `.doc` (legacy), `.odt`, or WYSIWYG layout work.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
npm ls docx --depth=0 2>/dev/null | grep -q docx || npm install docx # creation (docx-js)
|
||||
pip show pandoc >/dev/null 2>&1 || true; which pandoc || sudo apt install -y pandoc # reading
|
||||
which soffice || sudo apt install -y libreoffice # rendering/verification
|
||||
which pdftoppm || sudo apt install -y poppler-utils # PDF → images
|
||||
pip install defusedxml lxml # validation scripts
|
||||
```
|
||||
- Python 3.10+ with `python-docx` installed:
|
||||
`pip install python-docx` (import name is `docx`; lxml comes with it).
|
||||
- Comments `add` uses the native API on python-docx >= 1.2 and an XML
|
||||
fallback on older versions — both are automatic.
|
||||
- For image blocks: the image files must exist locally (PNG/JPEG).
|
||||
|
||||
macOS: `brew install pandoc libreoffice poppler`.
|
||||
## How to Run
|
||||
|
||||
All helpers live in `scripts/` next to this file. Run them with the
|
||||
`terminal` tool; each supports `--help` and prints JSON to stdout.
|
||||
|
||||
```bash
|
||||
python scripts/docx_create.py spec.json out.docx
|
||||
python scripts/docx_read.py out.docx --text
|
||||
python scripts/docx_edit.py replace out.docx --find old --replace new
|
||||
python scripts/docx_template.py tpl.docx values.json filled.docx
|
||||
python scripts/docx_revisions.py list out.docx
|
||||
python scripts/docx_comments.py list out.docx
|
||||
python scripts/docx_validate.py out.docx
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Approach |
|
||||
|---|---|
|
||||
| **Create** a new document | Write a `docx` (npm) script — see gotchas below |
|
||||
| **Edit** an existing document | `unzip` → edit `word/document.xml` → `zip` (docx-js cannot open existing files) |
|
||||
| **Read** content | `pandoc -t markdown file.docx` (or `read_file`, which auto-extracts .docx text) |
|
||||
| Task | Command |
|
||||
| --- | --- |
|
||||
| Create from JSON spec | `docx_create.py spec.json out.docx` |
|
||||
| Full text (body+tables+headers/footers) | `docx_read.py f.docx --text` |
|
||||
| Heading outline + table shapes | `docx_read.py f.docx --structure` |
|
||||
| Styles actually used | `docx_read.py f.docx --styles` |
|
||||
| Extract embedded images | `docx_read.py f.docx --images outdir/` |
|
||||
| Detect tracked changes/comments | `docx_read.py f.docx --revisions` |
|
||||
| Find/replace (formatting kept) | `docx_edit.py replace f.docx --find A --replace B -o out.docx` |
|
||||
| Set a table cell | `docx_edit.py set-cell f.docx --table 0 --row 1 --col 2 --text X` |
|
||||
| Insert paragraph before index N | `docx_edit.py insert f.docx --index N --text X --style Normal` |
|
||||
| Delete paragraph N | `docx_edit.py delete f.docx --index N` |
|
||||
| Apply style to paragraph N | `docx_edit.py style f.docx --index N --style "Heading 1"` |
|
||||
| Merge equal-format adjacent runs | `docx_edit.py normalize f.docx -o out.docx` |
|
||||
| Insert TOC field before para N | `docx_edit.py toc f.docx --index N -o out.docx` |
|
||||
| "Page X of Y" footer fields | `docx_edit.py page-numbers f.docx` |
|
||||
| Fill `{{tokens}}` | `docx_template.py tpl.docx values.json out.docx --strict` |
|
||||
| List revisions (id/author/date/text) | `docx_revisions.py list f.docx` |
|
||||
| Accept / reject all revisions | `docx_revisions.py accept-all f.docx -o out.docx` (or `reject-all`) |
|
||||
| Accept / reject one revision | `docx_revisions.py accept f.docx --id 3 -o out.docx` |
|
||||
| List comments (+anchored text) | `docx_comments.py list f.docx` |
|
||||
| Add comment anchored to text | `docx_comments.py add f.docx --target "phrase" --text "note" --author You` |
|
||||
| Delete comment by id | `docx_comments.py delete f.docx --id 0` |
|
||||
| Health-check the package | `docx_validate.py f.docx` (exit 1 on errors) |
|
||||
|
||||
> Script paths below are relative to this skill's directory.
|
||||
## Procedure
|
||||
|
||||
## Creating with docx-js — gotchas
|
||||
1. **Create.** Write a JSON spec with `write_file`, then run
|
||||
`scripts/docx_create.py`. The spec supports: `page` (size + margins in
|
||||
mm), `header`/`footer` strings, `footer_page_numbers` (adds a
|
||||
"Page X of Y" field footer), `styles` (custom paragraph styles with
|
||||
font, size, bold/italic, hex `color`), and `blocks` — `heading`
|
||||
(level 1-9), `paragraph` (either `text` or a `runs` list where each run
|
||||
may set `bold`/`italic`/`underline`), `bullet_list`, `numbered_list`,
|
||||
`table` (`header` row rendered bold, `rows`, optional built-in table
|
||||
`style` such as `Table Grid`), `image` (`path`, optional `width_mm`),
|
||||
`toc` (Table of Contents field), and `page_break`. The full spec
|
||||
format is documented at the top of `scripts/docx_create.py`.
|
||||
2. **Read.** Use `scripts/docx_read.py` with exactly one mode flag.
|
||||
`--text` returns body paragraphs, all table cell text, and
|
||||
header/footer text as JSON. `--structure` returns the heading outline
|
||||
plus paragraph/table/section counts. `--images DIR` copies every file
|
||||
under `word/media/` out of the package.
|
||||
3. **Edit.** Use `scripts/docx_edit.py`. `replace` walks body, tables
|
||||
(nested included), headers and footers, and preserves run formatting;
|
||||
add `--body-only` to skip headers/footers. Pass `-o out.docx` to keep
|
||||
the original; omit it to edit in place. Paragraph indices for
|
||||
`insert`/`delete`/`style`/`toc` refer to `--structure`/`--text` body
|
||||
order. Run `normalize` first on documents that came out of heavy Word
|
||||
editing — it merges adjacent runs with identical formatting so later
|
||||
find-replace matches reliably.
|
||||
4. **Review revisions.** `docx_revisions.py list` reports every `w:ins`
|
||||
and `w:del` (id, author, date, affected text) anywhere in body,
|
||||
tables, headers, or footers. `accept-all` / `reject-all` resolve them
|
||||
in bulk; `accept`/`reject --id N` handles a single revision. Accept
|
||||
keeps insertions and drops deleted text; reject does the reverse.
|
||||
5. **Comments.** `docx_comments.py list` returns each comment's id,
|
||||
author, date, body text, and the document text it is anchored to.
|
||||
`add --target "some phrase"` anchors a new comment to the first
|
||||
occurrence of that phrase (runs are split as needed; formatting is
|
||||
preserved). `delete --id N` removes the comment and its markers
|
||||
without touching document text.
|
||||
6. **Template.** Put `{{name}}`-style tokens in the document. Run
|
||||
`scripts/docx_template.py` with a JSON object of values. Use
|
||||
`--strict` to fail when tokens remain unfilled; the JSON output lists
|
||||
`filled` counts and `unfilled_tokens` either way.
|
||||
7. **Verify** (always): re-read the output with `--text` or
|
||||
`--structure`, and run `docx_validate.py` on anything you produced
|
||||
via revision/comment surgery.
|
||||
|
||||
Write the script and `require('docx')`. The model knows the API; these are the footguns:
|
||||
## Converting to PDF
|
||||
|
||||
- **Page size defaults to A4.** For US Letter set `page: { size: { width: 12240, height: 15840 } }` (DXA; 1440 = 1″).
|
||||
- **Landscape:** pass portrait dimensions and `orientation: PageOrientation.LANDSCAPE` — docx-js swaps width/height internally.
|
||||
- **Tables need dual widths:** set `columnWidths` on the table AND `width` on every cell, both in `WidthType.DXA` (PERCENTAGE breaks in Google Docs). Column widths must sum to the table width.
|
||||
- **Table shading:** use `ShadingType.CLEAR`, never `SOLID` (renders black).
|
||||
- **Lists:** never insert `•` literally; use a `numbering` config with `LevelFormat.BULLET`.
|
||||
- **`ImageRun` requires `type:`** (`"png"`, `"jpg"`, …).
|
||||
- **`PageBreak` must be inside a `Paragraph`.**
|
||||
- **Never use `\n`** — use separate `Paragraph` elements.
|
||||
- **TOC:** headings must use built-in `HeadingLevel.*`; custom heading styles need `outlineLevel` set or they won't appear.
|
||||
- **Don't use a table as a horizontal rule** — use a paragraph bottom border instead.
|
||||
- **Dot-leader / right-aligned-on-same-line:** use `PositionalTab` (`alignment: PositionalTabAlignment.RIGHT`, `leader: PositionalTabLeader.DOT`) inside a `TextRun`, not literal `.` or space padding.
|
||||
|
||||
## Verify the output
|
||||
|
||||
After writing a `.docx`, render it and look at it:
|
||||
No script needed. When LibreOffice is installed, convert headlessly:
|
||||
|
||||
```bash
|
||||
python scripts/office/soffice.py --headless --convert-to pdf output.docx
|
||||
pdftoppm -jpeg -r 100 output.pdf page
|
||||
ls page-*.jpg # then inspect each with vision_analyze
|
||||
soffice --headless --convert-to pdf --outdir outdir/ file.docx
|
||||
```
|
||||
|
||||
`pdftoppm` zero-pads page numbers to the width of the page count (`page-01.jpg`…`page-12.jpg`).
|
||||
|
||||
## Editing existing documents
|
||||
|
||||
Legacy `.doc` files must be converted first: `python scripts/office/soffice.py --headless --convert-to docx file.doc`.
|
||||
|
||||
```bash
|
||||
unzip -q doc.docx -d unpacked/
|
||||
find unpacked -type l -delete # strip symlink entries — docx from external parties is untrusted
|
||||
python scripts/merge_runs.py unpacked/ # coalesce fragmented runs so text is findable
|
||||
# edit unpacked/word/document.xml in place — do NOT reformat or pretty-print
|
||||
(cd unpacked && rm -f ../out.docx && zip -Xr ../out.docx .)
|
||||
python scripts/office/validate.py out.docx --original doc.docx # XSD checks; --auto-repair fixes common issues
|
||||
# redlining? add --author "<the name you redlined under>" to check every edit is tracked
|
||||
```
|
||||
|
||||
Word splits text across many `<w:r>` runs (revision ids, spell-check markers), so a phrase you can see in the document often doesn't exist as a contiguous string in the XML. `merge_runs.py` merges adjacent identically-formatted runs in `word/document.xml` without changing content or rendering; it also accepts a `.docx` directly (`python scripts/merge_runs.py doc.docx -o merged.docx`).
|
||||
|
||||
**Tracked changes:** when redlining, validate with `--author "<the name you redlined under>"` (needs `--original`) — it reports any text you changed without a `<w:ins>`/`<w:del>` around it, which is easy to do by accident and invisible in the accepted view. Wrap runs in `<w:ins>`/`<w:del>` with `w:id`, `w:author`, `w:date` attributes. Inside `<w:del>`, the text element is `<w:delText>`, not `<w:t>`. A deleted paragraph mark (`<w:pPr><w:rPr><w:del w:id=".." w:author=".." w:date=".."/></w:rPr></w:pPr>`) means "merge this paragraph into the next" — so deleting a paragraph outright is that plus a `<w:del>` around every run. The `<w:del/>` must come before the rPr's other children; their order is schema-enforced.
|
||||
|
||||
To produce a clean copy with all tracked changes accepted: `python scripts/accept_changes.py in.docx out.docx`.
|
||||
|
||||
Accepting a deleted paragraph mark should join that paragraph to the one below it, so a paragraph whose runs are *all* deleted vanishes. Word does this; `accept_changes.py` and `pandoc --track-changes=accept` don't always. Both fail the same way — they strip the deleted text but leave the emptied paragraph behind, which reads as a stray empty bullet when it was auto-numbered:
|
||||
|
||||
- `pandoc --track-changes=accept` never joins the paragraphs.
|
||||
- `accept_changes.py` (LibreOffice) joins them correctly, except when the deleted paragraph is followed by an empty spacer paragraph.
|
||||
|
||||
An empty bullet in either view is an artifact of that view, not a defect in the document. Check paragraph deletions in the XML.
|
||||
|
||||
## Comments
|
||||
|
||||
Comments require six cross-linked files. Use the helper — directory mode when you'll also be editing `document.xml` (saves an unzip/rezip cycle), `.docx`-direct mode otherwise:
|
||||
|
||||
```bash
|
||||
# Against an already-unpacked directory (preferred when also placing markers)
|
||||
python scripts/comment.py unpacked/ "Fees & expenses cap is too low"
|
||||
python scripts/comment.py unpacked/ "Agreed" --parent 0
|
||||
|
||||
# Against a .docx directly
|
||||
python scripts/comment.py contract.docx "This cap is too low" -o annotated.docx
|
||||
```
|
||||
|
||||
The script writes `comments.xml`, `commentsExtended.xml`, `commentsIds.xml`, `commentsExtensible.xml`, the relationships, and the content-type overrides. Comment IDs are auto-assigned. It then prints the `<w:commentRangeStart>`/`<w:commentRangeEnd>`/`<w:commentReference>` snippet to add to `word/document.xml` so the comment anchors to specific text — until you place those markers, the comment exists but is not visible.
|
||||
Check availability first (`command -v soffice || command -v
|
||||
libreoffice`). If neither exists, tell the user PDF conversion is
|
||||
unavailable in this environment rather than improvising — python-docx
|
||||
cannot render PDFs, and layout fidelity requires a real renderer.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Don't round-trip OOXML through `xml.etree.ElementTree` — it rewrites namespace prefixes and corrupts the file. Use `defusedxml.minidom` for scripted transforms.
|
||||
- Zip from INSIDE the unpacked directory (`cd unpacked && zip -Xr ../out.docx .`) and `rm` the target first, or deleted parts survive in the archive.
|
||||
- **Tokens split across runs.** Word often fragments text into several
|
||||
runs. The replace helpers collapse matched runs (replacement inherits
|
||||
the first run's formatting); running `docx_edit.py normalize` first
|
||||
reduces fragmentation for all later edits.
|
||||
- **Revision coverage.** `docx_revisions.py` resolves run-level
|
||||
insertions and deletions (the overwhelming majority). Paragraph-mark
|
||||
and table-row revisions, format-change records, and moves are detected
|
||||
by `--revisions` but not auto-resolved — see
|
||||
`references/revisions-and-comments.md` and hand those to Word.
|
||||
- **Comment threading.** Replies and "resolved" status live in
|
||||
`commentsExtended.xml`, which this skill ignores; comments it adds are
|
||||
plain top-level comments.
|
||||
- **Field results are computed by Word.** `toc`, `page-numbers`, and the
|
||||
`toc`/`footer_page_numbers` spec options write *field codes*.
|
||||
Word/LibreOffice populates the actual entries and numbers when the
|
||||
file is opened (Word may prompt to update fields); python-docx never
|
||||
computes them, so placeholder text shows until then.
|
||||
- **Validation is a health check, not schema validation.**
|
||||
`docx_validate.py` verifies the zip, required parts, relationship
|
||||
targets, image magic bytes, and referenced styles. It is NOT XSD
|
||||
validation — a file can pass and still contain XML Word dislikes.
|
||||
- **Style names must exist.** Applying a style that isn't defined in the
|
||||
document raises `KeyError`. Built-ins like `Heading 1`, `List Bullet`,
|
||||
`List Number`, `Table Grid` exist in the default template; custom
|
||||
styles must be declared in the create spec first.
|
||||
- **Numbered lists restart.** `List Number` relies on Word's default
|
||||
numbering; separate lists in one document may continue numbering
|
||||
instead of restarting. Warn users needing precise multi-list numbering.
|
||||
- **Cell writes replace formatting.** `set-cell` uses `cell.text = ...`,
|
||||
which resets runs in that cell to plain formatting.
|
||||
- **Encoding.** All JSON specs/values files are read as UTF-8 explicitly;
|
||||
never rely on locale defaults when writing your own glue code.
|
||||
- **Don't unzip-and-sed the XML.** Edit through the scripts (or
|
||||
python-docx); raw text substitution in `document.xml` corrupts files
|
||||
easily. Use `patch`/`write_file` only for the JSON inputs, never on the
|
||||
`.docx` itself.
|
||||
|
||||
## Verification
|
||||
|
||||
1. `python scripts/office/validate.py out.docx --original in.docx` — schema, relationship, and content-type checks; every failure names its fix.
|
||||
2. Render to PDF → images (see "Verify the output") and inspect each page with `vision_analyze` — look for broken tables, missing images, spacing artifacts, leftover placeholder text.
|
||||
|
||||
## Related skills
|
||||
|
||||
`pdf` (PDF work), `xlsx` (spreadsheets), `powerpoint` (decks), `ocr-and-documents` (scanned input extraction).
|
||||
- After create/edit/template, run `docx_read.py out.docx --text` and
|
||||
check the expected strings appear (and old strings are gone).
|
||||
- After accept/reject, `docx_revisions.py list` should return `[]` (or
|
||||
only the ids you intentionally left); after comment surgery,
|
||||
`docx_comments.py list` should reflect the change and `--text` output
|
||||
must be unchanged.
|
||||
- `docx_validate.py out.docx` exits 0 with `"ok": true` on a healthy
|
||||
package — run it after any revision/comment/field manipulation.
|
||||
- For templates run with `--strict`, or check `unfilled_tokens == []`.
|
||||
- Structure checks: `--structure` should show the expected heading
|
||||
outline and table shapes; `--styles` confirms custom styles applied.
|
||||
|
||||
Reference in New Issue
Block a user