Advanced: a power-features session¶
The Tutorial walked the four invariants on one document. This session picks up where it left off and drives the power features — the ones that turn wordlive from "polite scripted edits" into a real document workbench: reading a large document into a fixed token budget, pinning content so positional ids survive your edits, embedding and formatting a chart, seeing the rendered page, and enforcing a house style.
Like the tutorial it's a single continuous session with dual Python / CLI
tabs, and every step is one Ctrl-Z. It assumes you've read Concepts
and are comfortable with anchors and doc.edit().
Before you start¶
Use the same sample the tutorial drives —
quarterly-report.docx
from examples/sample/ (or rebuild it: uv run --with python-docx python
examples/sample/build_quarterly_report.py). Open it in Word now. Two steps
need optional extras:
pip install "wordlive[snapshot]" # Step 4 — render pages to PNG (PyMuPDF)
# Step 3 (charts) needs Microsoft Excel installed — it's the one feature that
# reaches a second Office app.
Step 1 — Read a big document without burning context¶
You rarely want to pour a whole document into a model. read (Python
doc.read) is a token-budgeted digest: headings verbatim — each tagged with
its heading:N anchor — tables collapsed to one-line stubs, body text sampled to
fit the budget, everything else elided to markers that still name the para:
range. So the loop is skim cheap, then drill precisely.
import wordlive as wl
with wl.attach() as word:
doc = word.documents.active
digest = doc.read(budget=1500) # whole doc, elided to ~1500 tokens
print(digest)
# ## Introduction <!-- heading:3 -->
# Scope: integration milestone and Q2 budget. …
# ## Budget <!-- heading:10 -->
# > table:1 — 5 rows × 3 cols: Item | Q1 | Q2 …
# … (12 paragraphs elided: para:14–para:26) …
# The model picks a region from the digest; pull just that, in full.
section = doc.to_markdown(within="heading:10") # the Budget section
read keeps every anchor addressable, so any elided region drills open with
to_markdown(within=…) (or to_html when you need underline preserved).
The digest is the map; to_markdown is the street view. An agent never has to
choose between "too little context" and "the whole document" — see
Cookbook §20.
Step 2 — Pin what you're about to move¶
heading:N and para:N are positional — they're paragraph indices, so an
insert earlier in the document renumbers everything after it. That's fine for a
single edit, but a multi-step session (or an agent making several passes) keeps
re-reading outline to stay in sync. A pin fixes that: it plants a durable
handle — a wordlive-managed hidden bookmark — that Word keeps attached to the same
content across inserts, deletes, and edits.
with wl.attach() as word:
doc = word.documents.active
with doc.edit("Pin the budget block"):
handle = doc.pin("heading:10", name="budget") # → pin:budget
pid = handle["anchor_id"] # "pin:budget"
# Now insert *above* it — the positional id would shift, the pin doesn't.
with doc.edit("Prepend an exec summary"):
doc.prepend_paragraph("Executive summary.", style="Body Text")
# pin:budget still points at the Budget heading, though it's now heading:11.
print(doc.anchor_by_id(pid).text) # "Budget"
Omit name= for a random pin:<code>; reuse a slug to move the handle. Pin
every heading at once with doc.pin_outline() (or doc.outline(pin=True),
which returns the outline and pins as a side effect). A pin vanishes if its
content is deleted — resolving it then raises AnchorNotFoundError.
Rule of thumb: positional ids for a single read-decide-write; pins for a
multi-step session or an agent that edits then edits again. In an exec batch,
add bind: "slug" to a creating op to pin its new content without a second call —
see MCP → Durable handles in a batch.
Step 3 — Insert a chart and dress it up¶
insert_chart embeds an Excel-backed chart at any anchor, then breaks the data
link so the chart ships static — no live workbook travels with the document.
The formatting verbs (add_trendline, set_axis, add_error_bars, format)
operate on that static chart and need no Excel. Charts are the one feature that
reaches a second Office app, so Excel must be installed: insert_chart raises
ExcelNotAvailableError (CLI exit 6) up front, document untouched.
with wl.attach() as word:
doc = word.documents.active
with doc.edit("Add revenue chart"):
chart = doc.heading("Budget").insert_chart(
"line",
{"Q1": 42, "Q2": 55, "Q3": 61, "Q4": 78}, # {label: value}
title="Quarterly revenue",
)
chart.add_trendline(kind="linear", display_equation=True)
chart.set_axis("y", title="$000s", minimum=0)
insert_chart returns a ChartAnchor (chart:N) and the formatting verbs
chain. kind is "bar", "pie", "line", or "scatter"; scatter takes
[x, y] pairs instead of a mapping. Discover existing charts with
doc.charts; reads report metadata only (the series data isn't read back).
Full depth (error bars, per-point colours, log axes, re-typing in place) is in Cookbook §16.
Step 4 — See what you just did¶
Everything so far was structured I/O. But layout — did the chart land where you
meant, is that heading stranding at a page foot? — is visual. snapshot exports
a pixel-faithful PDF of the live document and rasterises the pages you ask for, so
a vision model (or you) can look at the real page, real fonts and geometry
included.
with wl.attach() as word:
doc = word.documents.active
# The page(s) the Budget section — and its new chart — occupy.
pages = doc.snapshot(pages=None, max_dim=1000) # every page, long edge ≤1000px
pages[0].png # bytes → hand to a vision model
# Or just the section you're iterating on, with revision marks visible:
shot = doc.snapshot(out="budget.png", markup="all")
max_dim caps each page's long edge — the lever for a cheap whole-document
layout check (a vision model bills on pixel area, so the cap fixes a per-page
token budget; ~1000 stays legible for "did my styling land?"). markup="all"
renders tracked changes and comments as visible marks without touching the
user's on-screen view. Read-only — the cursor never moves. Needs the
snapshot extra.
Without --out, the CLI returns base64 PNG data inline; over MCP the
word_snapshot tool returns native image content the model sees directly. This
is the feedback loop for formatting work: edit → snapshot → look → adjust.
Step 5 — Enforce a house style¶
The linting guide covers the defaults; here's the advanced move —
a house-style profile that turns on policy rules and pins their targets, then
regularizes the whole document to them in one undo. A profile is an inline dict or
a committed wordlive.lint.json.
with wl.attach() as word:
doc = word.documents.active
profile = {
"extends": "default",
"rules": {
"body-justified": {"enabled": True},
"body-line-spacing": {"enabled": True, "target": "1.5"},
"table-numeric-right-align": {"enabled": True, "threshold": 0.8},
},
}
plan = doc.regularize(profile=profile, dry_run=True) # preview
report = doc.regularize(profile=profile) # apply, one undo
print(len(report["applied"]), "fixes")
doc.regularize(profile=profile)["applied"] # [] — idempotent
The whole pass is one Ctrl-Z and idempotent — safe to wire into a pre-send check.
Content-changing fixes (a page-number field, a footer notice) stay withheld unless
you pass allow_content=True / --allow-content. Full file format and the 45-rule
catalog live in the Linting guide.
What you just learned¶
| Step | Power feature | Reach for it when |
|---|---|---|
| 1 | read digest → to_markdown drill |
A document is too big to feed whole |
| 2 | pin / pin_outline |
A multi-step session churns positional ids |
| 3 | insert_chart + formatting verbs |
You need a static, self-contained chart |
| 4 | snapshot (max_dim, markup) |
Layout is visual — let a model see it |
| 5 | regularize(profile=…) |
A house style must be enforced, idempotently |
Where to next¶
- Agent patterns — how to compose these into a robust LLM loop (skim/drill, pin, verify, self-correct, look).
- Cookbook — the same features as random-access recipes, plus floating shapes, watermarks, custom list templates, and cross-references.
- Linting & regularizing — the full rule catalog and profile format.
- Python API · CLI — every signature and flag.