DOCX creation, editing, and analysis
Overview
A .docx file is a ZIP archive containing XML files.Quick Reference
Converting .doc to .docx
Legacy.doc files must be converted before editing:
.doc files created on Chinese Windows are often GBK-encoded internally. scripts/office/soffice.py automatically sets LANG=zh_CN.UTF-8 / LC_ALL=zh_CN.UTF-8 when the environment lacks a UTF-8 locale, so LibreOffice can decode CJK characters correctly during conversion. If Chinese text still appears garbled after conversion, verify that the zh_CN.UTF-8 locale is installed on the system:
Reading Content
Converting to Images
Accepting Tracked Changes
To produce a clean document with all tracked changes accepted (requires LibreOffice):Creating New Documents
Generate .docx files with JavaScript, then validate. Install:npm install -g docx
Setup
Validation
After creating the file, validate it. If validation fails, unpack, fix the XML, and repack.Page Size
Landscape orientation: docx-js swaps width/height internally, so pass portrait dimensions and let it handle the swap:
Styles (Override Built-in Headings)
Use Arial as the default font (universally supported). Keep titles black for readability.Lists (NEVER use unicode bullets)
Tables
CRITICAL: Tables need dual widths - set bothcolumnWidths on the table AND width on each cell. Without both, tables render incorrectly on some platforms.
WidthType.DXA — WidthType.PERCENTAGE breaks in Google Docs.
- Always use
WidthType.DXA— neverWidthType.PERCENTAGE(incompatible with Google Docs) - Table width must equal the sum of
columnWidths - Cell
widthmust match correspondingcolumnWidth - Cell
marginsare internal padding - they reduce content area, not add to cell width - For full-width tables: use content width (page width minus left and right margins)
Images
Page Breaks
Table of Contents
Headers/Footers
Critical Rules for docx-js
- Set page size explicitly - docx-js defaults to A4; use US Letter (12240 x 15840 DXA) for US documents
- Landscape: pass portrait dimensions - docx-js swaps width/height internally; pass short edge as
width, long edge asheight, and setorientation: PageOrientation.LANDSCAPE - Never use
\n- use separate Paragraph elements - Never use unicode bullets - use
LevelFormat.BULLETwith numbering config - PageBreak must be in Paragraph - standalone creates invalid XML
- ImageRun requires
type- always specify png/jpg/etc - Always set table
widthwith DXA - never useWidthType.PERCENTAGE(breaks in Google Docs) - Tables need dual widths -
columnWidthsarray AND cellwidth, both must match - Table width = sum of columnWidths - for DXA, ensure they add up exactly
- Always add cell margins - use
margins: { top: 80, bottom: 80, left: 120, right: 120 }for readable padding - Use
ShadingType.CLEAR- never SOLID for table shading - TOC requires HeadingLevel only - no custom styles on heading paragraphs
- Override built-in styles - use exact IDs: “Heading1”, “Heading2”, etc.
- Include
outlineLevel- required for TOC (0 for H1, 1 for H2, etc.)
Editing Existing Documents
Follow all 3 steps in order.Step 1: Unpack
“ etc.) so they survive editing. Use --merge-runs false to skip run merging.
Step 2: Edit XML
Edit files inunpacked/word/. See XML Reference below for patterns.
Use “Claude” as the author for tracked changes and comments, unless the user explicitly requests use of a different name.
Use the Edit tool directly for string replacement. Do not write Python scripts. Scripts introduce unnecessary complexity. The Edit tool shows exactly what is being replaced.
CRITICAL: Use smart quotes for new content. When adding text with apostrophes or quotes, use XML entities to produce smart quotes:
Adding comments: Use
comment.py to handle boilerplate across multiple XML files (text must be pre-escaped XML):
Step 3: Pack
--validate false to skip.
Auto-repair will fix:
durableId>= 0x7FFFFFFF (regenerates valid ID)- Missing
xml:space="preserve"on<w:t>with whitespace
- Malformed XML, invalid element nesting, missing relationships, schema violations
Common Pitfalls
- Replace entire
<w:r>elements: When adding tracked changes, replace the whole<w:r>...</w:r>block with<w:del>...<w:ins>...as siblings. Don’t inject tracked change tags inside a run. - Preserve
<w:rPr>formatting: Copy the original run’s<w:rPr>block into your tracked change runs to maintain bold, font size, etc.
XML Reference
Schema Compliance
- Element order in
<w:pPr>:<w:pStyle>,<w:numPr>,<w:spacing>,<w:ind>,<w:jc>,<w:rPr>last - Whitespace: Add
xml:space="preserve"to<w:t>with leading/trailing spaces - RSIDs: Must be 8-digit hex (e.g.,
00AB1234)
Tracked Changes
Insertion:<w:del>: Use <w:delText> instead of <w:t>, and <w:delInstrText> instead of <w:instrText>.
Minimal edits - only mark what changes:
<w:del/> inside <w:pPr><w:rPr>:
<w:del/> in <w:pPr><w:rPr>, accepting changes leaves an empty paragraph/list item.
Rejecting another author’s insertion - nest deletion inside their insertion:
Comments
After runningcomment.py (see Step 2), add markers to document.xml. For replies, use --parent flag and nest markers inside the parent’s.
CRITICAL: <w:commentRangeStart> and <w:commentRangeEnd> are siblings of <w:r>, never inside <w:r>.
Images
- Add image file to
word/media/ - Add relationship to
word/_rels/document.xml.rels:
- Add content type to
[Content_Types].xml:
- Reference in document.xml:
Dependencies
- pandoc: Text extraction
- docx:
npm install -g docx(new documents) - LibreOffice: PDF conversion (auto-configured for sandboxed environments via
scripts/office/soffice.py) - Poppler:
pdftoppmfor images