How to Work with XML in DOCX Files Using the Unpack/Pack Workflow
The unpack/pack workflow treats DOCX files as ZIP archives containing XML documents, allowing you to extract, edit, and rebuild Word documents programmatically using the unpack.py and pack.py scripts from the anthropics/skills repository.
Working with XML in DOCX files using the unpack/pack workflow provides a deterministic, scriptable approach to manipulating Word documents without proprietary software. The anthropics/skills repository implements this workflow through a pair of Python scripts that automate ZIP extraction, XML normalization, optional cleanup, and archive reconstruction. This approach enables direct manipulation of word/document.xml and other Open XML components using standard text editors or programmatic tools.
Unpacking DOCX Archives with unpack.py
The unpack.py script located at skills/docx/scripts/office/unpack.py serves as the entry point for the workflow. It extracts the DOCX ZIP contents to a specified directory and applies several normalization steps to make the XML human-readable and suitable for editing.
The function signature is:
unpack(input_file: str,
output_directory: str,
merge_runs: bool = True,
simplify_redlines: bool = True) -> tuple[None, str]
XML Normalization and Cleanup
During unpacking, the script performs several operations on the extracted XML files:
- Pretty-printing: Every
*.xmland*.relsfile is formatted with indentation to reveal the document structure - Smart quote escaping: Converts "smart quotes" to XML entities to prevent parsing errors
- Run merging: When
merge_runs=True, combines adjacent<w:r>elements with identical formatting to reduce noise caused by Word's editor - Redline simplification: When
simplify_redlines=True, merges adjacent<w:ins>or<w:del>elements from the same author to streamline tracked changes
Helper Modules
The cleanup operations rely on helper modules in skills/docx/scripts/office/helpers/:
merge_runs.py: Implements the logic for combining adjacent runs with identical propertiessimplify_redlines.py: Handles tracked change simplification and providesinfer_authorfunctionality for determining change authorship
Editing XML Content Directly
After unpacking, the document's main content resides at word/document.xml within the output directory. Because the XML has been pretty-printed, you can manipulate it using any text editor, XML-specific tools, or programmatic libraries.
Common editing tasks include:
- Adding custom
<w:customXml>elements for content controls - Adjusting paragraph properties (
<w:pPr>) for styling - Removing unwanted markup or namespaces
- Inserting new runs (
<w:r>) or text elements (<w:t>)
Repacking with pack.py
The pack.py script at skills/docx/scripts/office/pack.py reverses the process, converting the edited directory back into a valid DOCX file.
The function signature is:
pack(input_directory: str,
output_file: str,
original_file: str | None = None,
validate: bool = True,
infer_author_func=None) -> tuple[None, str]
XML Condensing and Validation
Before creating the ZIP archive, pack.py performs several critical steps:
- Condensing: The
_condense_xmlfunction removes whitespace-only text nodes, XML comments, and empty elements to minimize file size - Validation: When
validate=True, the script runs schema validators located inskills/docx/scripts/office/validators/to ensure Open XML compliance - Auto-repair: Validators can automatically fix common issues before finalizing the archive
Author Inference
If you provide the original_file parameter, the packing process can infer the author of new tracked changes using the infer_author functionality from helpers/simplify_redlines.py. This maintains proper attribution when adding new revisions to an existing document.
Practical Workflow Examples
Basic Command-Line Workflow
# Unpack the DOCX with default cleanup options
python -m skills.docx.scripts.office.unpack \
my_report.docx unpacked_dir/
# Edit word/document.xml using your preferred editor
# (Example: adding a custom XML element via Python)
python - <<'PY'
import pathlib
import xml.etree.ElementTree as ET
doc_path = pathlib.Path('unpacked_dir/word/document.xml')
tree = ET.parse(doc_path)
root = tree.getroot()
# Define namespace
ns = {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}
body = root.find('w:body', ns)
# Add custom XML element
custom = ET.Element('{%s}customXml' % ns['w'])
custom.text = 'Added by script'
body.append(custom)
tree.write(doc_path, encoding='utf-8', xml_declaration=True)
PY
# Repack with validation against original
python -m skills.docx.scripts.office.pack \
unpacked_dir/ edited_report.docx \
--original my_report.docx
Programmatic Library Usage
from pathlib import Path
from skills.docx.scripts.office.unpack import unpack
from skills.docx.scripts.office.pack import pack
import defusedxml.minidom as minidom
# Step 1: Unpack with specific options
_, msg = unpack(
input_file="contract.docx",
output_directory="temp_unpacked",
merge_runs=False, # Preserve original run structure
simplify_redlines=True, # Clean up tracked changes
)
print(msg)
# Step 2: Manipulate XML using defusedxml
doc_xml = Path("temp_unpacked/word/document.xml")
dom = minidom.parseString(doc_xml.read_text(encoding="utf-8"))
body = dom.getElementsByTagName("w:body")[0]
# Insert new paragraph at beginning
p = dom.createElement("w:p")
r = dom.createElement("w:r")
t = dom.createElement("w:t")
t.appendChild(dom.createTextNode("Automated insertion"))
r.appendChild(t)
p.appendChild(r)
body.insertBefore(p, body.firstChild)
doc_xml.write_bytes(dom.toxml(encoding="utf-8"))
# Step 3: Pack with validation
_, msg = pack(
input_directory="temp_unpacked",
output_file="contract_modified.docx",
original_file="contract.docx", # Enables author inference
validate=True,
)
print(msg)
Condensing Without Validation
If you need to repack quickly without schema validation:
python -m skills.docx.scripts.office.pack \
unpacked_dir/ output.docx \
--validate false
Key Files and Components
| Path (relative to repo) | Role |
|---|---|
skills/docx/scripts/office/unpack.py |
Extracts DOCX archives, pretty-prints XML, merges runs, simplifies redlines, escapes smart quotes |
skills/docx/scripts/office/pack.py |
Re-compresses directories into DOCX, condenses XML, runs optional validation and auto-repair |
skills/docx/scripts/office/helpers/merge_runs.py |
Implements run-merging logic to combine adjacent <w:r> elements with identical formatting |
skills/docx/scripts/office/helpers/simplify_redlines.py |
Handles tracked-change simplification and author inference for <w:ins> and <w:del> elements |
skills/docx/scripts/office/validators/ |
Contains schema validators and repair logic for Open XML compliance checking |
Summary
- The unpack/pack workflow treats DOCX files as ZIP archives containing Open XML files, enabling direct manipulation of
word/document.xmland other components. unpack.pyextracts and normalizes XML through pretty-printing, run merging, redline simplification, and smart quote escaping.pack.pyrebuilds DOCX files by condensing XML (removing whitespace and empty elements) and optionally validating against Open XML schemas.- Helper modules in
skills/docx/scripts/office/helpers/provide specialized logic for merging adjacent runs and simplifying tracked changes while maintaining author attribution. - Validation and repair occur through the validators in
skills/docx/scripts/office/validators/, ensuring output documents remain compatible with Microsoft Word and other processors.
Frequently Asked Questions
What is the unpack/pack workflow for DOCX files?
The unpack/pack workflow is a method for treating DOCX files as ZIP archives containing XML documents. It involves extracting the archive contents to access and edit Open XML files (like word/document.xml) directly, then repackaging the modified files back into a valid DOCX format. This approach enables programmatic manipulation of Word documents without requiring the Microsoft Word application or complex binary parsing libraries.
How does the unpack.py script clean up XML content?
The unpack.py script performs several normalization steps when extracting DOCX files: it pretty-prints all *.xml and *.rels files for readability, merges adjacent <w:r> (run) elements that share identical formatting to reduce editor noise, simplifies tracked changes by combining adjacent <w:ins> or <w:del> elements from the same author, and escapes smart quotes to XML entities to prevent parsing errors.
What validation does the pack.py script perform when rebuilding DOCX files?
The pack.py script optionally validates the rebuilt DOCX file against Open XML schemas using validators located in skills/docx/scripts/office/validators/. Before validation, it condenses the XML by removing whitespace-only text nodes, XML comments, and empty elements to optimize file size. If you provide the --original parameter, the script can also infer the author of new tracked changes by comparing against the source document's revision history.
Can I use the unpack/pack workflow programmatically in Python?
Yes, both unpack.py and pack.py expose Python functions that you can import and call directly in your scripts. Import unpack from skills.docx.scripts.office.unpack and pack from skills.docx.scripts.office.pack, then pass the appropriate file paths and boolean flags (such as merge_runs, simplify_redlines, and validate) to control the extraction and reconstruction process. This allows you to integrate DOCX manipulation into automated pipelines without shelling out to command-line tools.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →