AGENT SKILL

docx-authoring

Creating Word documents programmatically with python-docx — headings, styles, tables, lists, images, and sections. Use when generating DOCX reports, proposals, contracts, letters, or any structured Word document from data or templates.

使用场景文档处理
License 许可
MIT
最近更新
2026-11-09
Stars
★ 0
Forks
0

01 前言 Front Matter

name
docx-authoring
metadata
{ "processkit": { "id": "SKILL-docx-authoring", "layer": 2, "created": 1775606400000, "version": "1.0.0", "category": "documents", "apiVersion": "processkit.projectious.work/v1" } }
description
Creating Word documents programmatically with python-docx — headings, styles, tables, lists, images, and sections. Use when generating DOCX reports, proposals, contracts, letters, or any structured Word document from data or templates.

03 SKILL.md 正文

DOCX Authoring

Intro

Use python-docx to generate Word documents programmatically. The library manipulates the underlying Office Open XML directly through a Python API. Structure documents with paragraph styles, not ad-hoc formatting — styles make documents consistent and editable in Word after generation.

Overview

Setup

# /// script
# dependencies = ["python-docx"]
# ///
from docx import Document
from docx.shared import Inches, Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH

doc = Document()           # blank document
# doc = Document("template.docx")  # or start from a template

Install with uv add python-docx or pip install python-docx.

Headings and paragraphs

doc.add_heading("Document Title", level=0)   # Title style
doc.add_heading("Section 1", level=1)         # Heading 1
doc.add_heading("Subsection", level=2)        # Heading 2

p = doc.add_paragraph("Body text here.")
p = doc.add_paragraph("Intro sentence. ", style="Body Text")
run = p.add_run("Bold phrase.")
run.bold = True
run.font.size = Pt(12)

Level 0 maps to the built-in "Title" style; levels 1-9 map to "Heading 1" through "Heading 9". Always use heading levels for structural hierarchy — never bold a paragraph to fake a heading.

Paragraph styles

Prefer named styles over inline formatting. Built-in style names (case-sensitive): "Normal", "Body Text", "List Bullet", "List Number", "Quote", "Intense Quote", "Caption".

p = doc.add_paragraph("Quoted passage.", style="Quote")
p = doc.add_paragraph("First bullet point.", style="List Bullet")
doc.add_paragraph("Second bullet point.", style="List Bullet")

To define a custom style, either embed it in a template DOCX or create it programmatically via doc.styles.add_style().

Tables

table = doc.add_table(rows=1, cols=3, style="Table Grid")
hdr = table.rows[0].cells
hdr[0].text = "Name"
hdr[1].text = "Value"
hdr[2].text = "Unit"

row = table.add_row().cells
row[0].text = "Latency p99"
row[1].text = "42"
row[2].text = "ms"

# Merge cells
table.rows[0].cells[0].merge(table.rows[0].cells[1])

Use table styles ("Table Grid", "Light Shading", "Medium List 1") for consistent visual treatment. Access cell paragraph properties via cell.paragraphs[0].paragraph_format.

Images and sections

doc.add_picture("chart.png", width=Inches(5.0))

# Add a page break
doc.add_page_break()

# Section properties (portrait → landscape)
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
section = doc.sections[-1]
section.orientation = WD_ORIENT.LANDSCAPE
section.page_width, section.page_height = section.page_height, section.page_width

Headers, footers, and metadata

section = doc.sections[0]
header = section.header
header.paragraphs[0].text = "Company Confidential"

footer = section.footer
p = footer.paragraphs[0]
p.text = "Page "
p.add_run().add_field("PAGE")   # auto-numbered page field via XML injection

doc.core_properties.author = "Generated by Agent"
doc.core_properties.title = "Quarterly Report"

Saving

doc.save("output.docx")

Generate to a BytesIO buffer to avoid writing to disk:

from io import BytesIO
buf = BytesIO()
doc.save(buf)
buf.seek(0)

