> ## Documentation Index
> Fetch the complete documentation index at: https://docs.profy.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# Word Documents

> profy-docx — create, edit, and analyse .docx: docx-js generation, unpack-and-edit XML, cross-run text replacement, tracked changes and comments

# Word Documents (profy-docx)

Create, edit, and analyse Word documents. Understanding this capability starts with accepting one fact: **a `.docx` file is a ZIP archive containing XML**. Every constraint below that looks arbitrary follows from that structure.

## Activation

`profy-docx` is `user_selectable: false` — **automatically available, nothing to tick**.

```json theme={null}
{ "id": "profy-docx", "activation": { "user_selectable": false } }
```

`contracts` declares only `skills: ["skills/"]` and **no tools**. The expert runs scripts via `bash` in the sandbox; the skill document governs how.

Triggers include: Word documents, `.docx`, requests for polished deliverables with tables of contents, headings, page numbers, or letterheads, plus extracting or reorganising content from existing docx files, inserting or replacing images, find-and-replace, and working with tracked changes or comments. **PDFs, spreadsheets, and Google Docs do not take this path.**

## Three main routes

| Task                      | Approach                        |
| ------------------------- | ------------------------------- |
| Read / analyse content    | `pandoc`, or unpack for raw XML |
| Create a new document     | `docx-js` (JavaScript)          |
| Edit an existing document | Unpack → edit XML → repack      |

### Reading

```bash theme={null}
# Text extraction with tracked changes preserved
pandoc --track-changes=all document.docx -o output.md

# Raw XML access
python scripts/office/unpack.py document.docx unpacked/
```

### Converting legacy .doc

```bash theme={null}
python scripts/office/soffice.py --headless --convert-to docx document.doc
```

`.doc` is an entirely different binary format and must be converted before it can be edited.

### Converting to images for visual inspection

```bash theme={null}
python scripts/office/soffice.py --headless --convert-to pdf document.docx
pdftoppm -jpeg -r 150 document.pdf page
```

### Accepting tracked changes

```bash theme={null}
python scripts/accept_changes.py input.docx output.docx
```

Requires LibreOffice. Produces a clean document with every tracked change accepted.

## Text replacement: use the script, do not write your own

```bash theme={null}
# Replace the first occurrence
python scripts/office/docx_replace.py --file document.docx --search "old text" --replace "new text"

# Replace all occurrences
python scripts/office/docx_replace.py --file document.docx --search "old" --replace "new" --all

# Write to a different file
python scripts/office/docx_replace.py --file document.docx --search "old" --replace "new" --output result.docx

# Restrict to one paragraph (0-based)
python scripts/office/docx_replace.py --file document.docx --search "old" --replace "new" --paragraph 3
```

Exit codes:

| Code | Meaning                                 |
| ---- | --------------------------------------- |
| `0`  | Success, and verified                   |
| `1`  | Text not found — check stderr for hints |
| `2`  | Verification failed                     |

<Warning>
  **Do not hand-write python-docx scripts for simple find-and-replace.** Word splits a single sentence across multiple `run` elements — because one word is bold, or because the spell checker inserted a marker — so the string you are searching for is not contiguous in the XML at all. Hand-written scripts therefore **fail silently**: no error, just no replacement. `docx_replace.py` handles run-boundary splitting and verifies the result afterwards, which is exactly what exit code `2` reports.
</Warning>

This is a good general lesson about document formats: the visual model (a sentence) and the storage model (a run sequence) do not correspond, and any tool that assumes they do will be wrong intermittently rather than consistently — the worst possible failure mode.

## Creating documents: docx-js constraints

```bash theme={null}
npm install -g docx
```

None of these are style preferences. Each corresponds to a concrete failure where the output either will not open or renders incorrectly somewhere.

### Structure

| Constraint                                 | Detail                                                                                                                                                  |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Set page size explicitly                   | docx-js defaults to A4; US documents need US Letter (12240 × 15840 DXA)                                                                                 |
| Landscape takes portrait dimensions        | docx-js swaps width and height internally: pass the short edge as `width`, the long edge as `height`, then set `orientation: PageOrientation.LANDSCAPE` |
| Never use `\n`                             | Use separate `Paragraph` elements for line breaks                                                                                                       |
| `PageBreak` must live inside a `Paragraph` | Standalone produces invalid XML                                                                                                                         |
| `ImageRun` requires `type`                 | png / jpg / etc., never omitted                                                                                                                         |

