197 lines
10 KiB
Markdown
197 lines
10 KiB
Markdown
---
|
|
name: docx
|
|
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, templates, revisions, comments]
|
|
category: productivity
|
|
related_skills: [pdf, xlsx, powerpoint]
|
|
---
|
|
|
|
# Docx Skill
|
|
|
|
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
|
|
|
|
- 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
|
|
|
|
- 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).
|
|
|
|
## 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 | 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) |
|
|
|
|
## Procedure
|
|
|
|
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.
|
|
|
|
## Converting to PDF
|
|
|
|
No script needed. When LibreOffice is installed, convert headlessly:
|
|
|
|
```bash
|
|
soffice --headless --convert-to pdf --outdir outdir/ file.docx
|
|
```
|
|
|
|
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
|
|
|
|
- **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
|
|
|
|
- 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.
|