Document structure checklist

Before saving, verify:

  1. Title and all heading levels are set via styles, not bold/font-size
  2. Tables have a named style (never style=None)
  3. Images have explicit widths (Word's default sizing is unreliable)
  4. core_properties.title and core_properties.author are set
  5. The file opens correctly in Word and LibreOffice

Gotchas

Agent-specific failure modes — provider-neutral pause-and-self-check items:

  • Applying bold/font-size directly instead of using paragraph styles. A paragraph made bold with run.bold = True looks like a heading but is not — it breaks the document outline, navigation pane, and PDF export. Use doc.add_heading(text, level=N) or a named paragraph style for every structural element.
  • Starting from a blank Document() when the output must match a corporate template. A blank document uses Word's default styles, which rarely match a client's brand. When the target is a templated document (letterhead, report cover), always open the template with Document("template.docx") and add content into it.
  • Not specifying image width. doc.add_picture("chart.png") without width=Inches(...) inserts at the image's native resolution, which varies wildly. Always supply an explicit width or height so the image fits predictably within the page margins.
  • Generating tables with style=None. Unstyled tables render with no borders and no shading, making them hard to read. Always pass a named table style — at minimum "Table Grid" for a simple bordered table.
  • Changing page orientation without swapping width and height. Setting section.orientation = WD_ORIENT.LANDSCAPE alone does not resize the page — it just marks the orientation flag. You must also swap section.page_width and section.page_height for the layout to render correctly.
  • Saving to the same path the document was opened from without a copy. doc.save("template.docx") overwrites the template used as the base, destroying it for the next run. Always save to a distinct output path, or load the template from a path separate from the output.
  • Not verifying the output opens in Word and LibreOffice. python-docx can produce XML that is technically valid but triggers repair prompts or rendering differences in specific Word versions. Always open the generated file in at least one consumer before shipping.

Full reference

Page margins and layout

from docx.shared import Inches
section = doc.sections[0]
section.top_margin    = Inches(1)
section.bottom_margin = Inches(1)
section.left_margin   = Inches(1.25)
section.right_margin  = Inches(1.25)

Cell formatting

from docx.oxml.ns import qn
from docx.oxml import OxmlElement

def set_cell_background(cell, hex_color):
    tc = cell._tc
    tcPr = tc.get_or_add_tcPr()
    shd = OxmlElement("w:shd")
    shd.set(qn("w:fill"), hex_color)
    shd.set(qn("w:val"), "clear")
    tcPr.append(shd)

set_cell_background(table.rows[0].cells[0], "4472C4")

Numbered lists with custom start

from docx.oxml.ns import qn
from docx.oxml import OxmlElement

p = doc.add_paragraph("Step one.", style="List Number")
# To restart numbering or set start: manipulate numPr XML directly

Hyperlinks

python-docx does not have a native hyperlink API. Inject via XML:

from docx.opc.constants import RELATIONSHIP_TYPE as RT

def add_hyperlink(paragraph, text, url):
    part = paragraph.part
    r_id = part.relate_to(url, RT.HYPERLINK, is_external=True)
    hyperlink = OxmlElement("w:hyperlink")
    hyperlink.set(qn("r:id"), r_id)
    run = OxmlElement("w:r")
    rPr = OxmlElement("w:rPr")
    rStyle = OxmlElement("w:rStyle")
    rStyle.set(qn("w:val"), "Hyperlink")
    rPr.append(rStyle)
    run.append(rPr)
    t = OxmlElement("w:t")
    t.text = text
    run.append(t)
    hyperlink.append(run)
    paragraph._p.append(hyperlink)
    return hyperlink

Conversion to PDF

python-docx cannot produce PDF directly. Options:

  • libreoffice --headless --convert-to pdf output.docx (requires LibreOffice)
  • docx2pdf library (requires Word on macOS/Windows)
  • Upload to Google Docs API and export as PDF

04 同仓库更多 Skills