### Lists and tables

| Constraint                                  | Detail                                                                                                                                                                                                |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Never use unicode bullets                   | Use `LevelFormat.BULLET` with a numbering config                                                                                                                                                      |
| Table widths must use DXA                   | `WidthType.PERCENTAGE` breaks in Google Docs                                                                                                                                                          |
| Tables need dual widths                     | Both the `columnWidths` array and per-cell `width`, and they must agree                                                                                                                               |
| Table width equals the sum of column widths | Under DXA they must add up exactly                                                                                                                                                                    |
| Always add cell margins                     | `margins: { top: 80, bottom: 80, left: 120, right: 120 }`                                                                                                                                             |
| Use `ShadingType.CLEAR`                     | Never SOLID for table shading                                                                                                                                                                         |
| **Never use tables as dividers or rules**   | Cells have a minimum height and render as empty boxes, including in headers and footers. For a horizontal rule use `border.bottom` on a `Paragraph`; for two-column footers use tab stops, not tables |

### Tables of contents

| Constraint                           | Detail                                           |
| ------------------------------------ | ------------------------------------------------ |
| TOC recognises `HeadingLevel` only   | No custom styles on heading paragraphs           |
| Override built-in styles by exact ID | `"Heading1"`, `"Heading2"`                       |
| Include `outlineLevel`               | Required for TOC — 0 for H1, 1 for H2, and so on |

## Supported typesetting features

Page size, style overrides, multi-level lists, tables, images, page breaks, hyperlinks, footnotes, tab stops, multi-column layouts, tables of contents, headers and footers.

## Boundaries and failure modes

* **`.doc` must be converted first.** Editing it directly fails.
* **Hand-written replacement scripts fail silently** (see the run-boundary warning above).
* **Accepting tracked changes depends on LibreOffice**; without it `accept_changes.py` cannot run.
* **Unpacking and editing XML is a sharp tool.** Malformed XML makes Word refuse to open the file rather than degrade gracefully, so edits must be validated afterwards.
* **Tables render inconsistently across renderers.** Most of the constraints above (DXA, dual widths, CLEAR shading) exist to keep output correct outside Word — in Google Docs, WPS, and preview tools.

### Troubleshooting

| Symptom                                 | Cause                                                                    | Fix                                                       |
| --------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------- |
| Replace script exits 1                  | Text not found — likely split across runs, or contains hidden characters | Read the stderr hints and try a shorter search string     |
| Replace script exits 2                  | The replacement was written but verification failed                      | Document structure is unusual; switch to the unpack route |
| Word refuses to open the generated file | Invalid XML, commonly a standalone `PageBreak`                           | Wrap the page break in a `Paragraph`                      |
| Tables collapse in Google Docs          | Percentage widths were used                                              | Switch to DXA and make total width equal the column sum   |
| Table of contents is empty              | Heading paragraphs carry custom styles, or `outlineLevel` is missing     | Use `HeadingLevel` only and add `outlineLevel`            |
| Odd empty boxes in the header           | A table was used to draw a divider                                       | Replace with a `Paragraph` bottom border                  |

## Verify your output

1. Convert to images and look: `soffice.py --convert-to pdf` then `pdftoppm`. **This catches the overwhelming majority of layout accidents** and is far faster than reading XML.
2. If there is a table of contents, confirm the entry count matches the actual heading count.
3. If there are tables, open the file in Google Docs too — it is the least forgiving about width definitions, so passing there generally means passing everywhere.

## Related

<CardGroup cols={2}>
  <Card title="Office documents overview" href="/en/documentation/capabilities/office-documents">
    How the four document capabilities differ and when to use each
  </Card>

  <Card title="PDF documents" href="/en/documentation/capabilities/office-pdf">
    The alternative route when the deliverable needs real typesetting
  </Card>
</CardGroup>

<Note>
  Verified 2026-08-11. Sources: `services/agent-runtime/src/plugins/builtin/docx/plugin.json`, `skills/SKILL.md`, `skills/scripts/office/docx_replace.py`, `skills/scripts/accept_changes.py`, `skills/scripts/office/{unpack,pack,soffice}.py`.
</Note>
