Python API¶
Every entry on this page is generated from the docstrings in the
wordlive
package, so it stays in sync with the code. If something looks thin, the fix
is in the source docstring, not here.
The public surface is small on purpose. Three rough layers:
- Connect β
attach/connectreturn aWordhandle. - Address β
DocumentexposesBookmark,ContentControl, andHeadinganchors, plusanchor_by_idfor unified addressing. - Mutate β wrap writes in
Document.edit()βEditScopefor atomic undo and Selection preservation.
The package version is available as wordlive.__version__ (resolved from the
installed package metadata).
See Concepts for the why behind these shapes.
Connecting & documents¶
Get a Word handle and reach the open documents.
Connecting to Word¶
wordlive.attach ¶
Attach to an already-running Word instance.
Raises WordNotRunningError if no instance is available. Does not launch
Word and does not close it on exit.
Source code in src/wordlive/_app.py
wordlive.connect ¶
Attach to a running Word, or launch a new one if missing.
With launch_if_missing=False this behaves like attach(). Wordlive never
closes Word on exit β even when it launched the instance itself, the user
is expected to own its lifecycle.
Source code in src/wordlive/_app.py
wordlive.Word ¶
Documents¶
wordlive.Document ¶
Wraps a Word Document COM object.
Source code in src/wordlive/_document/_core.py
saved
property
¶
Whether the document has no unsaved changes (Word's Document.Saved).
True right after a save; False once an edit dirties it. A brand-new,
never-saved document reads False until its first save. This is the same
flag wordlive status reports per open document.
sources
property
¶
The document's bibliography sources β add and look up by tag.
See SourceCollection: doc.sources.add(...)
registers a source, doc.sources["Smith2020"] looks one up, and the
collection is iterable and in-testable by tag. Cite a source with
Anchor.insert_citation and list the
cited ones with Document.add_bibliography.
theme
property
¶
The document's theme β the document-wide brand primitive.
See DocumentTheme: doc.theme.apply("Facet")
swaps the whole theme, doc.theme.set_colors(accent1="#1A73E8") /
doc.theme.set_fonts(major="Arial") set brand colours/fonts, and
doc.theme.colors / doc.theme.to_dict() read it back. Wrap mutations in
doc.edit(...) for atomic undo.
tables
property
¶
Iterable, indexable view over the document's tables.
Index by 1-based position (doc.tables[1]) or Title
(doc.tables["Budget"]). Cells are anchors: doc.tables[1].cell(2, 3)
β or doc.anchor_by_id("table:1:2:3") β returns a Cell that works
with set_text, apply_style, and format_paragraph.
headings
property
¶
Iterable view over the document's headings.
Symmetric with bookmarks, content_controls, and styles. Index by
visible text (doc.headings["Risks"]) or 1-based paragraph position
(doc.headings[3]). Document.heading(name) remains as sugar for
self.headings[name].
paragraphs
property
¶
Indexable, iterable view over every paragraph (not just headings).
Index by 1-based position (doc.paragraphs[2]) to get a Paragraph
anchor (para:N) that works with set_text, apply_style,
format_paragraph, and the list verbs. doc.paragraphs.list() emits
offsets, so a body paragraph can be turned into a range:START-END
target for a mid-paragraph insertion. para:N shares its index space
with heading:N.
lists
property
¶
Read-only, iterable view over the document's bullet / numbered lists.
Index a list by 1-based position (doc.lists[2]) to get a
RangeAnchor over its range, so every list verb
(apply_list, restart_numbering, β¦) is available on it. List
formatting itself is applied through any anchor's apply_list(...).
images
property
¶
Read-only, iterable view over the document's embedded images (doc.images).
Index an image by 1-based position (doc.images[2]) to get an
ImageAnchor (image:N), then read_image()
for its raw bytes + MIME type β the path for handing an embedded picture
to a vision model. list() summarises each image (MIME, size, alt text,
and the para:N it's anchored in). The write mirror is any anchor's
insert_image.
equations
property
¶
Read-only, iterable view over the document's equations (doc.equations).
Index an equation by 1-based position (doc.equations[2]) to get an
EquationAnchor (equation:N), then mathml
/ linear to read it. list() summarises each equation (type, a linear
preview, and the para:N it sits in). The write mirror is any anchor's
insert_equation.
charts
property
¶
Read-only, iterable view over the document's charts (doc.charts).
Index a chart by 1-based position (doc.charts[2]) to get a
ChartAnchor (chart:N); chart_type / title
read its metadata. list() summarises each chart (kind, title, and the
para:N it sits in). Charts are inserted with their data link broken
(static data), so reading the series back is deferred β this view is
metadata only. The write mirror is any anchor's
insert_chart.
shapes
property
¶
Iterable view over the document's floating shapes (doc.shapes).
Index a shape by 1-based position (doc.shapes[2]) to get a
ShapeAnchor (shape:N) β a text box, a floating
image, or WordArt β then restyle it in place (set_wrap / set_position
/ set_size / format / replace_image). list() summarises each shape
(kind, size, wrap, the para:N it's anchored in). Header-story watermarks
are excluded; positions follow document order and renumber as shapes come
and go. The write mirror is any anchor's
insert_text_box / a floating
insert_image.
text_boxes
property
¶
The text boxes among doc.shapes β the shape_type == "text_box" subset.
A discovery filter, not a second id space: doc.text_boxes[1] returns a
ShapeAnchor that keeps its canonical shape:N
id (its position among all floating shapes). Created by any anchor's
insert_text_box.
sections
property
¶
Indexable view over the document's sections, headers, and footers.
doc.sections[1].header() / .footer() return HeaderFooter anchors
(addressed header:S:WHICH / footer:S:WHICH) that work with
set_text / apply_style like any other anchor.
comments
property
¶
Iterable, indexable view over the document's review comments.
doc.comments.add(anchor, text, author=...) attaches a comment to any
anchor's range without changing the text β the polite, side-channel way
to flag something. Index existing comments by 1-based position
(doc.comments[2]) to resolve() or delete() them.
revisions
property
¶
Iterable view over the document's tracked changes (doc.revisions).
When Track Changes is on, every edit is a Revision the user can accept
or reject. doc.revisions.list() reports each as
{index, type, author, text, anchor_id, start, end, date} β the
structured way to see what tracked edits a batch recorded (the visual
way is snapshot(markup="all")). Index by
1-based position (doc.revisions[2]); type is "insert" / "delete"
/ "format" / β¦ .
Resolve them too: doc.revisions[2].accept() / .reject() for one, or
accept_all /
reject_all
(within=anchor to scope to one section/range) for many. For a read that
separates the inserted from the deleted runs of a just-edited range, use
Anchor.text_final /
text_original /
revision_segments. Writing tracked
changes is tracked_changes().
footnotes
property
¶
Read-only, iterable view over the document's footnotes (doc.footnotes).
Index a footnote by 1-based position (doc.footnotes[2]) to get a
Footnote anchor (footnote:N) whose set_text /
delete edit the note. list() summarises each note (number, body
text, and the para:N it's anchored at). Create one with
Anchor.insert_footnote.
endnotes
property
¶
Read-only, iterable view over the document's endnotes (doc.endnotes).
The endnote mirror of footnotes; notes
are addressed endnote:N. Create one with
Anchor.insert_endnote.
hyperlinks
property
¶
Read-only, iterable view over the document's hyperlinks (doc.hyperlinks).
The read mirror of Anchor.link_to: index a
link by 1-based position (doc.hyperlinks[2]) to get a
Hyperlink, or list() to summarise each β
visible text, external address or internal sub_address bookmark,
screen tip, and the range:START-END / para:N it sits in.
fields
property
¶
Read-only, iterable view over the document's fields (doc.fields).
The read mirror of Anchor.insert_field:
index a field by 1-based position (doc.fields[2]) to get a
Field, or list() to summarise each β its kind
(the code's leading keyword, PAGE / REF / TOC / β¦), raw code,
rendered result, and the range:START-END / para:N it sits in.
Refresh stale results with update_fields.
properties
property
¶
Read/write view over the document's built-in and custom properties (metadata).
doc.properties.read() returns {"builtin": {β¦}, "custom": {β¦}} β the
Title / Author / Keywords / β¦ bag plus any custom name/value pairs.
doc.properties.set("Title", "β¦") writes a built-in property;
set(name, value, custom=True) writes (creating if needed) a custom one.
Wrap writes in doc.edit(...) for atomic undo.
variables
property
¶
Read/write view over the document's variables (doc.variables).
Document variables are invisible named string storage β the backing store
for { DOCVARIABLE name } fields. doc.variables.list() returns
{name: value}; set(name, value) / delete(name) manage them. Wrap
writes in doc.edit(...) for atomic undo.
start
property
¶
An anchor at the very start of the document β the prepend target.
The mirror of end. doc.start (anchor id
start, also anchor_by_id("start")) names the position before the
first paragraph; its insert verbs all prepend β
doc.start.insert_paragraph_after(text) adds a new first paragraph
(delegating to prepend_paragraph)
and insert_after(text) prepends inline (delegating to
prepend). The CLI reaches it too:
wordlive insert --anchor-id start --text "β¦".
end
property
¶
An anchor at the very end of the document β the append target.
doc.end (anchor id end, also anchor_by_id("end")) names the one
position no content names: past the last paragraph. Its insert verbs
all append β doc.end.insert_paragraph_after(text) adds a new final
paragraph (delegating to append_paragraph),
insert_after(text) appends inline (delegating to
append), and insert_image(...) drops a
picture at the end. Because it resolves through anchor_by_id, the CLI
reaches it too: wordlive insert --anchor-id end --text "β¦".
bibliography_style
property
writable
¶
The citation/bibliography style (e.g. "APA", "MLA", "Chicago").
Read/write. Setting it changes how every citation and the bibliography
render (refresh them with update_fields).
Word accepts a build-dependent set of identifiers; an unsupported value
raises OpError.
track_changes
property
writable
¶
Whether Word's Track Changes is currently on for this document.
range ¶
Return a RangeAnchor over the absolute offsets [start, end).
Offsets are UTF-16 code units β the coordinates Word uses and that
find() emits as range:START-END. Lazy: the offsets aren't validated
against the document until the anchor is used.
Source code in src/wordlive/_document/_core.py
anchor_by_id ¶
Resolve an anchor_id string into an Anchor.
Recognised forms
startβ the position before the first paragraph (the prepend target)endβ the position past the last paragraph (the append target)heading:Nβ Nth paragraph in the document (1-based, must be a heading)para:Nβ Nth paragraph (1-based, any paragraph; same index space asheading:N)bookmark:NAMEβ bookmark by namepin:CODEβ a durable handle minted bypin/stamp/pin_outlinecc:NAMEβ content control by Title (or Tag)footnote:Nβ Nth footnote (1-based), resolving to its note bodyendnote:Nβ Nth endnote (1-based), resolving to its note bodyimage:Nβ Nth embedded image (1-based, Word's InlineShapes order)equation:Nβ Nth equation (1-based, Word's OMaths order)chart:Nβ Nth chart (1-based, document order over chart inline shapes)shape:Nβ Nth floating shape (1-based, document order: text box / image / WordArt)textbox:Nβ Nth text box (alias onto its canonicalshape:N)table:N:R:Cβ cell at 1-based (row, column) of the Nth tablerange:START-ENDβ arbitrary character span (the formfind()emits)header:S:WHICHβ the WHICH header of section S (WHICH = primary/first/even)footer:S:WHICHβ the WHICH footer of section S
The bare table:N form is not an anchor (a whole table is a collection,
not a single range) β use doc.tables[N] instead.
Raises AnchorNotFoundError for unknown schemes or missing anchors.
Source code in src/wordlive/_document/_core.py
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 | |
edit ¶
Open an atomic-undo / Selection-preserving edit scope.
Source code in src/wordlive/_document/_core.py
go_to ¶
Move the user's Selection to the given anchor (rare β most ops preserve it).
Does NOT open an UndoRecord β cursor moves don't belong on the user's
undo stack. If you want the move to ride along with a batch of edits,
call this inside a doc.edit(...) scope and the surrounding
UndoRecord will still group everything together.
Source code in src/wordlive/_document/_core.py
save ¶
Save the document to its existing file, returning the absolute path.
Raises OpError if the document has never
been saved (it has no path yet) β call save_as
first. This is the ungated Python-API surface: it writes wherever the
document already lives. The CLI / MCP save verb additionally checks that
path against the configured save-directory whitelist before calling this.
Source code in src/wordlive/_document/_persistence.py
save_as ¶
Save the document to path, returning the absolute path written.
fmt is "docx" (the modern Open XML format). For PDF, use
export_pdf (it goes through a different
COM call and takes a page range). By default refuses to clobber an
existing file β pass overwrite=True to allow it. Ungated like
save; the CLI / MCP surface whitelists the
target first.
Source code in src/wordlive/_document/_persistence.py
export_pdf ¶
Export the document (or a page span) to a PDF at path; return the path.
from_page / to_page are 1-based and inclusive; omit both to export the
whole document, or give from_page alone to export a single page. Goes
through Document.ExportAsFixedFormat (the same engine
snapshot uses), so the PDF is a
pixel-faithful render β the recommended "hand back a deliverable" path.
Overwrites an existing file. Ungated like save.
Source code in src/wordlive/_document/_persistence.py
pin ¶
Plant a durable handle on anchor's range and return its pin: id.
The fix for fragile positional ids: pin("para:7") mints a hidden
bookmark over that paragraph's range and hands back a pin:<code> anchor
id that keeps pointing at the same content across later inserts / deletes
/ edits (Word maintains the association natively β that's the durability).
Resolve it like any anchor β doc.anchor_by_id("pin:a3f9c2") β or feed it
straight into another op. If the pinned content is later deleted the handle
correctly vanishes (resolving it raises AnchorNotFoundError).
anchor is an Anchor or an anchor id string. name
optionally gives a readable slug (budget-intro -> pin:budget-intro;
lowercase words joined by single hyphens); omit it for a random code.
Re-using a slug moves the handle to the new range (Word's Bookmarks.Add
semantics). Editing through the pin (set_text) keeps it; rewriting the
same span via a different anchor's Range.Text drops it.
Returns {"anchor_id": "pin:β¦", "pin": "pin:β¦", "target": <resolved id>}.
stamp is an alias. Wrap in doc.edit(...) for atomic undo β but do not
call it inside an already-open edit scope (custom undo records don't nest;
the exec batch already owns one). The CLI verb is
wordlive pin ANCHOR_ID [--name SLUG]; the exec op is pin.
Source code in src/wordlive/_document/_persistence.py
pin_outline ¶
Pin every heading at once and return the {heading_id: pin_id} map.
A durable navigation scaffold up front: stamp a handle on each heading so
an agent can address sections by pin: ids that survive the inserts /
deletes it is about to make, instead of re-reading outline after every
edit. Idempotent β a heading already carrying a wordlive handle reuses it,
so calling this twice returns the same map (run it once on a stable
document; the reuse keys on each heading's range start).
levels filters which headings get pinned: None (default) pins every
heading, an int n pins levels 1..n, and a (lo, hi) tuple pins
the inclusive band. Returns an ordered {"heading:3": "pin:a3f9c2", β¦}.
Wrap in doc.edit(...) for atomic undo. See
pin for the single-anchor form.
Source code in src/wordlive/_document/_persistence.py
tracked_changes ¶
Turn on Track Changes for the duration of the block, then restore it.
Every mutation made inside the scope is recorded as a tracked revision
the user can accept or reject β "make this edit visibly." The prior
TrackRevisions setting is restored on exit, so the scope stays polite
even when the user had tracking off.
Pairs with edit() for an atomic, visibly-tracked batch:
with doc.tracked_changes(), doc.edit("Suggest rewordings"):
doc.find_replace("utilise", "use", all=True)
Source code in src/wordlive/_document/_persistence.py
set_watermark ¶
set_watermark(text: str, *, font: str = 'Calibri', color: str = '#C0C0C0', layout: str = 'diagonal', semitransparent: bool = True) -> int
Stamp a text watermark (DRAFT / CONFIDENTIAL / β¦) behind every page.
Adds a WordArt shape to each section's primary header story β the same
mechanism (and shape name) as Word's Design β Watermark β Custom, so it
shows behind the body text on every page and replaces any existing text
watermark. layout is "diagonal" (default, rotated 45Β°) or
"horizontal"; color is the fill colour ("#C0C0C0" / "red");
semitransparent washes it out (50% transparency) so body text stays
readable. Returns the number of sections stamped.
Any prior watermark is cleared first, so calling it twice doesn't stack.
Remove one with remove_watermark.
Wrap in doc.edit(...) for atomic undo. Raises OpError for a bad
layout or color.
Source code in src/wordlive/_document/_persistence.py
remove_watermark ¶
Remove any text watermark added by set_watermark (or Word's ribbon).
Deletes every WordArt shape named like Word's watermark object across all
sections' header stories. Returns the number of shapes removed (0 if there
was no watermark). Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_document/_persistence.py
watermark ¶
The text watermark stamped behind the pages, or None if there is none.
The read side of set_watermark /
remove_watermark β it walks each
section's primary header story for the WordArt shape Word's watermark
feature draws (named like PowerPlusWaterMarkObjectβ¦) and reads its text.
Returns a WatermarkInfo (text + the 1-based
sections carrying it), or None when the document has no text
watermark. Pure read β selection, scroll, and Saved are untouched.
Only text watermarks (the set_watermark / Design β Watermark kind) are
reported; a picture watermark or an ordinary floating shape is not.
Source code in src/wordlive/_document/_persistence.py
group_shapes ¶
Group two or more floating shapes into a single group shape.
Each anchor_id is a shape:N (the members must all be floating shapes).
Returns the new group's ShapeAnchor (shape:N,
shape_type == "group") β move / size / delete it as one unit, or
ungroup it to get the members back. Word
requires members to allow overlap, so this enables that first. Wrap in
doc.edit(...) for atomic undo. Raises OpError for fewer than two
shapes or a non-shape anchor.
Source code in src/wordlive/_document/_structure.py
add_table ¶
add_table(rows: int, cols: int, *, style: str | None = None, data: list[list[Any]] | None = None, header: bool = False) -> Table
Append a rows Γ cols table at the end of the document and return it.
The "build a document from the bottom up" helper for tables β the
counterpart to append_paragraph.
Sugar for self.end.insert_table(...); see
Anchor.insert_table for the full
semantics of style (defaults to the built-in "Table Grid"), data
(row-major fill, validated up front), and header. To place a table
somewhere other than the end, resolve a position anchor and call
insert_table on it directly (e.g.
doc.headings["Pricing"].insert_table(3, 2, ...)). Wrap in
doc.edit(...) for atomic undo.
Source code in src/wordlive/_document/_structure.py
add_toc ¶
add_toc(*, levels: tuple[int, int] = (1, 3), use_heading_styles: bool = True, hyperlinks: bool = True) -> Any
Insert a table of contents at the very start of the document.
The "documents want their TOC at the top" helper β sugar for
self.start.insert_toc(...). See
Anchor.insert_toc for the full semantics
of levels (a (upper, lower) heading-level pair), use_heading_styles,
and hyperlinks, and for the page-number-repagination caveat. Returns
the new Toc. Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_document/_structure.py
add_index ¶
Insert a back-of-book index at the very end of the document.
The "indexes live at the back" helper β sugar for
self.end.insert_index(...). See
Anchor.insert_index for the full
semantics of columns, run_in, and right_align_page_numbers, and for
the page-number-repagination caveat. Mark entries first with
Anchor.mark_index_entry. Returns the
new Index. Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_document/_structure.py
add_bibliography ¶
Insert a bibliography at the very end of the document.
The "references live at the back" helper β sugar for
self.end.insert_bibliography(). See
Anchor.insert_bibliography for the
field-block / repagination caveat. Add sources with
doc.sources.add and cite them with
Anchor.insert_citation first. Returns
the new Bibliography. Wrap in doc.edit(...).
Source code in src/wordlive/_document/_structure.py
add_table_of_authorities ¶
add_table_of_authorities(*, category: str | int = 'all', passim: bool = True, keep_entry_formatting: bool = True, entry_separator: str | None = None, page_range_separator: str | None = None) -> Any
Insert a table of authorities at the very end of the document.
Sugar for self.end.insert_table_of_authorities(...). See
Anchor.insert_table_of_authorities
for the full semantics of category, passim, keep_entry_formatting,
and the separators, and for the page-number-repagination caveat. Mark
citations first with Anchor.mark_citation.
Returns the new TableOfAuthorities. Wrap in
doc.edit(...) for atomic undo.
Source code in src/wordlive/_document/_structure.py
outline ¶
Return all heading paragraphs as [{level, text, anchor_id}, ...].
With pin=True each row also carries a durable pin id and the headings
are pinned as a side effect (idempotent β see
pin_outline). This mutates the
document, so it is a Python-API-only convenience; the read surfaces
(wordlive read outline, MCP word_read outline) stay pure β pin in bulk
via pin_outline / the pin_outline exec op instead.
Source code in src/wordlive/_document/_reading.py
between ¶
Return a RangeAnchor spanning the gap between two anchors.
The "give me the block between these two headings" read. start and
end are anchor ids (e.g. "heading:1" / "heading:3") or
Anchor objects; the headline use is a pair of heading:N ids, but any
anchors work (bookmarks, paragraphs, ranges).
With inclusive=False (default) the span runs from the end of
start's range to the start of end's range β the content strictly
between them, excluding both bounding paragraphs (so two headings yield
just the body in between). With inclusive=True it runs from the
start of start to the end of end, covering both bounding paragraphs.
Read .text on the result for the spanned text, or feed its
range:START-END id into any range-taking op. A pure read (the returned
offsets are live β use them before further edits shift the document).
Raises OpError if end begins before start.
Source code in src/wordlive/_document/_reading.py
nearest_heading ¶
The heading nearest to a position, scanning before or after it.
where is an anchor id ("para:12"), an Anchor, or a raw character
offset (int). direction is "before" (the nearest heading at or
above the position β i.e. the section the position sits in) or
"after" (the next heading past it). Returns an outline()-shaped
row {level, text, anchor_id} (anchor_id is heading:N), or
None if there is no heading in that direction. A pure read.
Source code in src/wordlive/_document/_reading.py
find_paragraphs ¶
Fuzzy-rank paragraphs by similarity to text (typo/paraphrase tolerant).
Unlike find() (exact substring on normalized text), this scores
every paragraph against text with difflib.SequenceMatcher over
the same normalized form (NFKC, smart quotes, dashes, whitespace) β so
an approximately-remembered paragraph still locates its para:N.
Returns up to limit rows, sorted by descending score, keeping only
those with score >= min_score:
[{anchor_id, index, score, text, level, is_heading}, ...]. An empty
or whitespace-only query returns []. A pure read.
Source code in src/wordlive/_document/_reading.py
stats ¶
A one-call summary of the document β the "what am I looking at" read.
Returns {pages, words, characters, paragraphs, lines, sections,
headings, tables, images, equations, charts, comments, revisions, saved}. The five text
counts come from Word's own ComputeStatistics; the structural counts
come from wordlive's discovery collections (so they agree with
doc.tables / doc.images / outline etc.); saved is doc.saved.
pages/lines are print-layout truth, so the document is
repaginated first (content-neutral β selection, scroll, and view are
untouched), the same guarantee a snapshot gives. A pure read; nothing
is mutated β Repaginate flips Word's dirty bit, so the document's
Saved state is snapshotted and restored around it.
Source code in src/wordlive/_document/_reading.py
to_markdown ¶
Serialise the document (or one anchor's range) to clean Markdown.
The read mirror of insert_markdown:
headings (#β######), bullet / numbered lists (with nesting),
**bold** / *italic* / ***both***, GFM pipe tables, inline
images as , and hyperlinks as [text](url). The
constrained subset import speaks round-trips; the rest is a richer read
(export is lossy by design β underline, colours, and merged table
cells do not survive).
within scopes the output to an anchor's literal range β pass a
range:START-END (e.g. from find) or any anchor id / Anchor. A
heading:N covers only the heading line, not its section body β use
doc.between(...) or a range for "the section under X". None (the
default) serialises the whole document. A pure read; nothing is mutated.
Source code in src/wordlive/_document/_reading.py
to_html ¶
Serialise the document (or one anchor's range) to an HTML fragment.
The HTML counterpart of to_markdown,
rendered from the same document walk so the two agree on structure:
headings (<h1>β<h6>), <ul>/<ol> lists (nested),
<strong>/<em>/<u>, <table>, <img>, and <a>. Unlike
the Markdown dialect, HTML keeps underline. Returns a fragment (no
<html>/<body> wrapper). within scopes to an anchor's literal
range (see to_markdown); None is the whole document. A pure read.
Source code in src/wordlive/_document/_reading.py
read ¶
A token-budgeted, structure-aware digest of the whole document.
Loads a large document into context cheaply while keeping every anchor
addressable: headings are emitted verbatim (each tagged with its
<!-- heading:N --> anchor β the navigation spine), tables become one-line
shape stubs (> table:N β R rows Γ C cols: β¦), and body text is sampled to
fit budget (an approximate token count, ~4 chars/token), weighted so
shallower sections keep more than deep ones. Overflow is elided to markers
that still name the para: range and word count, so an agent can drill in
with to_markdown(within=β¦). depth caps
how deep a section keeps any body (deeper sections collapse to a marker).
Returns annotated Markdown. A pure read; the eliding heuristic's knobs live
in _export for tuning. For the full text of any region, use to_markdown.
Source code in src/wordlive/_document/_reading.py
proofing ¶
Run Word's proofing tools and report spelling, grammar, and readability.
Returns {spelling, grammar, readability}. spelling and grammar are
each {count, errors} β the exact error count plus a (capped) list of
{text, anchor_id, para} for the flagged runs, so a range:START-END
can be fed back into read or comments.add. readability is Word's
readability statistics (Flesch Reading Ease, Flesch-Kincaid Grade Level,
passive-sentence %, word/sentence averages), snake_cased.
Heavier than stats: it asks Word to (re)check
the document. Still a pure read β nothing is mutated. If proofing is
disabled or the document is protected, the affected section reports a
None count / empty readability rather than failing.
Source code in src/wordlive/_document/_reading.py
lint ¶
lint(*, rules: Any = None, within: str | Anchor | None = None, profile: Any = None) -> list[dict[str, Any]]
Audit the document for publishing-quality defects β a pure read.
Returns a severity-ranked list of findings, each
{rule, kind, severity, anchor_id, message, fixable, fix, observed,
expected}. kind is consistency (a direct override fighting the
applied style β a Heading 1 at 15pt), structural (an objective layout
defect β a heading that may dangle at a page foot, a multi-page table with
no repeating header, a numbered list Word split into independent "1."
runs), or policy (a house-style target β off unless a profile enables
it). A fixable finding carries an op-shaped fix describing exactly what
regularize would change.
rules selects which rules run: None is the default set (all
consistency + structural); a list of rule ids / tags
(["headings", "lists"]) includes only those; {"exclude": [...]} runs
the default set minus the listed ids/tags. within=anchor scopes the
audit to an anchor's range (a heading's section, a range:, a table).
profile is a house-style config (a path to a wordlive.lint.json file,
an inline dict, or None) that opts policy rules in, supplies their
targets (body-line-spacing's spacing, table-numeric-right-align's
threshold), and can override a rule's severity or disable a default rule β
spec-linter.md Β§6.
Read-only β selection, scroll, and Saved are untouched (the layout
rules repaginate content-neutrally, like stats).
Source code in src/wordlive/_document/_reading.py
regularize ¶
regularize(*, rules: Any = None, within: str | Anchor | None = None, profile: Any = None, dry_run: bool = False, allow_content: bool = False) -> dict[str, Any]
Apply the fixable lint findings in one
atomic-undo step. Returns {applied, skipped, deferred, findings}.
Each fixable finding's fix op(s) run through the batch op loop inside a
single doc.edit("Regularize formatting"), so one Ctrl-Z reverts the
whole pass and the user's selection/scroll are preserved. The default
fixes are targeted and idempotent β they bring a drifted direct
override back to its style's value, so running regularize twice applies
nothing the second time. rules / within / profile are as for lint
(a profile also lets its policy fixes β justify, line-spacing,
numeric-column alignment β participate). dry_run=True plans the fixes
(returning them in findings) without writing.
Formatting/structure fixes apply by default; content-changing fixes are
opt-in. A fix that adds or destroys content (inserting a caption/notice,
deleting a stray paragraph, stripping a watermark) is flagged
adds_content and withheld unless allow_content=True. Withheld fixes
are listed in deferred so you can see what an opt-in would apply. If
Track Changes is on, the edits are tracked like any other for review.
Source code in src/wordlive/_document/_reading.py
checkpoint ¶
Fingerprint the document's structure right now β a pure read.
Returns an opaque, serialisable Checkpoint (call
.to_json() to store it). Later, feed it to
changes_since (checkpoint β now) or
diff (two stored checkpoints) for a structured,
content-aligned change list β the only reliable way to answer "what
changed in session" (Word emits no content-change event), and the way an
agent verifies its own edits landed without re-reading the whole document.
include sets the fingerprint depth: "text" (cheapest β a restyle is
invisible), "text+style" (default β folds the applied paragraph-style
name in, so a restyle surfaces), or "text+format" (also hashes each
paragraph's format_info, so a pure direct-formatting edit surfaces as a
reformat). within=anchor fingerprints just one section/range.
Read-only β walks paragraphs like outline,
touching no selection/scroll and leaving Saved untouched.
Source code in src/wordlive/_document/_reading.py
changes_since ¶
Diff a stored checkpoint against the document now β a pure read.
cp is a Checkpoint (or its to_json() string /
parsed dict, so a token round-tripped through a file works directly).
Returns the change list described in diff; the
checkpoint's include depth and within scope are re-derived so the two
fingerprints are comparable. An unchanged document returns [] via the
doc_hash fast-path.
Source code in src/wordlive/_document/_reading.py
diff ¶
diff(cp_a: Checkpoint | str | dict[str, Any], cp_b: Checkpoint | str | dict[str, Any]) -> list[dict[str, Any]]
Diff two stored checkpoints β a structured, content-aligned change list.
Each change is one of: replace (text edit), insert, delete,
restyle (same text, paragraph style changed), or reformat (same
text+style, direct formatting changed β only with include="text+format").
Records carry {op, anchor_id, index_before, index_after, text_before,
text_after, style_before, style_after} as applicable; inserts/replaces/
restyles carry the current para:N (anchor_id) so the caller can
act on the change immediately, while a delete references only the old
index/text (its anchor is gone).
Alignment is by paragraph content, not index (para:N renumbers under
inserts/deletes). Both checkpoints must share the same include depth.
Move detection is deferred β a cut-paste surfaces as delete+insert. A pure
read (the tokens carry the data; Word is not touched).
Source code in src/wordlive/_document/_reading.py
snapshot ¶
snapshot(out: str | Path | None = None, *, pages: int | tuple[int, int] | None = None, dpi: int = 150, max_dim: int | None = None, markup: str = 'none') -> list[Snapshot]
Render document page(s) to PNG so a vision model can see the layout.
Word exports a pixel-faithful PDF of the live document and wordlive rasterises the requested pages β a true WYSIWYG image (real fonts, spacing, page geometry), ideal for iterating on style and formatting.
pages selects what to render: None (default) renders every page,
an int a single 1-based page, and a (start, end) tuple an inclusive
span. Returns one Snapshot per page (so a single
page is a one-element list); read .png for the bytes.
If out is given the image is also written there: a single page to out
itself, multiple pages alongside it as <stem>-p<N><suffix>.
markup is "none" (default β render the final document) or "all"
(render tracked changes and comments as visible revision marks and
balloons). The marks come from the export, not a view change, so the
user's on-screen markup setting is left untouched. The structured
counterpart is revisions.
dpi controls resolution; ~150 reads well for a vision model without
bloating the image. max_dim caps each page's long edge in pixels,
only ever lowering the resolution β the lever for a cheap whole-document
layout check (a vision model is billed on pixel area, so a long-edge cap
gives a predictable per-page token budget regardless of paper size; ~1000
keeps a page legible for "did my styling land" at a fraction of the
tokens). dpi=72 is a coarser alternative. Read-only β the document and
the user's cursor are untouched. Requires the snapshot extra (PyMuPDF),
else SnapshotError.
Source code in src/wordlive/_document/_reading.py
snapshot_anchor ¶
snapshot_anchor(anchor: Anchor, out: str | Path | None = None, *, dpi: int = 150, max_dim: int | None = None, markup: str = 'none') -> list[Snapshot]
Render the page(s) an anchor sits on. Backs Anchor.snapshot.
A heading: anchor expands to its whole section (the heading plus the
body beneath it, up to the next same-or-higher heading); any other
anchor renders the page(s) its range spans. See
snapshot for out/dpi/max_dim/markup
semantics and the return shape.
Source code in src/wordlive/_document/_reading.py
find ¶
Locate every occurrence of text within scope (or the whole doc).
mode selects the matcher:
fuzzy(default): whitespace- and Unicode-normalized (NFKC, smart quotes, dashes, NBSP) β forgiving of cosmetic drift.literal: exact substring, no folding.regex:textis a Python regular expression.
Returns a list of {anchor_id, start, end, text} where offsets are
absolute document positions and text is the actual original substring
(not the normalized form).
anchor_id for each match is range:START-END, which resolves through
anchor_by_id to a RangeAnchor β so a hit can be fed straight back
into replace --anchor-id or comments.add. The offsets are live,
though, so use them before further edits shift the document.
Matches are located per segment (contiguous body text or a single table
cell) so the returned offsets stay exact even inside tables; see
_scope_segments.
Source code in src/wordlive/_document/_editing.py
find_replace ¶
find_replace(find: str, replace: str, *, scope: Anchor | None = None, all: bool = False, occurrence: int | None = None, mode: str = 'fuzzy', required: bool = True) -> list[dict[str, Any]]
Plain-text replace. See find() for matching semantics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
find
|
str
|
the text to look for. |
required |
replace
|
str
|
the replacement text. In |
required |
scope
|
Anchor | None
|
optional anchor to restrict the search to. Headings expand to their body section. |
None
|
all
|
bool
|
replace every match. |
False
|
occurrence
|
int | None
|
1-based index β replace only the Nth match. |
None
|
mode
|
str
|
|
'fuzzy'
|
required
|
bool
|
when |
True
|
Raises:
| Type | Description |
|---|---|
AnchorNotFoundError
|
zero matches and |
AmbiguousMatchError
|
more than one match and neither |
Returns the list of replacements actually applied, each
{anchor_id, start, end, text} in their pre-replacement coordinates.
Matching is segment-aware (see _scope_segments), so a match inside a
table cell resolves to the right cell rather than drifting into its
neighbour. As a backstop, each write is verified against the located text
and raises ReplaceVerificationError rather than overwriting the wrong
span.
Source code in src/wordlive/_document/_editing.py
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | |
prepend ¶
Prepend text to the very start of the document, inline (no new paragraph).
The mirror of append: text lands before
the document's first character, joining the opening paragraph. Embed
\r / \n for your own paragraph breaks; reach for
prepend_paragraph when you want
text to become a new first paragraph. Wrap in doc.edit(...) for
atomic undo. Not idempotent β each call adds more text.
Source code in src/wordlive/_document/_editing.py
prepend_paragraph ¶
Prepend text as a new paragraph at the very start of the document.
The mirror of append_paragraph
β for a title, a banner, or a disclaimer above everything else. text
may contain \r / \n to prepend several paragraphs at once. If
style is given it must name a style defined in the document, otherwise
StyleNotFoundError is raised before any text is inserted. Wrap in
doc.edit(...) for atomic undo. Not idempotent.
Equivalent to insert_paragraph_before(text, style=style) on the
document's first paragraph.
Source code in src/wordlive/_document/_editing.py
append ¶
Append text to the very end of the document, inline (no new paragraph).
The high-level form of the old doc.com.Content.InsertAfter(...) escape
hatch: text lands immediately after the document's last character,
continuing the final paragraph. Embed \r / \n to introduce your
own paragraph breaks; reach for
append_paragraph when you want
text to become a new paragraph. Wrap in doc.edit(...) for atomic
undo. Not idempotent β each call adds more text.
Source code in src/wordlive/_document/_editing.py
append_paragraph ¶
Append text as a new paragraph at the very end of the document.
The polite, high-level "end of doc" helper β there is no named anchor
for the position past the last paragraph, so this is how you add a
closing note, drop in a generated summary, or build a document from the
bottom up. text may contain \r / \n to append several paragraphs
at once. If style is given it must name a style defined in the
document, otherwise StyleNotFoundError is raised before any text is
inserted. Wrap in doc.edit(...) for atomic undo. Not idempotent β
each call adds another paragraph.
Equivalent to calling insert_paragraph_after(text, style=style) on the
document's last paragraph, without having to locate it first.
Source code in src/wordlive/_document/_editing.py
delete_paragraph ¶
Delete the paragraph(s) at anchor β text and the trailing mark.
anchor is an anchor id (para:N, heading:N) or an Anchor; the
whole paragraph is removed, mark included, so the surrounding text closes
up with no empty line left behind (the gap set_text("") would leave).
A range anchor that spans several paragraphs removes all of them.
Word keeps a mandatory empty paragraph at the very end of the document:
deleting the last paragraph clears its content but leaves that final
mark (its range otherwise straddles the undeletable terminal mark and
raises COM 0x80020009). Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_document/_editing.py
update_fields ¶
Refresh the document's fields β recompute every { PAGE }, { REF }, etc.
Fields (page numbers, cross-references, dates, a TOC) cache their last
rendered value; after edits that change them, this recomputes the
document's main-story fields via Fields.Update(). The clean "make the
numbers right again" verb β pair it with
insert_field. A
snapshot also forces repagination, so
{ PAGE }/{ NUMPAGES } in headers and footers settle without this.
Wrap in doc.edit(...) for atomic undo.
Scope is the main text story; refreshing fields that live only in headers/footers or other stories is deferred.
Source code in src/wordlive/_document/_editing.py
wordlive.DocumentCollection ¶
Indexable view over open documents.
Source code in src/wordlive/_document/__init__.py
list ¶
[{name, path, saved, is_active}, ...] β used by wordlive status.
name is the document's window name (e.g. Report.docx, or
Document1 for one never saved) and is always non-empty so a caller
can confirm which document it is about to edit. saved is whether the
document has an on-disk location yet; path is that full path, or empty
for an unsaved document. The active document is matched by full path
(falling back to name), which is robust when several unsaved documents
share a blank path.
Source code in src/wordlive/_document/__init__.py
wordlive.WatermarkInfo
dataclass
¶
The text watermark stamped behind a document's pages (doc.watermark()).
text is the watermark's text (Word stamps the same text into every
section's header story, so this is the common value); sections lists the
1-based section indices that carry it. The read mirror of
set_watermark /
remove_watermark.
Anchors, editing & formatting¶
The anchor model, the edit scope, and the formatting / list / style verbs that run on any anchor.
Anchors¶
Every anchor type inherits apply_style(name), format_paragraph(...),
format_run(...), set_shading(...), set_borders(...), add_tab_stop(...),
insert_paragraph_before/after(...), insert_block(...), insert_image(...),
insert_text_box(...), insert_table(...),
insert_break(...), insert_field(...), insert_footnote(...),
insert_endnote(...), insert_toc(...), insert_table_of_figures(...),
mark_index_entry(...), insert_index(...), insert_citation(...),
insert_bibliography(...), mark_citation(...),
insert_table_of_authorities(...), insert_content_control(...),
link_to(...),
insert_cross_reference(...), insert_caption(...), the revision-aware reads
(text_final, text_original, revision_segments() β see
Track Changes), and the list verbs (apply_list, remove_list,
list_info, restart_numbering, indent_list, outdent_list) from
Anchor, so the same calls work uniformly on bookmarks,
content controls, headings, paragraphs, table cells, header/footer ranges, and
arbitrary range anchors. insert_image accepts a file path, raw bytes, or a
base64 string and embeds the picture; wrap is required ("inline", "auto",
or a float wrap like "square"/"top-bottom"), and block=True places the
image on its own new line rather than in the anchor's text run. The read mirror
is read_image(), which returns (bytes, mime_type) for the single picture in
the anchor's range β see Images.
insert_block(items, where="after") inserts a contiguous run of styled
paragraphs in one op (each item a plain string or {text | runs, style?}, where
text carries **bold**/*italic*/`code` markdown and runs is the
structured [{text, bold?, italic?, underline?, code?, style?}] form) and returns a
RangeAnchor spanning the block β feed it straight into
apply_list to bullet the section. Two opinionated macros build on it:
insert_section(heading, body, *, level=1, where="after") places a
Heading {level} paragraph plus its body (the same items shape, or a bare
string) in one op, and insert_markdown(md, *, where="after") maps a
constrained-Markdown subset β #/##/### headings, -/* bullets, 1.
numbers, blank-line paragraphs, inline **bold**/*italic*/`code` (a
monospace run) β to real Word structure (not CommonMark: no code fences,
nested lists, or tables in v1). On a blank document these structural inserts
reuse the lone empty paragraph; append_paragraph promises a new final
paragraph and so leaves it stranded above your content.
Headings additionally have replace_section_body(body, *, markdown=False), which
clears the body under a heading (up to the next same-or-higher heading) and
inserts a replacement, keeping the heading β the "rewrite section X" workflow.
All three return the new content's RangeAnchor.
a = doc.headings["Methods"]
a.insert_section("Results", ["We saw a **20%** lift.", "Caveats apply."], level=2)
a.insert_markdown("# Plan\n\nKick-off.\n\n- scope it\n- staff it")
a.replace_section_body("Updated findings.\n\n- point one\n- point two", markdown=True)
insert_table(rows, cols, β¦)
creates a new table at the anchor and returns its Table
(append at the end with Document.add_table); pass data
as a 2-D array or as records (a list of dicts whose keys become a header row),
and rows/cols are inferred from data when omitted.
insert_break(kind="page"|"column"|"section_next"|"section_continuous") drops
an explicit break; for a reflow-safe page break tied to a paragraph (e.g. every
Heading 1), pass page_break_before=True to format_paragraph instead.
format_paragraph also takes line_spacing (the leading within a paragraph: a
multiple like 1.5, the keywords "single"/"1.5"/"double", or an exact
length such as "14pt") and the pagination controls keep_together,
keep_with_next, and widow_control (tri-state booleans) for clean multi-page
layout.
format_run(...) sets character formatting (bold/italic/underline, font,
size, color, highlight, sub/superscript, caps, spacing) β the run-level
layer, ideal with a range:START-END anchor to style a phrase.
format_info() (no args) is the read mirror of format_paragraph /
format_run: it returns {anchor_id, style, paragraph, font}, where style is
the applied paragraph style's name and paragraph / font each map a field name
to {value, style, override} β the effective value, the value the applied
style contributes (style), and override=True when the two differ (a direct
override). font also carries a mixed key listing the font fields that read
wdUndefined because they vary across the range's runs (their value is None,
never flagged as an override). Lengths are points (floats); color is #RRGGBB
or "auto"; alignment is left/center/right/justify; line_spacing is
single/1.5/double, "1.15" (a multiple), "14pt" (exactly), or
"at_least:14pt". The paragraph fields are alignment, left_indent,
right_indent, first_line_indent, space_before, space_after,
line_spacing, page_break_before, keep_together, keep_with_next,
widow_control; the font fields are name, size, bold, italic,
underline, strikethrough, color, subscript, superscript, small_caps,
all_caps, spacing, hidden, and highlight (a keyword β "yellow", β¦ β or
"none"; it lives on the range, not the style, so it's effective-only with
style: null). It's a pure read β diff the override flags to see what a
paragraph carries beyond its style (the input doc.regularize()
writes back). set_shading,
set_borders, and add_tab_stop add range/cell fill, borders, and tab stops;
colours accept a name, hex, or (r, g, b) and sizes/positions accept points or
a unit string ("12pt", "1in"). drop_cap(lines=3, position="dropped"|"margin"|"none", β¦)
turns the first letter of the anchor's paragraph into a real Word drop cap (the
editorial oversized initial; position="none" removes one). insert_field(kind, ...) drops a
self-updating field ("page", "numpages", "date", β¦, or "field" + a raw
code) β pair it with a footer for page numbers and refresh with
Document.update_fields(). insert_footnote(text) /
insert_endnote(text) attach a note to the anchor's range and return a
Footnote / Endnote (addressed
footnote:N / endnote:N); insert_toc(levels=(1, 3), β¦) inserts a table of
contents and returns a Toc, insert_table_of_figures(label=
"Figure", β¦) lists the captions of one label as a TableOfFigures,
and mark_index_entry(entry, β¦) + insert_index(β¦) mark and build a back-of-book
Index. insert_citation(tag, β¦) cites a registered source and
insert_bibliography(β¦) builds the works-cited block, while mark_citation(
long_citation, β¦) + insert_table_of_authorities(β¦) mark and build a
TableOfAuthorities β see
Footnotes, endnotes & TOC.
insert_content_control(kind="rich_text", β¦) wraps the anchor's range in a new
content control (see Anchoring & linking). link_to(address=β¦ |
bookmark=β¦) makes the anchor a hyperlink, insert_cross_reference(target, β¦)
references another anchor, and insert_caption(label, β¦) adds a numbered
caption β see Anchoring & linking. Every anchor also has snapshot(...), which
renders the page(s) it sits on to PNG (a heading expands to its whole section) β
see Snapshots.
location() is the non-visual companion: it returns {page, end_page, line,
column, in_table} β where the anchor sits in the laid-out document (its page
span, and its first character's line/column) β so an agent can answer "what page
is this on" without a snapshot. It repaginates first (content-neutral; selection
and view untouched), so page numbers are print-layout truth.
wordlive.Anchor ¶
Abstract base β subclasses know how to materialise their COM Range.
Concrete subclasses must implement _range() and set_text(). Other
operations (text, insert_before, insert_after, delete,
apply_style, format_paragraph) are derived and inherited as-is.
Source code in src/wordlive/_anchors/_anchor_core.py
anchor_id
abstractmethod
property
¶
Stable string identifier for this anchor (e.g. bookmark:Address).
Each anchor kind has its own scheme (bookmark:, cc:, heading:),
so subclasses must declare theirs explicitly β no useful default
exists at this level.
text_final
property
¶
The anchor's text as if every tracked change in it were accepted.
Inserted runs stay, deleted runs drop β the after-the-edits view. Equal to
text when nothing tracked touches the range. The mirror is
text_original; the per-segment breakdown
is revision_segments.
text_original
property
¶
The anchor's text as if every tracked change in it were rejected.
Deleted runs stay, inserted runs drop β the before-the-edits view. The
mirror of text_final.
set_text
abstractmethod
¶
revision_segments ¶
The anchor's text split into tracked-change segments (revision-aware read).
Returns [{text, change}, β¦] in document order, where change is
"insert", "delete", or None (unchanged). Word's text read
shows the final view (inserted runs present, deleted runs gone); this
also surfaces the deleted runs, so you can see both sides of a tracked
edit. text_final and
text_original are the two flattened
views. The structured, whole-document counterpart is doc.revisions.
Source code in src/wordlive/_anchors/_anchor_read.py
snapshot ¶
snapshot(out: str | Path | None = None, *, dpi: int = 150, max_dim: int | None = None) -> list[Snapshot]
Render the page(s) this anchor sits on to PNG β let a model see it.
A heading expands to its whole section; any other anchor renders the
page(s) its range spans. Returns a list of
Snapshot (one per page); pass out to also write
the image(s) to disk. max_dim caps each page's long edge in pixels (for
a cheaper render). Sugar for
Document.snapshot_anchor; see it
for the full semantics. Requires the snapshot extra (PyMuPDF).
Source code in src/wordlive/_anchors/_anchor_read.py
read_image ¶
Extract the image embedded in this anchor's range as (bytes, mime_type).
The read side of the image story β pull an embedded picture's original
bytes back out (e.g. to hand to a vision model), the counterpart to
insert_image. The range must contain
exactly one picture: an image:N anchor (or any
single-image text anchor) reads cleanly, while a range with no image β or
more than one β raises ImageSourceError. bytes is the picture's raw
encoded data (PNG/JPEG/β¦); mime_type is its content type
("image/png", "image/jpeg", β¦). Discover what's there first with
doc.images. Read-only β nothing is mutated.
Source code in src/wordlive/_anchors/_anchor_read.py
location ¶
Where this anchor sits in the laid-out document β a pure read.
Returns {page, end_page, line, column, in_table}:
page/end_pageβ the 1-based pages the anchor's first and last characters fall on (equal for a collapsed/single-line anchor); the pair is the anchor's page span, so a section/table/image that straddles a page boundary reports both.pageis what answers "what page is this on"; scanparagraphsand watchpagestep up to find "which paragraph starts page 2".line/columnβ the first character's 1-based line and column in the page's text grid (Range.Information).in_tableβ whether the anchor sits inside a table.
Page/line numbers are only meaningful in print layout, so the document
is repaginated first (content-neutral β it touches neither the
user's selection, scroll, nor view), mirroring the guarantee a
snapshot gives. No politeness concern: this mutates nothing β the
document's Saved state is snapshotted and restored around the
repaginate, which would otherwise flip Word's dirty bit.
Source code in src/wordlive/_anchors/_anchor_read.py
apply_list ¶
Turn this anchor's paragraphs into a list.
list_type is "bulleted", "numbered", or "outline" (the three
ListGalleries). By default numbering starts fresh at 1; pass
continue_previous=True to continue from a list immediately above.
Raises ValueError for an unknown list_type.
Source code in src/wordlive/_anchors/_anchor_lists.py
remove_list ¶
Strip list formatting (bullets / numbers) from this anchor's paragraphs.
list_info ¶
Describe the list this anchor sits in: {type, level, number, string}.
type is "none" when there's no list formatting, otherwise one of
"bulleted", "numbered", "outline", "number-only", or "mixed".
number is the first paragraph's value, string its rendered marker.
Source code in src/wordlive/_anchors/_anchor_lists.py
apply_list_format ¶
Author a custom multi-level list template and apply it here.
The richer counterpart to apply_list (which only applies a gallery
default): levels is a 1-based list of per-level specs that defines the
marker, indentation, and marker font of each list level. Each spec is a
dict; all keys are optional except a bullet level's glyph:
kindβ"number"(default) or"bullet".formatβ for a number level, the marker template ("%1.","%1)","%1.%2";%Nreferences level N's number), default"%{level}."; for a bullet level, the glyph (or passbullet).styleβ a number level's scheme:"arabic","upper-roman","lower-roman","upper-letter","lower-letter","ordinal", β¦ .bullet/fontβ a bullet level's glyph and marker font (default"Symbol");fontalso sets a number level's marker font.start_atβ a number level's first value.number_position/text_positionβ the marker and text indents (points or a length string like"0.5in").trailingβ what follows the marker:"tab"/"space"/"none".alignmentβ the marker's alignment:"left"/"center"/"right".bold/italic/colorβ the marker font's styling.
More than one level mints an outline template (levels beyond those given
keep Word's defaults). read_list_levels() is the read mirror. Wrap in
doc.edit(...) for atomic undo; a bad spec raises OpError.
Source code in src/wordlive/_anchors/_anchor_lists.py
read_list_levels ¶
The per-level format of the list this anchor sits in β a pure read.
Returns one {level, kind, format, number_style, style, trailing,
number_position, text_position, font} dict per level of the applied
ListTemplate, or [] if the anchor carries no list (number_style is
the raw WdListNumberStyle int). The read mirror of apply_list_format.
Source code in src/wordlive/_anchors/_anchor_lists.py
restart_numbering ¶
Restart this list's numbering at 1.
Re-applies the range's current list template with "continue previous"
off. Raises ValueError if the range isn't part of a list.
Source code in src/wordlive/_anchors/_anchor_lists.py
indent_list ¶
outdent_list ¶
apply_style ¶
Apply the named paragraph or character style to this anchor's range.
Word selects paragraph- vs. character-style behaviour from the style's
own Type; we don't model that distinction. Raises StyleNotFoundError
if the style isn't defined in the document.
Source code in src/wordlive/_anchors/_anchor_format.py
format_paragraph ¶
format_paragraph(*, alignment: Any = None, left_indent: float | None = None, right_indent: float | None = None, first_line_indent: float | None = None, space_before: float | None = None, space_after: float | None = None, line_spacing: Any = None, page_break_before: bool | None = None, keep_together: bool | None = None, keep_with_next: bool | None = None, widow_control: bool | None = None) -> None
Set paragraph-formatting properties on this anchor's range.
All kwargs are optional; only the ones explicitly passed are written.
Indent and spacing values are in points (Word's native unit for
ParagraphFormat.LeftIndent etc.). alignment accepts a
WdParagraphAlignment enum, its int value, or a string
("left"/"center"/"right"/"justify").
line_spacing sets the leading between lines within the paragraph
(distinct from space_before/space_after, which space paragraphs
apart). It accepts a number β a multiple of single spacing (1
single, 1.5, 2 double) β one of the keywords "single"/"1.5"/
"double", or an exact length string ("14pt", "1.5cm") for a
fixed line height.
page_break_before=True forces the paragraph to begin on a new page β
the clean way to page-break (e.g. apply it to every Heading 1): it's
a paragraph property that survives reflow and leaves no stray break
character, unlike insert_break.
False clears the property. Indents/spacing accept a number (points) or
a unit string ("0.5in").
The remaining flags are Word's pagination controls (all tri-state β
True/False set, None leaves untouched), for clean multi-page
layout: keep_together keeps every line of the paragraph on one page;
keep_with_next keeps it on the same page as the following paragraph
(e.g. a heading with its first body line); widow_control prevents a
lone first/last line stranded at the bottom/top of a page (on by default
in Word).
Source code in src/wordlive/_anchors/_anchor_format.py
drop_cap ¶
drop_cap(lines: int = 3, *, position: str = 'dropped', distance: Any = 0.0, font: str | None = None) -> None
Turn the first letter of this anchor's paragraph into a drop cap.
The editorial oversized initial β a real Word DropCap, not a faked
big-font run, so it reflows and re-wraps the body text around it
natively. Applies to the first paragraph of the anchor's range.
position is "dropped" (the default β the letter sits into the
text, the common magazine style), "margin" (it hangs out in the left
margin), or "none" (remove an existing drop cap; lines/distance/
font are then ignored). lines is how many lines tall the letter is
(Word's default is 3). distance is the gap between the letter and the
body text, in points (or a unit string like "2pt"). font optionally
sets the dropped letter's font family.
Word rejects a drop cap on an empty paragraph (there's no letter to
drop) β that surfaces as a ComError. Wrap in doc.edit(...) for atomic
undo. Raises OpError for an unknown position or a bad distance.
Source code in src/wordlive/_anchors/_anchor_format.py
format_run ¶
format_run(*, bold: bool | None = None, italic: bool | None = None, underline: bool | None = None, strikethrough: bool | None = None, font: str | None = None, size: Any = None, color: Any = None, highlight: Any = None, subscript: bool | None = None, superscript: bool | None = None, small_caps: bool | None = None, all_caps: bool | None = None, spacing: Any = None) -> None
Set character-formatting (run-level) properties on this anchor's range.
Direct formatting β the bold this phrase layer, distinct from
apply_style (named styles) and
format_paragraph (paragraph-scope).
Pairs naturally with range:START-END to style a sub-paragraph span.
All kwargs are optional and tri-state; only the ones explicitly passed
are written (None leaves the property untouched). bold/italic/
underline/strikethrough/subscript/superscript/small_caps/
all_caps are booleans. font is a family name; size and spacing
accept a number (points) or a unit string ("12pt", "1.5mm").
color accepts a named colour, hex ("#FF0000"), or (r, g, b).
highlight is a named text-highlight colour ("yellow", "green", β¦,
or "none"/"auto" to clear it) β a palette index, not an RGB.
Bad colour/length/highlight input raises OpError (bad-input). Wrap in
doc.edit(...) for atomic undo.
Source code in src/wordlive/_anchors/_anchor_format.py
format_info ¶
The effective paragraph + character formatting on this anchor β the
read mirror of format_paragraph and
format_run. Pure read.
Returns {anchor_id, style, paragraph, font}. style is the applied
paragraph style's name. paragraph and font carry one entry per field,
each {value, style, override}:
valueβ the effective value (what's actually rendered);styleβ the value the applied style would give on its own;overrideβTruewhenvalue != style, i.e. a direct override sits on top of the style (the signal the consistency linter rules act on). A mixed field (value is None) is never flagged as an override.
font.mixed lists the character fields that read wdUndefined because
they vary across the range's runs (e.g. a heading with one bold word) β
those carry value: null rather than a bogus number. Lengths are in
points; color is #RRGGBB (or "auto"); alignment/line_spacing
use the same keywords the write verbs accept. font.hidden flags Word's
hidden-text attribute. font.highlight is a highlight keyword ("yellow",
β¦ or "none"); it lives on the range, not the style, so it's
effective-only β style is always null and override just means a
highlight is present.
The field vocabulary is identical to the write side, so a value read here
can be written straight back through format_paragraph/format_run.
Source code in src/wordlive/_anchors/_anchor_format.py
set_shading ¶
Set the background (fill) shading of this anchor's range.
fill is a named colour, hex ("#FFFF00"), or (r, g, b) β applied to
Range.Shading.BackgroundPatternColor. Because a Cell is an Anchor,
this is also how you shade a table cell. pattern (a shading pattern/
texture) is accepted for forward-compatibility but not yet applied β
deferred. Bad colour input raises OpError. Wrap in doc.edit(...).
Source code in src/wordlive/_anchors/_anchor_format.py
set_borders ¶
set_borders(*, sides: Any = 'all', style: Any = 'single', weight: Any = 0.5, color: Any = None) -> None
Draw borders on this anchor's range (or cell β a Cell is an Anchor).
sides is "all"/"box" (the default β four outer edges), a single
edge ("top"/"bottom"/"left"/"right"), an interior gridline
("horizontal"/"vertical", for multi-cell ranges), or a list of those.
style is a line style ("single", "double", "dot", "dash", β¦, or
"none" to remove). weight is the line width in points, snapped to
Word's discrete set (0.25/0.5/0.75/1/1.5/2.25/3 pt). color is an
optional border colour (name/hex/RGB).
This sets per-range / per-cell borders. Page borders
(Section.Borders) are out of scope; whole-table borders (the entire
grid in one call, including interior gridlines) go through
Table.set_borders / the table
set-borders verb. Bad input raises OpError. Wrap in doc.edit(...).
Source code in src/wordlive/_anchors/_anchor_format.py
add_tab_stop ¶
Add a tab stop to this anchor's paragraph(s).
position is the distance from the left margin in points (or a unit
string like "3in"). align is "left"/"center"/"right"/
"decimal"/"bar". leader is an optional fill drawn up to the stop β
"dots" (price lists / tables of contents), "dashes", "lines", β¦ β
defaulting to none. Maps to ParagraphFormat.TabStops.Add. Bad input
raises OpError. Wrap in doc.edit(...).
Source code in src/wordlive/_anchors/_anchor_format.py
insert_toc ¶
insert_toc(*, levels: tuple[int, int] = (1, 3), use_heading_styles: bool = True, hyperlinks: bool = True, where: str = 'after') -> Any
Insert a table of contents at this anchor and return it as a Toc.
Builds a TOC from the document's heading paragraphs over the given
levels (a (upper, lower) pair β (1, 3) covers Heading 1β3).
use_heading_styles=True sources entries from the built-in Heading
styles; hyperlinks=True makes each entry a clickable jump (and a real
hyperlink in exported PDFs). Returns a Toc.
A TOC's page numbers populate only after repagination β call
toc.update() (or Document.update_fields,
or take a snapshot, which forces print layout) before reading them.
Most documents want the TOC at the top: doc.add_toc(...) is the sugar
for doc.start.insert_toc(...).
where is "after" (default) or "before" this anchor's range.
Wrap in doc.edit(...) for atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_references.py
link_to ¶
link_to(address: str | None = None, *, bookmark: str | None = None, text: str | None = None, screen_tip: str | None = None) -> None
Turn this anchor into a hyperlink (or insert new linked text).
Pass exactly one destination: address for an external link (a URL,
mailto:, or file path) or bookmark for an internal jump to a named
bookmark in this document. With text=None the anchor's existing range
becomes the clickable link; pass text=... to insert new linked text
at the end of the anchor's range (so linking a heading or a range:
phrase with text=... adds the link rather than overwriting the content).
screen_tip is the hover tooltip.
Pair it with doc.bookmarks.add(...)
to build internal navigation, or a range:START-END id (from find) to
link an existing phrase. Wrap in doc.edit(...) for atomic undo. Bad
input (not exactly one destination) raises OpError.
Source code in src/wordlive/_anchors/_anchor_references.py
insert_cross_reference ¶
insert_cross_reference(target: str, *, kind: str = 'text', hyperlink: bool = True, where: str = 'after') -> None
Insert a cross-reference to another anchor at this anchor.
target is the anchor id to point at: bookmark:NAME, heading:N,
footnote:N, or endnote:N. kind selects what the reference shows:
"text" (the heading/bookmark text β the default), "page" ("see
page 5"), "number" (the paragraph or note number), or
"above_below" ("above"/"below"). hyperlink=True makes the inserted
reference a clickable jump.
An unresolvable target raises AnchorNotFoundError (exit 2) before
anything is inserted. where is "after" (default) or "before"
this anchor's range. Wrap in doc.edit(...) for atomic undo; other bad
input raises OpError.
Source code in src/wordlive/_anchors/_anchor_references.py
insert_caption ¶
insert_caption(label: str = 'Figure', *, text: str | None = None, position: str | None = None) -> None
Insert a numbered caption as its own paragraph at this anchor.
label is a caption label β built-in "Figure" / "Table" /
"Equation" or any custom string; Word auto-numbers per label
(Figure 1, Figure 2, β¦). text is the caption title shown after the
label and number. Pairs with
insert_cross_reference for
"see Figure 2".
position is "above" or "below" the anchor. Left as None it
follows convention: a "Table" caption goes above, every other
label goes below. The caption always becomes its own
Caption-styled paragraph β it never fuses into the target paragraph.
On a table cell (table:N:R:C) the caption is placed above / below the
whole table, not inside the cell.
Wrap in doc.edit(...) for atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_references.py
mark_index_entry ¶
mark_index_entry(entry: str, *, cross_reference: str | None = None, bold: bool = False, italic: bool = False) -> None
Mark this anchor's range as a back-of-book index entry (an XE field).
entry is the text that appears in the index; use "main:sub" to file
it as a subentry under main (Word's colon convention). bold /
italic style the entry's page number in the built index.
cross_reference replaces the page number with a "see β¦" pointer (e.g.
cross_reference="Widgets" β "see Widgets").
This is the per-term half of indexing; once entries are marked, build the
list with insert_index /
Document.add_index. The XE field is
hidden text and doesn't disturb the visible flow. Wrap in doc.edit(...)
for atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_references.py
insert_index ¶
insert_index(*, columns: int = 2, run_in: bool = False, right_align_page_numbers: bool = False, where: str = 'after') -> Any
Insert a back-of-book index at this anchor and return it as an Index.
Gathers every XE entry marked with
mark_index_entry into an
alphabetised, page-numbered list. columns is the number of newspaper
columns the index is laid out in (2 is the book default). run_in=True
packs subentries into a single paragraph instead of one per line;
right_align_page_numbers=True flushes page numbers to the right margin.
Returns an Index; like a TOC it's a field block whose
page numbers populate only after repagination β call index.update(),
Document.update_fields, or take a
snapshot. Most documents want the index at the end:
doc.add_index(...) is the sugar for doc.end.insert_index(...).
where is "after" (default) or "before" this anchor's range.
Wrap in doc.edit(...) for atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_references.py
insert_table_of_figures ¶
insert_table_of_figures(*, label: str = 'Figure', include_label: bool = True, hyperlinks: bool = True, right_align_page_numbers: bool = True, where: str = 'after') -> Any
Insert a table of figures at this anchor and return it.
The caption-driven sibling of insert_toc:
it lists every caption of one label β "Figure" (the default),
"Table", "Equation", or any custom label you passed to
insert_caption β with its page number.
include_label=True keeps the "Figure 1" prefix in each entry;
hyperlinks=True makes entries clickable jumps;
right_align_page_numbers=True flushes page numbers right.
Returns a TableOfFigures; like a TOC its page
numbers populate only after repagination β call tof.update(),
Document.update_fields, or take a
snapshot. where is "after" (default) or "before" this anchor's
range. Wrap in doc.edit(...) for atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_references.py
insert_citation ¶
insert_citation(tag: str, *, pages: str | None = None, prefix: str | None = None, suffix: str | None = None, volume: str | None = None, suppress_author: bool = False, suppress_year: bool = False, suppress_title: bool = False, locale: int = 1033, where: str = 'after') -> Any
Insert an in-text citation at this anchor and return it as a Citation.
References a source in the document's store (add one with
doc.sources.add) by its tag and
renders it in the current bibliography_style
β e.g. (Smith 2020). pages adds a page locator ((Smith 2020, 15));
prefix / suffix wrap the citation ("see " / ", at 12"); volume
adds a volume. suppress_author / suppress_year / suppress_title drop
those parts. locale is the LCID the style formats under (1033 = en-US).
Returns a Citation; a citation to an unknown tag
renders "Invalid source specified." rather than failing. where is
"after" (default) or "before" this anchor's range. Wrap in
doc.edit(...) for atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_references.py
insert_bibliography ¶
Insert a bibliography at this anchor and return it as a Bibliography.
Inserts a BIBLIOGRAPHY field β the reference list of every source
cited in the document, formatted in the current
bibliography_style. Most
documents want it at the end: doc.add_bibliography() is the sugar for
doc.end.insert_bibliography().
Returns a Bibliography; like a TOC it's a field
block β call bibliography.update(),
Document.update_fields, or take a
snapshot after adding citations. where is "after" (default) or
"before" this anchor's range. Wrap in doc.edit(...) for atomic undo.
Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_references.py
mark_citation ¶
mark_citation(long_citation: str, *, short_citation: str | None = None, category: str | int = 'cases', where: str = 'after') -> None
Mark this anchor's range as a table-of-authorities citation (a TA field).
The legal analog of mark_index_entry:
long_citation is the full citation as it appears in the table (e.g.
"Smith v. Jones, 1 U.S. 1 (2020)"), short_citation the abbreviated
form Word matches elsewhere in the text (defaults to long_citation), and
category the section it files under β "cases" (the default),
"statutes", "other", "rules", "treatises",
"regulations", "constitutional", or a category number (1-16).
This is the per-authority half; build the table with
insert_table_of_authorities
/ Document.add_table_of_authorities.
The TA field is hidden and doesn't disturb the visible flow. Wrap in
doc.edit(...) for atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_references.py
insert_table_of_authorities ¶
insert_table_of_authorities(*, category: str | int = 'all', passim: bool = True, keep_entry_formatting: bool = True, entry_separator: str | None = None, page_range_separator: str | None = None, where: str = 'after') -> Any
Insert a table of authorities at this anchor and return it.
Gathers the TA citations marked with
mark_citation into a page-numbered
table. category selects which authorities to include β "all" (the
default), "cases", "statutes", β¦ or a number (1-16). passim=True
replaces five-or-more page references for one authority with "passim";
keep_entry_formatting=True preserves each citation's character
formatting. entry_separator / page_range_separator override the
defaults between a citation and its first page / between page ranges.
Returns a TableOfAuthorities; like a TOC
it's a field block whose page numbers populate only after repagination β
call toa.update(), Document.update_fields,
or take a snapshot. doc.add_table_of_authorities(...) is the sugar for
doc.end.insert_table_of_authorities(...). where is "after"
(default) or "before" this anchor's range. Wrap in doc.edit(...) for
atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_references.py
insert_image ¶
insert_image(image: str | Path | bytes, *, wrap: str, where: str = 'after', block: bool = False, width: float | None = None, height: float | None = None, alt_text: str | None = None, lock_aspect: bool = True) -> ShapeAnchor | None
Insert an image at this anchor (atomic-undo when inside doc.edit()).
image is a file path, raw image bytes, or a base64 string β a str
is treated as a path when it names an existing file, otherwise as
base64. Word embeds the picture (SaveWithDocument=True) and
auto-detects its natural size, so width/height (points) are optional
overrides. alt_text sets the image's accessibility text.
wrap is required β there is no default β so layout intent is always
explicit:
"inline"keeps the image in the text flow (anInlineShape)."auto"floats it: Square when its width is at most half the section's usable text width, else top-and-bottom."square" | "tight" | "through" | "top-bottom" | "front" | "behind"floats it with that wrap type.
where is "after" (default) or "before" the anchor's range.
block places the image in its own new paragraph (reset to Normal)
rather than embedding it in the anchor's text run β so
heading.insert_image(..., wrap="inline", where="before", block=True)
drops the image on its own line above the heading instead of joining
the heading text. Without it, an inline image anchored at a heading lands
mid-run and the heading text trails it on the same line.
A floating image (any wrap other than "inline") leaves the inline
text flow, so image:N no longer addresses it β this returns its floating
ShapeAnchor (shape:N) for restyle
(re-wrap / reposition / resize / replace_image). An "inline" image
stays an InlineShape (addressed as image:N) and returns None.
Raises ImageSourceError for a missing/unreadable/invalid image and
ValueError for an unknown wrap or where.
Source code in src/wordlive/_anchors/_anchor_media.py
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | |
insert_text_box ¶
insert_text_box(text: str, *, width: Any = 200, height: Any = 100, wrap: str = 'square', where: str = 'after', font: str | None = None, size: Any = None, bold: bool | None = None, italic: bool | None = None, alignment: str | None = None, fill: str | None = None, border: str | bool | None = None) -> ShapeAnchor
Insert a floating text box (a pull quote / call-out) anchored here.
A Shapes.AddTextbox floating shape is anchored to this anchor's
paragraph and seeded with text. width / height are points or a unit
string ("3in" / "8cm"). wrap is how body text flows around it β
"square" (default), "tight", "through", "top-bottom",
"front", or "behind" (the same vocabulary as insert_image, minus
"inline"). where places the anchor "after" (default) or
"before" this anchor's range.
The remaining kwargs style the box and its text, each optional:
font / size (points or unit string) / bold / italic set the
character format; alignment ("left"/"center"/"right"/
"justify") the paragraph; fill is a background colour
("#eeeeff" / "navy") and border is False for no outline, a
colour string for a coloured outline, or True for the default.
Returns the text box's floating ShapeAnchor
(shape:N) so it can be restyled in place afterwards (set_text /
set_wrap / set_position / set_size / format); discover text boxes
later via doc.text_boxes. Wrap in
doc.edit(...) for atomic undo; raises ValueError for an unknown
wrap / where.
Source code in src/wordlive/_anchors/_anchor_media.py
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | |
insert_chart ¶
insert_chart(kind: str, data: Any, *, title: str | None = None, where: str = 'after') -> ChartAnchor
Insert an Excel-backed chart at this anchor and return it.
kind is one of "bar" (clustered columns), "pie", "line", or
"scatter". data is either an object mapping {label: value} (for
bar / pie / line) or an array of [x, y] pairs (for scatter β both
axes numeric, duplicate x preserved β and line). title sets the
chart title and series name; None leaves it untitled. where places
the chart "after" (default) or "before" this anchor's range.
Charts are Excel-backed: this embeds a chart whose data lives in a hidden
Excel workbook, then breaks the link so the data is static β no live
workbook ships in the document and the series data can't be read back
(deferred). Requires Excel installed: raises ExcelNotAvailableError
(CLI exit 6), checked up front so the document is untouched on a missing
Excel. Raises OpError for malformed data and ValueError for an
unknown kind / where.
Word's chart API only inserts off the live Selection, so this moves the
cursor to the insertion point; wrap in doc.edit(...) (as the CLI / exec
/ MCP surfaces do) for atomic undo and to restore the user's selection.
Source code in src/wordlive/_anchors/_anchor_media.py
insert_equation ¶
insert_equation(*, unicodemath: str | None = None, latex: str | None = None, mathml: str | None = None, where: str = 'after', display: bool = True) -> EquationAnchor
Insert a mathematical equation at this anchor and return it.
The equation is given in exactly one of three input dialects:
unicodemath=β Word's native UnicodeMath linear form, e.g."x=(-bΒ±β(b^2-4ac))/(2a)"or"a^2+b^2=c^2". Zero-dependency: the string is typed into a math zone and built up into the 2-D form by Word itself.latex=β a LaTeX math string, e.g.r"\frac{-b\pm\sqrt{b^2-4ac}}{2a}". Converted LaTeXβMathMLβOMML; the LaTeXβMathML hop needs the optionallatexextra (pip install "wordlive[latex]") and raisesEquationErrorwithout it.mathml=β a MathML (<math>β¦</math>) string. Converted MathMLβOMML through Office's own transform (no extra needed).
The equation always lands on its own paragraph, and that paragraph's
style is pinned so it never inherits the style of whatever it was
inserted next to (an equation dropped before a Heading 2 used to come
out styled Heading 2 and land in the outline/TOC). display (default
True) gives it the dedicated centred Equation paragraph style
(created on first use, based on Normal β a stable hook for later
equation numbering); display=False resets the paragraph to Normal
and left-aligns it (it is still its own paragraph β wordlive does not
place math mid-sentence β but reads as body text, not centred display
math). where is "after" (default) or "before" this anchor's
range β so doc.headings["Derivation"].insert_equation(...) drops an
equation under a heading and doc.end.insert_equation(...) appends one.
Returns an EquationAnchor (equation:N);
read it back as MathML with equation.mathml, or discover every equation
via doc.equations. Wrap in
doc.edit(...) for atomic undo. Raises EquationError for malformed
input (none, or more than one, of the three dialects; unparseable
MathML/LaTeX; a missing LaTeX backend) and ValueError for a bad where.
Source code in src/wordlive/_anchors/_anchor_media.py
insert_paragraph_before ¶
Insert a new paragraph immediately before this anchor's range.
If style is given it must name a style defined in the document
(StyleNotFoundError otherwise, before any text is inserted); with no
style the new paragraph defaults to Normal rather than inheriting
the anchor's style.
Source code in src/wordlive/_anchors/_anchor_insert.py
insert_paragraph_after ¶
Insert a new paragraph immediately after this anchor's range.
If style is given it must name a style defined in the document
(StyleNotFoundError otherwise, before any text is inserted); with no
style the new paragraph defaults to Normal rather than inheriting
the anchor's style.
When the anchor is (or ends at) the document's final paragraph there is
no position after the terminal paragraph mark to write to β Word
rejects Range(end, end) there with a "value out of range" COM error.
In that case the new paragraph is split in just before the final mark
instead, so appending to the end of a document β the common
"build from scratch" case, where the only paragraph is the last one β
just works.
Source code in src/wordlive/_anchors/_anchor_insert.py
insert_block ¶
Insert a contiguous run of styled paragraphs at this anchor, atomically.
The multi-paragraph counterpart to insert_paragraph_after β drop a
whole styled section (a feature list, a set of bullets, a heading plus
its body) in one op, in natural reading order. Inserting paragraphs
one at a time forces a reverse-order dance to dodge positional-anchor
renumbering; this places them all at a single point so order is just the
order of items.
Each item is one paragraph, given as either a plain string or a dict:
"some text"β sugar for{"text": "some text"}.{"text": "**Bold lead** β rest", "style": "List Bullet"}βtextcarries the tiny inline markdown (**bold**,*italic*,***both***, and`code`for a monospace run; escape a literal delimiter with a backslash,\*/`\```), andstyle` names the paragraph style.{"runs": [{"text": "Bold lead", "bold": true}, {"text": " β rest"}], "style": "List Bullet"}β the structured form: each run is{text, bold?, italic?, underline?, code?, style?}(a per-run character style). Use it when markup is ambiguous or you need a runstyle.
An item that names no style gets Normal β not the insertion point's
style. Pass style explicitly to match the surroundings (a paragraph's
current style is in doc.paragraphs.list()[i]["style"]).
Returns a RangeAnchor spanning the inserted
block (range:START-END), so a follow-up op can target the whole run β
e.g. apply_list it into a bulleted section, or comment on it. where
is "after" (default) or "before" this anchor's range. Resolves
every paragraph/run style up front, so an unknown style name raises
StyleNotFoundError before any text is inserted. Wrap in doc.edit(...)
for atomic undo. Raises OpError for a malformed items payload.
Source code in src/wordlive/_anchors/_anchor_insert.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | |
insert_section ¶
Insert a heading plus its body in one atomic op.
The opinionated common case over insert_block: a single
Heading {level} paragraph followed by body, placed in reading
order at one point. heading carries the same inline markdown a block
item's text does (**bold**, *italic*); body is the insert_block
items shape β a list of plain strings or {text|runs, style?} dicts
(a bare string is sugar for a one-paragraph body). level is 1β9 and
selects the built-in Heading {level} style (validated before any
mutation; an absent style raises StyleNotFoundError via insert_block).
A body item that names no style gets Normal (see insert_block) β
it does not inherit the heading just written above it, nor the anchor's own
style. Give an item an explicit style to override that.
Returns the section's spanning RangeAnchor
(range:START-END). Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_anchors/_anchor_insert.py
insert_markdown ¶
Insert a constrained-Markdown block as real Word structure, atomically.
Maps a deliberately tiny block dialect (see _markdown) to paragraphs,
headings, and lists: #/##/### β Heading 1/2/3, -/*
β a bulleted list, 1. β a numbered list, blank-line-separated text β
Normal paragraphs, with inline **bold**/*italic* spans honoured.
It is a subset, not CommonMark β no code fences, nested lists, block
quotes, or tables in v1; anything unrecognised is literal paragraph text.
The whole block is one insert_block (one contiguous write); each
same-kind list run is then apply_list-ed over its own span, so a
numbered list reads 1..N. where is "after" (default) or "before"
this anchor's range. Returns the RangeAnchor
spanning everything inserted. Raises OpError for empty markdown.
Source code in src/wordlive/_anchors/_anchor_insert.py
insert_table ¶
insert_table(rows: int | None = None, cols: int | None = None, *, where: str = 'after', style: str | None = None, data: list[Any] | None = None, header: bool = False) -> Any
Create a rows Γ cols table at this anchor and return it.
The structural counterpart to insert_image β it creates new
document structure rather than editing existing structure. Returns the
new Table wrapper so create β fill β read closes on
one object; the table's 1-based document index is on .index.
where is "after" (default) or "before" this anchor's range β
so doc.headings["Pricing"].insert_table(...) drops a table just under
a heading, and doc.end.insert_table(...) (i.e.
Document.add_table) appends one.
style names a table style defined in the document (e.g. "Table
Grid"); an unknown name raises StyleNotFoundError before anything is
inserted. style=None applies the built-in "Table Grid" when it's
available, so a table has visible borders by default rather than the
invisible cell gridlines of a styleless table.
data populates the cells at creation and can be given two ways:
- a row-major 2-D list (
[[r1c1, r1c2], β¦]); or - records β a list of dicts (
[{"Item": "Travel", "Cost": "$400"}, β¦]), where the first record's keys become a header row and each dict a body row (soheaderis forced on). The natural shape for tabular data an LLM already has as rows of objects.
When data is given, rows/cols are optional β they're inferred
from the data's shape β so the common case is just
end.insert_table(data=β¦). Pass them explicitly to pad the grid
larger than the data; data is validated against the final rows Γ
cols up front (OpError on overflow) and a short payload leaves the
trailing cells empty. Filling at creation keeps the whole grid in one
atomic undo and beats a set_cell storm. With no data, both rows
and cols are required.
header=True bolds the first row as a header (records imply it). Wrap
in doc.edit(...) for atomic undo. Raises ValueError for an unknown
where and OpError for a non-positive rows/cols, a missing
dimension with no data to infer it from, or a bad data shape.
Source code in src/wordlive/_anchors/_anchor_insert.py
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 | |
insert_break ¶
Insert a page, column, or section break at this anchor.
The explicit one-off break β the clean alternative to appending a
paragraph whose text is a literal form-feed. kind is one of:
"page"(default) β a manual page break (the 90% case)."column"β a column break (multi-column layouts)."section_next"β a section break that starts the new section on the next page."section_continuous"β a section break with no page break, so the new section flows on the same page.
Section breaks pair with Document.sections:
each new section gets its own headers/footers and page setup. To make a
style (e.g. every Heading 1) open a new page without a stray break
character, prefer
format_paragraph(page_break_before=True)
instead β it survives reflow.
where is "after" (default) or "before" this anchor's range.
Wrap in doc.edit(...) for atomic undo. Raises ValueError for an
unknown kind or where.
Source code in src/wordlive/_anchors/_anchor_insert.py
insert_field ¶
Insert a Word field at this anchor β a self-updating value, not literal text.
A field shows a computed value Word keeps current: a page number, the page count, today's date, the file name, a document property. The named kinds are:
"page"β the current page number ({ PAGE })."numpages"β the total page count ({ NUMPAGES }); pair with"page"for "Page X of Y"."date"/"time"β the current date / time."filename"β the document's file name."author"/"title"β document-property fields.
For anything else, kind="field" is the escape hatch: pass the raw
field code as text (e.g.
insert_field("field", text="REF myBookmark \\h")) and Word inserts an
empty field carrying that code.
Page numbers belong in a header or footer β because a HeaderFooter
is an anchor, doc.sections[1].footer().insert_field("page") works,
and HeaderFooter.insert_page_number() is the
sugar for it. Newly inserted fields render once; call
Document.update_fields() (or take a snapshot,
which repaginates) to refresh them after later edits.
where is "after" (default) or "before" this anchor's range.
Bad input raises OpError. Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_anchors/_anchor_insert.py
insert_footnote ¶
Insert a footnote at this anchor and return it as a Footnote anchor.
A footnote drops a reference mark in the main text and puts text in the
note body at the bottom of the page; Word auto-numbers the mark. The
returned Footnote is addressed footnote:N, so
note.set_text(...) edits the body and note.delete() removes the mark
and body together. Discover existing footnotes with
doc.footnotes.
where is "after" (default) or "before" this anchor's range β
the side the reference mark lands on. Wrap in doc.edit(...) for atomic
undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_insert.py
insert_endnote ¶
Insert an endnote at this anchor and return it as an Endnote anchor.
The endnote mirror of insert_footnote:
the reference mark lands in the main text and text collects at the end
of the document (or section). The returned
Endnote is addressed endnote:N; discover existing
endnotes with doc.endnotes.
where is "after" (default) or "before" this anchor's range.
Wrap in doc.edit(...) for atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_insert.py
insert_content_control ¶
insert_content_control(kind: str = 'rich_text', *, title: str | None = None, tag: str | None = None, items: list[Any] | None = None, where: str = 'wrap', lock_contents: bool = False, lock_control: bool = False) -> ContentControl
Wrap (or insert) a content control at this anchor and return it.
Content controls are the building blocks of form-like documents β a
labelled region the user fills in. kind selects the type:
"rich_text" (the default β formatted text), "text" (plain text),
"picture", "combo_box" / "dropdown" (a pick list β pass
items), "date", "checkbox" (Word 2013+), "building_block",
"group", or "repeating_section" (Word 2013+).
where is "wrap" (default β the control surrounds this anchor's
existing range, so a range:START-END from find wraps that phrase) or
"before" / "after" (insert a fresh empty control at the anchor's
start / end). Set title and/or tag to give the control a name: a
titled control is addressable later as cc:TITLE (falling back to the
tag) and shows a label in Word's UI. items populates a combo box or
dropdown β each is a string, or a {"text": ..., "value": ...} dict.
lock_contents stops the user editing the value; lock_control stops
them deleting the control.
Returns the new ContentControl (usable even
when unnamed β it caches the live control). Wrap in doc.edit(...) for
atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_anchor_insert.py
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 | |
wordlive.Bookmark ¶
wordlive.ContentControl ¶
Bases: Anchor
Source code in src/wordlive/_anchors/_content_controls.py
set_properties ¶
set_properties(*, title: Any = _charts._UNSET, tag: Any = _charts._UNSET, lock_contents: bool | None = None, lock_control: bool | None = None) -> ContentControl
Re-set this control's metadata in place β no delete + reinsert.
Tri-state: omit a field to leave it untouched, pass a string to set it,
or None (equivalently "") to clear title / tag. lock_contents
stops the user editing the value; lock_control stops them deleting the
control β pass a bool to set either, omit to leave. Renaming the title
(or the tag, when untitled) changes the control's cc:NAME anchor id;
the returned handle keeps working regardless. Returns self (chainable);
wrap in doc.edit(...) for atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_content_controls.py
set_items ¶
Replace a combo-box / dropdown's choice list β no delete + reinsert.
items is the full new list (it replaces the existing entries, not
appends); each is a string or a {"text": ..., "value": ...} dict.
Only valid on a combo_box / dropdown control β any other kind
raises OpError. Returns self (chainable); wrap in doc.edit(...)
for atomic undo. Bad input raises OpError.
Source code in src/wordlive/_anchors/_content_controls.py
wordlive.Heading ¶
Bases: Anchor
Source code in src/wordlive/_anchors/_anchor_core.py
section_range ¶
COM Range covering the body under this heading.
Spans from the end of the heading paragraph to the start of the next
heading whose level is <= this one's (or to the end of the document
if no such heading exists). Excludes the heading paragraph itself.
Source code in src/wordlive/_anchors/_headings.py
section_text ¶
replace_section_body ¶
Rewrite this heading's body, leaving the heading paragraph intact.
The "rewrite section X" workflow: clears the span under this heading
(section_range, up to the next same-or-higher heading) and inserts
body after the heading. With markdown=False (default) body is the
insert_block items shape (or a bare string); with markdown=True
body is a constrained-Markdown string routed through insert_markdown.
Returns the new body's spanning RangeAnchor.
Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_anchors/_headings.py
wordlive.HeadingCollection ¶
Iterable, indexable view over a document's headings.
Symmetric with BookmarkCollection and ContentControlCollection:
for h in doc.headings: # iteration β Heading per heading paragraph
...
doc.headings["Risks"] # by visible text
doc.headings[3] # by 1-based paragraph index
"Risks" in doc.headings # membership
doc.headings.list() # same shape as doc.outline()
Source code in src/wordlive/_anchors/_headings.py
list ¶
Same shape as Document.outline() β [{level, text, anchor_id}, ...].
Source code in src/wordlive/_anchors/_headings.py
wordlive.Paragraph ¶
Bases: Anchor
A paragraph located by 1-based index over doc.Paragraphs.
para:N addresses any paragraph β body text, headings, list items alike.
heading:N is the same index space narrowed to heading paragraphs, so
para:5 and heading:5 resolve to the same paragraph when paragraph 5 is a
heading. A Paragraph inherits every anchor verb (set_text, apply_style,
format_paragraph, apply_list, insert_paragraph_before/after, β¦).
Source code in src/wordlive/_anchors/_paragraphs.py
wordlive.ParagraphCollection ¶
Indexable, iterable view over every paragraph in the document.
Unlike headings, this includes body paragraphs and list items, not just
heading paragraphs. Index by 1-based position (doc.paragraphs[2]); iterate
for a Paragraph per paragraph. list() emits each paragraph's start /
end offsets, so a body paragraph can be turned into a range:START-END
insertion point for mid-paragraph edits.
Source code in src/wordlive/_anchors/_paragraphs.py
at ¶
Return the paragraph whose range contains offset, or None.
Used to map a character offset (e.g. the cursor position) back to a
para:N anchor.
Source code in src/wordlive/_anchors/_paragraphs.py
list ¶
Every paragraph as [{index, anchor_id, level, style, is_heading, start, end, text}, ...].
style is the paragraph's applied Word style name (e.g. "Normal",
"List Number", "Heading 2") β the handle to feed back into
apply_style / a write's style= to mirror existing formatting, since
level (Word's OutlineLevel) is 10 for all non-heading paragraphs
and so can't distinguish a list item from body text. It's None if the
style can't be read.
Source code in src/wordlive/_anchors/_paragraphs.py
wordlive.RangeAnchor ¶
Bases: Anchor
An anchor over an arbitrary character range β doc.range(start, end).
Unlike bookmarks/headings/cells, a range anchor names nothing in the
document: it's a pair of absolute character offsets (UTF-16 code units, the
same coordinates Word's Document.Range(start, end) uses and that
Document.find() emits as range:START-END). It's the generic target when
no named anchor exists β feed a find() hit straight into a replace, or
drop a comment on an offset span.
The anchor is ephemeral: offsets resolve live against the document on each
access, so an edit elsewhere that shifts the text can leave it pointing at
the wrong span. Resolve, act, discard. set_text keeps the anchor's own
end in sync with the replacement so chained ops on the same instance stay
consistent.
Source code in src/wordlive/_anchors/_range.py
wordlive.StartAnchor ¶
Bases: Anchor
A zero-width anchor at the very start of the document body β doc.start.
The mirror of EndAnchor: the insertion point before
the first paragraph. doc.start returns it and anchor_by_id("start")
resolves it, so "prepend to the document" composes with the usual verbs and
the CLI --anchor-id plumbing.
Only the prepend direction is meaningful at a single start-point, so every
insert verb lands text at the start: insert_paragraph_before /
insert_paragraph_after add a new first paragraph (delegating to
Document.prepend_paragraph), and
insert_before / insert_after / set_text prepend inline (delegating to
Document.prepend). text is always empty and
delete() is a no-op. insert_image and apply_style are inherited: they
resolve to the collapsed start position.
Source code in src/wordlive/_anchors/_range.py
wordlive.EndAnchor ¶
Bases: Anchor
A zero-width anchor at the very end of the document body β doc.end.
The one position no content names: the insertion point past the last
paragraph. doc.end returns it and anchor_by_id("end") resolves it, so
"append to the document" composes with the same verbs and the same CLI
--anchor-id plumbing as every other anchor β no .com drop needed.
Only the append direction is meaningful at a single end-point, so every
insert verb lands text at the end: insert_paragraph_after /
insert_paragraph_before add a new final paragraph (delegating to
Document.append_paragraph), and
insert_after / insert_before / set_text append inline (delegating to
Document.append). text is always empty and
delete() is a no-op β there is no content here to read or remove.
insert_image and apply_style are inherited: they resolve to the
collapsed end position, so an image lands at the end and a style falls on
the final paragraph.
Source code in src/wordlive/_anchors/_range.py
Editing¶
Selection is the explicit cursor surface: doc.selection.info() reads where
the cursor is, and doc.selection.write(text, replace=...) types at it.
write deliberately moves the cursor, so wrap it in
doc.edit() and call
scope.allow_cursor_move() for atomic undo without
snapping the cursor back. Everywhere else, prefer anchors over the cursor.
wordlive.EditScope ¶
Wraps a Word UndoRecord + a Selection snapshot.
One Ctrl-Z reverts every mutation made inside the with block. The
user's cursor and scroll position are restored on exit unless code inside
the scope calls allow_cursor_move().
Source code in src/wordlive/_edit.py
wordlive.Selection ¶
Wrapper around Application.Selection. Mostly used for reads.
Source code in src/wordlive/_selection.py
info ¶
Structured snapshot of the current selection for wordlive reads.
collapsed is true when there's an insertion point but no selected
text (start == end). The CLI's cursor read enriches this with the
containing para:N anchor.
Source code in src/wordlive/_selection.py
write ¶
Insert text at the user's cursor β the deliberate cursor write.
Unlike every anchor write, this targets the live Selection. With a
spanning selection and replace=True (the default) the selected text is
overwritten; with replace=False, or a collapsed cursor, the text is
inserted at the selection start. Either way the cursor is left after
the inserted text.
This intentionally moves the cursor, so it fights EditScope's
cursor-preservation. To get atomic undo without snapping the cursor
back, wrap it: ::
with doc.edit("type at cursor") as scope:
scope.allow_cursor_move()
doc.selection.write("β¦")
Source code in src/wordlive/_selection.py
wordlive.SelectionSnapshot
dataclass
¶
A point-in-time capture of where the user's cursor and view are.
vertical_percent
class-attribute
instance-attribute
¶
ActiveWindow.VerticalPercentScrolled at snapshot time, or None if unavailable.
Lists & numbering¶
List operations apply to a range's paragraphs, so the verbs live on
Anchor β apply_list("numbered"), remove_list(),
list_info(), restart_numbering(), and indent_list() / outdent_list()
work on any anchor. Document.lists is a read-only
ListCollection for discovering the lists already in
the document; index it (doc.lists[2]) to get a
RangeAnchor over a list's range.
Custom list formats. Where apply_list only applies a gallery default,
anchor.apply_list_format(levels) authors a custom multi-level list template
and applies it. levels is a 1-based list of per-level spec dicts β each setting
the marker format ("%1.", "%1)", "%1.%2"), number style ("arabic",
"upper-roman", "lower-letter", β¦) or bullet glyph + font, plus
start_at / number_position / text_position / trailing / alignment /
bold / color. More than one level mints an outline template.
anchor.read_list_levels() is the read mirror β one {level, kind, format,
number_style, style, trailing, number_position, text_position, font} dict per
template level (number_style is the raw WdListNumberStyle int). A multi-level
number level authored without an explicit format keeps Word's built-in outline
default; hierarchical numbering (%1.%2.%3.) still needs an explicit format.
with doc.edit("custom numbering"):
doc.headings["Steps"].apply_list_format([
{"kind": "number", "format": "%1)", "style": "lower-letter", "trailing": "space"},
{"kind": "bullet", "bullet": "β", "font": "Symbol"},
])
wordlive.ListCollection ¶
Read-only, iterable view over the document's lists (doc.lists).
Index a list by 1-based position (doc.lists[2]) to get a
RangeAnchor over its whole range β so every list
verb (apply_list, restart_numbering, β¦) is immediately available on it.
list() returns a summary per list; positions match Word's own
Document.Lists(n) ordering.
Source code in src/wordlive/_lists.py
list ¶
All lists as {index, type, count, anchor_id} dicts.
Source code in src/wordlive/_lists.py
Styles¶
Styles are document-scoped handles. Document.styles is a
StyleCollection; apply styles to anchors via
Anchor.apply_style. Define a new style with
doc.styles.add(name, type="paragraph", based_on=β¦, next_style=β¦), which returns
a writable Style: set its defaults with style.format_run(β¦)
/ style.format_paragraph(β¦) (the same kwargs as the anchor methods, minus
highlight) and chain styles via style.base_style / style.next_paragraph_style.
The brand/template workflow: add a house style once, then apply_style it
everywhere.
wordlive.Style ¶
A read-only view onto a single Word style.
Properties access the COM object lazily; nothing is cached so renames or deletions during the session don't return stale data.
Source code in src/wordlive/_styles.py
com
property
¶
Raw COM Style object. Raises StyleNotFoundError if the style is gone.
Tries direct lookup (Styles(name)) first β O(1) on Word's side β and
falls back to iteration only if that raises. Membership checking
still iterates (Word doesn't reserve an HRESULT for "missing style"
and a generic com_error would be indistinguishable from a real
failure), but once the caller has a Style instance the name is
presumed valid and the direct path is safe.
base_style
property
writable
¶
The name of the style this one inherits from (None if unset).
next_paragraph_style
property
writable
¶
The name of the style applied to the next paragraph (None if unset).
format_run ¶
Set this style's character (font) defaults.
Same kwargs as Anchor.format_run β
bold/italic/underline/font/size/color/β¦ β minus highlight
(a style's Font has no highlight property). Tri-state: only the kwargs
you pass are written. Bad input raises OpError.
Source code in src/wordlive/_styles.py
format_paragraph ¶
Set this style's paragraph defaults.
Same kwargs as
Anchor.format_paragraph
(alignment, indents, spacing, page_break_before). Tri-state. Bad
input raises OpError.
Source code in src/wordlive/_styles.py
wordlive.StyleCollection ¶
Indexable, iterable view over a document's styles.
Source code in src/wordlive/_styles.py
list ¶
All styles as {name, type, builtin, in_use} dicts.
Source code in src/wordlive/_styles.py
add ¶
add(name: str, *, type: str = 'paragraph', based_on: str | None = None, next_style: str | None = None) -> Style
Define a new style and return it as a writable Style.
type is "paragraph" (default), "character", "table", or "list".
based_on and next_style are names of existing styles (the inheritance
parent and the style applied to the following paragraph). The brand /
template primitive: define a house style once, then apply_style it
everywhere. Style its defaults via the returned object's
format_run(...) / format_paragraph(...). Bad type raises OpError;
an unknown based_on / next_style raises StyleNotFoundError.
Source code in src/wordlive/_styles.py
Tables¶
Create, read, and restructure tables β a cell is itself an anchor.
Tables¶
Document.tables is a TableCollection. Index a
table by 1-based position or Title, then read or edit it. A
Cell is an Anchor β its id is
table:N:R:C, so doc.anchor_by_id("table:1:2:3") returns a cell that works
with set_text, apply_style, and format_paragraph like any other anchor.
Create tables with Document.add_table(rows, cols, β¦)
(append at the end) or Anchor.insert_table(...) (at any
position anchor); both return the new Table, populate cells
from a row-major data grid, default to the Table Grid style, and keep
appended tables from merging into an adjacent one. Table.delete() removes a
whole table β the structural mirror of add_row / delete_row.
Table.set_heading_row(row=1, heading=True, allow_break=None) marks a row as a
repeating header that reprints on every page the table spans.
Treat a table as records keyed by its header row (row 1) β the read/update
mirror of building one from data=[{...}]. Table.records() returns the body
rows as a list of {header: cell_text} dicts; Table.append_record({...})
appends a row from a dict (keys mapped to header columns, missing β empty, extra
β ignored); Table.update_row(key, {...}, column=None) sets cells by header name
on the first row whose key-column (the first column, or the header named by
column) equals key β addressing a row by content instead of a fragile
1-based index.
Restyle a table after creation. Table.set_style(name) points an existing
table at any built-in or custom table style β the post-creation counterpart of
insert_table(style=β¦). Applying a style reapplies its conditional formatting and
overwrites direct cell shading, so restyle first, then layer cell-level
overrides. Table.set_alignment("left"|"center"|"right") positions the whole
table across the page; Table.set_borders(sides=β¦, style=β¦, weight=β¦, color=β¦)
rules the whole grid in one call (the table-wide counterpart of the per-cell
set_borders; interior gridlines via "horizontal"/"vertical");
Table.set_banding(first_row=β¦, last_row=β¦, first_column=β¦, last_column=β¦,
banded_rows=β¦, banded_columns=β¦) toggles Word's "Table Style Options" (tri-state,
None leaves a flag untouched β needs a real table style applied to show).
Cell.set_vertical_alignment("top"|"center"|"bottom") sets a cell's vertical
alignment.
Style a whole row or column in one call. A row is addressable as
table:N:row:R (a RowAnchor) and a column as
table:N:col:C (a ColumnAnchor); Table.row(R) /
Table.column(C) return the same objects. Both are anchors, so the inherited
set_shading / set_borders / apply_style / format_run / format_paragraph
style the whole strip β doc.tables[1].row(1).set_shading(fill="#DDD") shades the
header row, table.column(3).format_paragraph(alignment="right") right-aligns a
totals column. A row is a contiguous range. A column is not β Word has no
per-column model on a table with merged or mixed-width cells, so a column op there
raises OpError pointing at per-cell table:N:R:C styling (a regular table fans
the op across the column's cells transparently).
Add or remove a column; merge or split cells. Table.add_column(values=None)
appends a column at the right edge β the column mirror of add_row, with
values filling top-to-bottom; Table.delete_column(index) removes one.
(delete_column raises OpError on a merged / mixed-width table β Word can't
address an individual column there, so delete its cells via table:N:R:C.)
Cell.merge(other) joins two cells (and the rectangle they span) into one,
keeping the calling cell's id; Cell.split(rows=1, cols=2) is its inverse.
Either makes the table non-uniform: Table.is_uniform then reports False,
table:N:R:C indexes physical cells (a merged row has fewer than
column_count), and Table.read() walks each row's physical cells so it stays
safe on an irregular grid (its uniform field flags the shape).
wordlive.TableCollection ¶
Indexable, iterable view over a document's tables.
Index by 1-based position (doc.tables[1]) or by the table's Title
(doc.tables["Budget"]). Positions match Word's own Tables(n) ordering β
document order, top to bottom.
Source code in src/wordlive/_tables.py
wordlive.Table ¶
Wraps a Word Table COM object, located by its 1-based document position.
The index is stored at construction (the collection knows it without a COM
round-trip), so anchor_id and cell ids never have to re-scan the document.
Source code in src/wordlive/_tables.py
is_uniform
property
¶
Whether every row has the same physical cell count β a clean grid.
True for a freshly built R Γ C table; False once a cell has been
merged or split. On a non-uniform table table:N:R:C indexes physical
cells (so an index can shift after a merge or fall off a short row),
delete_column / column anchors raise "mixed cell widths", and
row_count Γ column_count overstates the true cell count. Worth a
check before addressing cells in a table you didn't build.
cell ¶
Return the Cell at 1-based (row, col).
Raises AnchorNotFoundError (kind "table cell") if the coordinates
fall outside the table's grid.
Source code in src/wordlive/_tables.py
row ¶
Return the RowAnchor for the 1-based row (table:N:row:R).
A styling handle for the whole row β table.row(1).set_shading(fill=β¦)
shades it, .format_run(bold=True) bolds it. Same object as
doc.anchor_by_id("table:N:row:R"). Raises AnchorNotFoundError (kind
"table row") if out of range.
Source code in src/wordlive/_tables.py
column ¶
Return the ColumnAnchor for the 1-based col (table:N:col:C).
The column counterpart of row() β table.column(3).format_paragraph(
alignment="right") right-aligns a totals column. Same object as
doc.anchor_by_id("table:N:col:C"). Raises AnchorNotFoundError (kind
"table column") if out of range. (A column op on a merged / mixed-width
table raises OpError when applied β see ColumnAnchor.)
Source code in src/wordlive/_tables.py
grid ¶
All cell text as a row-major list[list[str]].
Iterates each row's physical cells, so it stays safe on a merged /
split table (a merged row simply yields fewer columns); on a uniform
table it's the plain row_count Γ column_count grid.
Source code in src/wordlive/_tables.py
read ¶
Structured dump: metadata plus every cell with its addressable id.
Each cell carries its anchor_id (table:N:R:C) so a caller can feed
it straight back into replace / style apply / format-paragraph.
Cells are walked physically per row (robust to merged / split
tables); uniform reports whether rows Γ columns is the full grid.
Source code in src/wordlive/_tables.py
to_dict ¶
Metadata only β {index, title, rows, columns}. Used by table list.
records ¶
Read the body rows as a list of dicts keyed by the header row.
Row 1 is taken as the header (the exact inverse of building a table from
data=[{...}] β see insert_table); each row below it becomes
{header: cell_text}. A pure read β no doc.edit() needed.
Edge cases mirror the write path: a duplicate header label collapses (the rightmost column wins), and a blank header cell yields an empty-string key β both the caller's responsibility.
Source code in src/wordlive/_tables.py
add_row ¶
Append a row at the end of the table, optionally filling its cells.
values are matched to columns left-to-right; extras past the column
count are ignored, short lists leave trailing cells empty.
Source code in src/wordlive/_tables.py
append_record ¶
Append a row from a dict, mapping its keys to the header columns.
Keys are matched against row 1's headers; a header with no matching key
gets an empty cell and an extra key is ignored β the same lenient
mapping insert_table(data=[{...}]) uses. The new row inherits the
table's existing formatting / banding (Word's Rows.Add). Wrap in
doc.edit(...) for atomic undo.
Source code in src/wordlive/_tables.py
update_row ¶
Update the first row whose key-column cell equals key, by header name.
The key column is the first column by default, or the header named
by column=. Each item in values sets the cell under that header
({header: new_text}). First match wins when several rows share key.
Validates against the header before mutating: an unknown column,
or a values key that isn't a header, raises OpError (exit 1). If no
row matches key, raises AnchorNotFoundError (exit 2). Wrap in
doc.edit(...) for atomic undo.
Source code in src/wordlive/_tables.py
delete_row ¶
Delete the 1-based row index.
Raises AnchorNotFoundError (kind "table row") if out of range.
Source code in src/wordlive/_tables.py
add_column ¶
Append a column at the right edge of the table, optionally filling it.
The column mirror of add_row: values are matched to rows
top-to-bottom; extras past the row count are ignored, a short list
leaves trailing cells empty. (Word's Columns.Add tolerates a merged
table, so this works where delete_column can't.) Wrap in
doc.edit(...) for atomic undo.
The new column lands at the right edge, so existing table:N:R:C ids are
unchanged β but any cached column_count is now stale; re-read it.
Source code in src/wordlive/_tables.py
delete_column ¶
Delete the 1-based column index.
Raises AnchorNotFoundError (kind "table column") if out of range.
Word can't address an individual column on a table with merged /
mixed-width cells ("mixed cell widths") β that ComError is re-raised as
an OpError pointing at per-cell deletion via table:N:R:C (the same
contract as a column-anchor style op). Wrap in doc.edit(...) for
atomic undo.
Every column to the right of index renumbers down by one, so any
cached table:N:R:C ids past it are now stale β re-resolve through
doc.tables before addressing another column.
Source code in src/wordlive/_tables.py
set_heading_row ¶
Mark a 1-based row as a repeating table heading row.
A heading row (HeadingFormat) repeats at the top of every page the
table spans β set it on the header row of a multi-page table so the
column labels carry over. heading=False clears the flag.
allow_break controls AllowBreakAcrossPages (whether a row's content
may split across a page boundary). It defaults to not heading β a
repeating header shouldn't fracture β so the common
set_heading_row(1) both repeats row 1 and keeps it intact; pass
allow_break explicitly to override.
Raises AnchorNotFoundError (kind "table row") if row is out of
range.
Source code in src/wordlive/_tables.py
autofit ¶
Resize the table's columns β fit to content, the window, or pin them.
mode is one of:
"content"(default) β shrink/grow each column to fit its cells."window"β stretch the table to the page (container) width."fixed"β pin the current column widths so Word stops auto-sizing (setsAllowAutoFit = False).
A clean way to tidy a table whose columns drifted after edits. An unknown
mode raises OpError. Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_tables.py
set_style ¶
Restyle this existing table with a named table style.
The post-creation counterpart of insert_table(style=β¦) β point a table
at any built-in or custom table style ("Grid Table 4 - Accent 1",
"Plain Table 3", β¦; discover them via style list filtered to
type=="table"). Raises StyleNotFoundError (exit 2) if the style isn't
defined in the document.
Direct cell shading is not preserved. Applying a table style reapplies
the style's conditional formatting (banding, header shading), which
overwrites explicit per-cell set_shading colours (live-confirmed). So
restyle first, then layer cell-level overrides on top β not the
reverse. Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_tables.py
set_alignment ¶
Align the whole table across the page width β left, center, or right.
alignment is "left" / "center" ("centre") / "right",
mapped onto Table.Rows.Alignment. This positions the table between the
page margins (distinct from the text alignment inside cells, which is
format_paragraph). Idempotent. Wrap in doc.edit(...); bad input raises
OpError.
Source code in src/wordlive/_tables.py
set_borders ¶
set_borders(*, sides: Any = 'all', style: Any = 'single', weight: Any = 0.5, color: Any = None) -> None
Draw borders across the whole table grid in one call.
The table-wide counterpart of the per-cell set_borders (a Cell is an
Anchor). sides is "all"/"box" (the four outer edges β the
default), a single outer edge ("top"/"bottom"/"left"/
"right"), the interior gridlines ("horizontal"/"vertical" β
the lines between cells), or a list (e.g. ["box", "horizontal",
"vertical"] to rule every line). style is a line style ("single",
"double", "dot", "dash", β¦, or "none" to clear). weight
is the line width in points, snapped to Word's set (0.25/0.5/0.75/1/1.5/
2.25/3). color is an optional name/hex/RGB. Idempotent. Bad input raises
OpError. Wrap in doc.edit(...).
Source code in src/wordlive/_tables.py
set_banding ¶
set_banding(*, first_row: bool | None = None, last_row: bool | None = None, first_column: bool | None = None, last_column: bool | None = None, banded_rows: bool | None = None, banded_columns: bool | None = None) -> None
Toggle the table-style options (Word's "Table Style Options" ribbon group).
Each flag turns one conditional-formatting band of the applied table
style on or off β first_row (a distinct header row), last_row (a
total row), first_column / last_column, and banded_rows /
banded_columns (the alternating stripes). All are tri-state: True /
False set, None (the default) leaves that flag untouched.
These only show once a real table style is applied β a styleless table
or plain "Table Grid" ignores band conditions. Pair with set_style.
Idempotent. Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_tables.py
delete ¶
Delete this entire table β the structural mirror of add_row.
Removes the table and all its cells from the document. Afterwards this
Table (and any Cell anchors derived from it) is stale; the indices
of any tables that followed it shift down by one, so re-resolve through
doc.tables before addressing another.
Source code in src/wordlive/_tables.py
wordlive.Cell ¶
Bases: Anchor
A single table cell, addressed by 1-based (row, column).
Subclasses Anchor, so it inherits insert_before / insert_after /
delete / apply_style / format_paragraph unchanged. Only the bits that
differ for cells β the COM range, text read/write, and the anchor id β are
overridden here.
Source code in src/wordlive/_tables.py
set_vertical_alignment ¶
Set where this cell's content sits vertically β top, center, or bottom.
align is "top" / "center" ("centre") / "bottom", mapped
onto Cell.VerticalAlignment. (Word shares this value space with page
vertical alignment, whose 2 = justify slot a cell rejects, so only
those three are offered.) Idempotent. Wrap in doc.edit(...) for atomic
undo; bad input raises OpError.
Source code in src/wordlive/_tables.py
merge ¶
Merge this cell with other into one cell spanning their rectangle.
other must belong to the same table. Word joins the cells' text and
collapses the rectangle into its upper-left cell, regardless of which
corner is self vs other β so the merged cell is addressed by the
upper-left coordinate of the spanned rectangle (e.g.
cell(2, 2).merge(cell(1, 1)) yields a cell at table:N:1:1), and the
other spanned coordinates stop resolving. The table becomes
non-uniform (Table.is_uniform β False) β afterwards table:N:R:C
indexes physical cells (a short row has fewer than column_count), so
re-read the table to see the new shape. Wrap in doc.edit(...) for
atomic undo; cross-table cells raise OpError.
Source code in src/wordlive/_tables.py
split ¶
Split this cell into a rows Γ cols grid of cells.
The inverse of merge; defaults to two side-by-side cells
(rows=1, cols=2). Makes the table non-uniform (Table.is_uniform
β False) β see merge. Wrap in doc.edit(...) for atomic undo; a
count below 1 raises OpError.
Source code in src/wordlive/_tables.py
wordlive.RowAnchor ¶
Bases: Anchor
A whole table row, addressed by table:N:row:R (1-based).
Subclasses Anchor over the row's contiguous Rows(R).Range, so the
inherited styling verbs β set_shading, set_borders, apply_style,
format_run, format_paragraph β restyle the entire row in one call
(shading --anchor-id table:1:row:1 shades the header row;
format-run --anchor-id table:1:row:1 --bold bolds it). set_text is
refused β a row is a styling target, not a text slot; edit its cells via
table:N:R:C.
Source code in src/wordlive/_tables.py
wordlive.ColumnAnchor ¶
Bases: Anchor
A whole table column, addressed by table:N:col:C (1-based).
Unlike a row, a column is not a contiguous Word range β Column.Range
isn't reachable under late binding β so this anchor styles the column by
fanning each op out across its cells (Columns(C).Cells). The column-wide
styling verbs (set_shading, set_borders, apply_style, format_run,
format_paragraph) are overridden to loop the cells; range-only ops that need
a single span (set_text, replace, insert_*) raise OpError.
A table with merged or mixed-width cells has no per-column model β Word
raises "mixed cell widths" β so any column op on such a table raises OpError
pointing at per-cell table:N:R:C styling. (Rows are unaffected; use
table:N:row:R.)
Source code in src/wordlive/_tables.py
Embedded objects¶
Pictures, floating shapes, equations, and charts as first-class anchors.
Images¶
The read side of the image story (the write side is
Anchor.insert_image). doc.images is a read-only
discovery collection over the document's embedded pictures; its list() reports
each image's image:N id, MIME type, size (points), alt text, and the para:N
it sits in. Index it (doc.images[2]) for an ImageAnchor,
then call read_image() for the raw bytes + MIME β the path
for handing an embedded picture to a vision model. read_image() also works on
any anchor whose range contains exactly one picture (e.g. doc.paragraphs[7]);
a range with no image, or more than one, raises
ImageSourceError. Extraction is non-mutating, so
it needs no doc.edit(...).
An ImageAnchor is also lightly writable:
set_alt_text(text), set_size(width/height/lock_aspect), and
set_crop(left/top/right/bottom) (trim the picture in from its edges β lengths in
points / "0.2in") restyle an inline picture in place (chainable; wrap in
doc.edit(...)). These cover the non-wrap subset β re-wrapping an image
(floating it) is insert_image(wrap=β¦), which converts it to a shape:N (see
below). To change the picture's bytes, delete and re-insert.
wordlive.ImageAnchor ¶
Bases: Anchor
An embedded picture located by 1-based index β image:N.
Mirrors Word's own InlineShapes(N) ordering (document order). The anchor
resolves to the picture's own one-character range, so
read_image pulls exactly that image's bytes
out. Discover the available images β with their MIME, size, and the para:N
they sit in β via doc.images. An image carries
no editable text, so set_text raises; read_image() is the point of it.
Source code in src/wordlive/_anchors/_image_anchors.py
set_alt_text ¶
Set the picture's accessibility (alt) text. Returns self (chainable);
wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_anchors/_image_anchors.py
set_size ¶
Resize the inline picture. width / height are lengths (points /
"3in"); lock_aspect toggles proportional scaling (dropped
automatically when both dimensions are given, so both stick). To re-wrap
a picture (float it) use insert_image(wrap=β¦) β that crosses it into
shape:N. Returns self (chainable). Bad input raises OpError.
Source code in src/wordlive/_anchors/_image_anchors.py
set_crop ¶
set_crop(*, left: Any = None, top: Any = None, right: Any = None, bottom: Any = None) -> ImageAnchor
Crop the inline picture in from its edges. left / top / right /
bottom are the amounts trimmed off each edge (lengths in points /
"0.2in"); cropping shrinks the displayed size. At least one edge is
required. Returns self (chainable). Bad input raises OpError.
Source code in src/wordlive/_anchors/_image_anchors.py
wordlive.ImageCollection ¶
Read-only, iterable view over the document's embedded images (doc.images).
Index an image by 1-based position (doc.images[2]) to get an
ImageAnchor (image:N), then read_image() for
its bytes + MIME. list() summarises every image β id, MIME, size, alt text,
and the para:N it's anchored in β so a model can see what's there before
pulling any bytes. Positions match Word's own InlineShapes(n) ordering.
Source code in src/wordlive/_anchors/_image_anchors.py
list ¶
Every image as {index, anchor_id, mime, width, height, crop, alt_text, para}.
mime is the picture's content type (read from its package XML β
None if the shape isn't a raster image, e.g. an embedded chart or OLE
object). width/height are in points; crop the {left, top, right,
bottom} insets in points (or None if uncropped). para is the
para:N anchor of the paragraph the image sits in (or None if it
can't be located). Reads each image's content type but not its
(potentially large) bytes β call read_image
for those.
Source code in src/wordlive/_anchors/_image_anchors.py
Watermarks, text boxes & floating shapes¶
Document.set_watermark(text, β¦) stamps a WordArt text watermark
(DRAFT / CONFIDENTIAL) behind every page via each section's header story β
layout="diagonal"/"horizontal", color, font, semitransparent; it
replaces any prior text watermark rather than stacking, and
Document.remove_watermark() clears it (idempotent). Document.watermark() is the
read mirror β it returns a WatermarkInfo (text + the
sections carrying it) or None. Anchor.insert_text_box(text, β¦)
drops a floating text box / pull quote anchored to any anchor's paragraph, with
width/height (points or unit strings), wrap (the insert_image vocabulary
minus "inline"), where, the text-format kwargs, and fill/border. Both are
edits β wrap in doc.edit(...) for atomic undo.
Floating shapes β text boxes, floating images, and WordArt β are on the
anchor model, addressed shape:N. Anchor.insert_text_box returns a
ShapeAnchor, and a floating insert_image (any
wrap other than "inline") returns the picture's ShapeAnchor too β an
"inline" image stays an InlineShape (image:N) and returns None. Discover
them via doc.shapes (all body shapes;
header-story watermarks excluded) or doc.text_boxes
(the text-box subset, a discovery filter that keeps each box's canonical
shape:N id). Restyle in place:
set_wrap(wrap, side, distance_top/bottom/left/right) (the wrap style, which
sides text flows past β both/left/right/largest, honoured by
square/tight/through β and the standoff gaps; pass any one),
set_position(left/top/relative_to), set_size(width/height/lock_aspect),
set_crop(left/top/right/bottom) (trim a picture shape in from its edges),
format(fill/border/border_weight), set_alt_text; set_text edits a text box's
contents and replace_image swaps a floating picture's bits (delete + reinsert at
the same anchor, preserving wrap / position / size). shape:N is positional in
document order, so adding or removing a shape renumbers the rest β re-list rather
than caching an id.
Deeper layout knobs round it out: set_rotation(degrees) (absolute angle),
set_z_order("front"|"back"|"forward"|"backward") (restack within the floating
layer β distinct from wrap's in-front-of/behind-text; because Document.Shapes
orders by z-order, a restack renumbers shape:N β re-list before reusing an
id), and
set_text_frame(margin_left/right/top/bottom, word_wrap) for a text box's
internal insets. Grouping: doc.group_shapes(*shape_ids)
collapses two or more floats into one group shape:N (moved / sized / deleted as
a unit), and ShapeAnchor.ungroup() dissolves it back
into its members' ShapeAnchors. There is no autosize ("resize-to-fit-text")
control β Word doesn't expose it cleanly over COM. The textbox:N id is an alias
onto a text box's canonical shape:N (anchor_by_id("textbox:1") β‘ the first
text box).
wordlive.ShapeAnchor ¶
Bases: Anchor
A floating shape located by 1-based index β shape:N.
Indexes the document's body-story floating shapes (text boxes, floating
images, WordArt) in document order β the restyle handle that
insert_text_box and a floating
insert_image return. shape_type reports
the kind ("text_box" / "picture" / "wordart" / β¦). Restyle in
place with set_wrap / set_position / set_size / format /
set_alt_text; a text box's contents edit via set_text, a floating
picture's image swaps via replace_image. Discover shapes via
doc.shapes (or just the text boxes via
doc.text_boxes).
A floating shape anchors to a paragraph, not a character position, so positions renumber as shapes come and go β re-list, don't cache. Watermarks live in the header story and are excluded.
Source code in src/wordlive/_anchors/_shape_anchors.py
shape_type
property
¶
The shape kind β "text_box" / "picture" / "wordart" / "group" / β¦.
z_order
property
¶
The shape's 1-based stacking position (ZOrderPosition; higher = nearer the front).
revision_segments ¶
The shape's text as a single unchanged segment (no tracked-change view).
A floating shape's text lives in its own text-frame story, while
doc.revisions enumerates the main body β the two don't share offsets, so
tracked-change views aren't available inside shapes. This mirrors
text (rather than reporting the anchoring
paragraph's unrelated revision history). text_final / text_original
therefore both equal text.
Source code in src/wordlive/_anchors/_shape_anchors.py
set_wrap ¶
set_wrap(wrap: str | None = None, *, side: str | None = None, distance_top: Any = None, distance_bottom: Any = None, distance_left: Any = None, distance_right: Any = None) -> ShapeAnchor
Set how body text flows around the shape.
wrap is the style β "square" / "tight" / "through" /
"top-bottom" / "front" / "behind". side is which sides text
flows past β "both" / "left" / "right" / "largest" (only
"square" / "tight" / "through" honour it; Word ignores it for
the others). distance_* are the standoff gaps between text and the shape
(lengths in points / "0.1in"). At least one argument is required.
Returns self (chainable). Bad input raises OpError.
Source code in src/wordlive/_anchors/_shape_anchors.py
set_crop ¶
set_crop(*, left: Any = None, top: Any = None, right: Any = None, bottom: Any = None) -> ShapeAnchor
Crop a floating picture in from its edges. left / top / right /
bottom are the amounts trimmed off each edge (lengths in points /
"0.2in"); cropping shrinks the displayed size. At least one edge is
required. Only valid on a "picture" shape; raises OpError on a text
box / WordArt / group. Returns self (chainable).
Source code in src/wordlive/_anchors/_shape_anchors.py
set_position ¶
Reposition the shape. left / top are lengths (points / "2in") or
"center"; relative_to is the frame they're measured from
("margin" (default) or "page"). Returns self. Bad input raises
OpError.
Source code in src/wordlive/_anchors/_shape_anchors.py
set_size ¶
Resize the shape. width / height are lengths (points / "3in");
lock_aspect toggles proportional scaling (dropped automatically when both
dimensions are given, so both stick). Returns self. Bad input raises
OpError.
Source code in src/wordlive/_anchors/_shape_anchors.py
set_rotation ¶
Rotate the shape clockwise by degrees (absolute angle, e.g. 30 or
-15). Returns self (chainable). Bad input raises OpError.
Source code in src/wordlive/_anchors/_shape_anchors.py
set_z_order ¶
Restack the shape in the floating layer β "front" / "back" /
"forward" / "backward" (this is the stacking order among floats,
distinct from set_wrap's in-front-of / behind-text).
Note: Document.Shapes orders by z-order, so this renumbers shape:N β
the returned self keeps its old index and may now address a different
shape. Re-list (doc.shapes) before using a shape:N id again. Returns
self for chaining the call itself; bad input raises OpError.
Source code in src/wordlive/_anchors/_shape_anchors.py
set_text_frame ¶
set_text_frame(*, margin_left: Any = None, margin_right: Any = None, margin_top: Any = None, margin_bottom: Any = None, word_wrap: bool | None = None) -> ShapeAnchor
Set a text box's internal margins and word-wrap. margin_* are lengths
(points / "0.1in"); word_wrap toggles whether text wraps to the box
width. Only valid on a text box; raises OpError on a picture / WordArt /
group. Returns self (chainable).
Source code in src/wordlive/_anchors/_shape_anchors.py
format ¶
format(*, fill: Any = None, border: str | bool | None = None, border_weight: Any = None) -> ShapeAnchor
Set the shape's fill and outline. fill is any colour; border is
False (no outline), True (default), or a colour string;
border_weight is the outline thickness (points / "1.5pt"). Returns
self. Bad input raises OpError.
Source code in src/wordlive/_anchors/_shape_anchors.py
set_alt_text ¶
Set the shape's accessibility (alt) text. Returns self.
replace_image ¶
Swap this floating picture's image in place.
Delete + reinsert at the same anchor, preserving wrap / position / size /
alt text β image is a path, raw bytes, or a base64 string (like
insert_image). Only valid on a "picture" shape; raises OpError
otherwise, ImageSourceError for a bad image. Returns self (chainable);
wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_anchors/_shape_anchors.py
set_text ¶
Replace a text box's contents. Raises OpError on a shape with no text
frame (a picture / WordArt).
Source code in src/wordlive/_anchors/_shape_anchors.py
ungroup ¶
Dissolve a group shape into its members, returning their ShapeAnchors.
The children become top-level floating shapes again (each keeps its own
shape:N slot β re-list, don't cache). Only valid on a "group" shape;
raises OpError otherwise. Wrap in doc.edit(...) for atomic undo. The
inverse of Document.group_shapes.
Source code in src/wordlive/_anchors/_shape_anchors.py
delete ¶
wordlive.ShapeCollection ¶
Iterable view over the document's floating shapes (doc.shapes).
Index a shape by 1-based position (doc.shapes[2]) to get a
ShapeAnchor (shape:N); list() summarises every
shape β id, kind, size, wrap, and the para:N it's anchored in. Positions
follow document order over the body story (header-story watermarks excluded),
and renumber as shapes come and go β re-list, don't cache. The write mirror is
insert_text_box / a floating
insert_image.
Source code in src/wordlive/_anchors/_shape_anchors.py
list ¶
Every floating shape as {index, anchor_id, shape_type, name, width,
height, rotation, z_order, wrap, wrap_side, crop, alt_text, has_text,
para}.
shape_type is the kind string; width / height are points; rotation
the clockwise angle in degrees; z_order the 1-based stacking position;
wrap the text-wrap keyword and wrap_side which sides text flows past;
crop the picture's {left, top, right, bottom} insets in points (or
None); has_text whether a text frame holds text; para the para:N
the shape is anchored in.
Source code in src/wordlive/_anchors/_shape_anchors.py
wordlive.TextBoxCollection ¶
Iterable view over the document's text boxes (doc.text_boxes).
The shape_type == "text_box" subset of doc.shapes β a discovery filter, not a second id space: each
text box keeps its canonical shape:N id (its position among all floating
shapes), so doc.text_boxes[1].anchor_id may be e.g. shape:3. Index 1-based
over the text boxes; list() is the text-box rows of doc.shapes.list().
Source code in src/wordlive/_anchors/_shape_anchors.py
list ¶
The text-box rows of doc.shapes.list() (each keeping its shape:N id).
Equations¶
Mathematical equations as first-class anchors. The write side is
Anchor.insert_equation: it takes exactly one of three input
dialects β unicodemath= (Word's native linear form, e.g. "a^2+b^2=c^2",
zero-dependency), latex= (the optional latex extra does the LaTeXβMathML
hop), or mathml= (a <math> string) β converts it to Office Math, and places
it on its own paragraph with a pinned style so it never inherits a neighbouring
heading's style: display=True gives it the dedicated centred Equation
paragraph style (created on first use, based on Normal); display=False
resets the paragraph to Normal and left-aligns it (still its own paragraph,
not mid-sentence). It returns an EquationAnchor
addressed equation:N β a positional id in Word's OMaths order, so
inserting another equation before it renumbers it (re-list rather than caching
the id across further inserts). LaTeX and MathML travel LaTeXβMathMLβOMMLβWord through Office's own
shipped XSLT (MML2OMML.XSL), so only the LaTeXβMathML step needs a third-party
library; malformed input or a missing backend raises
EquationError.
doc.equations is the read side: a discovery collection whose list() reports
each equation's equation:N id, type (display/inline), a linear preview,
and the para:N it sits in. Index it (doc.equations[2]) for an
EquationAnchor, then read equation.mathml (a
non-mutating round-trip back to MathML via Office's OMML2MML.XSL) or
equation.linear. An equation has no plain text, so set_text raises β delete
and re-insert to change it.
wordlive.EquationAnchor ¶
Bases: Anchor
A mathematical equation located by 1-based index β equation:N.
Mirrors Word's own OMaths(N) ordering (document order). The anchor resolves
to the equation's range, so mathml round-trips it back to MathML (via
Office's own transform, without mutating the document) and linear reads its
UnicodeMath form. type is "display" or "inline". Create equations
with Anchor.insert_equation; discover
them via doc.equations. An equation isn't
plain text, so set_text raises β delete and re-insert to change it.
Source code in src/wordlive/_anchors/_equation_anchors.py
mathml
property
¶
The equation as MathML β a non-mutating read via Office's OMMLβMathML transform.
wordlive.EquationCollection ¶
Read-only, iterable view over the document's equations (doc.equations).
Index an equation by 1-based position (doc.equations[2]) to get an
EquationAnchor (equation:N), then mathml /
linear to read it. list() summarises every equation β id, type, a linear
preview, and the para:N it sits in. Positions match Word's own OMaths(n)
ordering. The write mirror is any anchor's
insert_equation.
Source code in src/wordlive/_anchors/_equation_anchors.py
list ¶
Every equation as {index, anchor_id, type, linear, para}.
type is "display" / "inline"; linear is the built-up text as
a compact preview (read EquationAnchor.mathml
for fidelity); para is the para:N the equation sits in (or None).
Reads no XML, so this is cheap to call over a whole document.
Source code in src/wordlive/_anchors/_equation_anchors.py
Charts¶
Excel-backed charts as first-class anchors. The write side is
Anchor.insert_chart: kind is "bar" (clustered
columns), "pie", "line", or "scatter", and data is either a {label:
value} mapping (for bar/pie/line) or an array of [x, y] pairs (for scatter β
both axes numeric, with duplicate/clustered x preserved as distinct points; line
accepts either). title= sets the chart title and series name. It returns a
ChartAnchor addressed chart:N β a positional id in
document order, so inserting another chart earlier renumbers it.
Charts embed a chart via InlineShapes.AddChart2, whose data lives in a hidden
Excel workbook β so Excel must be installed. A non-invasive registry probe
gates the insert and raises ExcelNotAvailableError
(CLI exit 6) before touching the document if Excel is absent. After populating
the data wordlive breaks the data link, so the chart's data is static: no
embedded workbook ships in the document, and the series data isn't read back
(which keeps the hidden Excel from orphaning). The Python API is ungated; the
CLI/MCP surfaces add the same Excel probe.
doc.charts is the read side: a discovery collection whose list() reports each
chart's chart:N id, kind, title, chart_style, has_legend, and the
para:N it sits in (metadata only). Index it (doc.charts[2]) for a
ChartAnchor, then read chart.chart_type /
chart.title / chart.chart_style / chart.has_legend. A chart has no plain
text, so set_text raises β delete and re-insert to change the data.
Formatting & design¶
The chart's appearance β Word's "Design" and "Format" tabs β is a curated set of
methods on ChartAnchor. They operate on the
post-insert, static chart, so no Excel is involved (and no
ExcelNotAvailableError); every field is tri-state (only what you pass is
written), and each method returns self so they chain:
doc.charts[1].format(
title="Quarterly revenue", legend=True, legend_position="bottom",
chart_style=242, background="#F4F6F7", data_labels=True,
).set_axis("value", title="USD (M)", minimum=0, maximum=30, scale="log")
scatter = doc.charts[2]
scatter.add_trendline(kind="power", display_equation=True) # draws the law of best fit
scatter.set_series_color("#2E86C1") # or point=N for one bar / pie slice
scatter.format_series(marker="circle", marker_size=8, smooth=True) # markers + smoothed line
doc.charts[1].add_error_bars(kind="percent", amount=5) # Β± error bars on the value axis
format(...)β whole-chart/design:title(Noneclears),legend+legend_position,chart_style(design-gallery int),background/plot_backgroundfills,font/font_size/font_color,data_labels+data_label_format,chart_typeto re-type the chart in place, plusgap_width/overlap(bar spacing) anddata_table(the grid beneath the plot).set_axis(which, ...)βwhichis"value"/"y"or"category"/"x"; setstitle,minimum/maximum,scale("linear"/"log"),number_format,gridlines.add_trendline(...)βkindβ linear/exponential/logarithmic/ moving_average/polynomial/power on a 1-basedseries, withdisplay_equation/display_r_squared,forward/backwardforecast, andorder(polynomial degree 2β6) /period(moving-average window).set_series_color(color, *, series=1, point=None)β recolour a whole series, or one 1-basedpoint(bar / pie slice / marker).coloris a name, hex, or(r, g, b).format_series(*, series=1, point=None, ...)β markers (markerglyph name orXlMarkerStyleint,marker_size), linesmooth, pieexplosion, and per-series/point data labels (data_labels,data_label_size,data_label_color).pointnarrows marker / explosion / label to one point.add_error_bars(*, series=1, kind="fixed", amount=None, include="both", axis="y")β drawfixed/percent/stdev/sterrorerror bars;amountis required for all kinds butsterror(which Word computes).
Bad input (unknown colour / scale / trendline kind / marker, or an error-bar
kind missing its amount) raises OpError. Wrap calls in
doc.edit(...) for atomic undo.
wordlive.ChartAnchor ¶
Bases: Anchor
An Excel-backed chart located by 1-based index β chart:N.
Indexes the document's chart inline shapes in document order. The anchor
resolves to the chart's range, so it inherits apply_style / formatting like
any anchor. chart_type reports the kind string ("bar" / "pie" /
"line" / "scatter") and title the chart title β metadata only:
charts are inserted with a broken data link, so the underlying series data is
static and isn't read back. Create charts with
Anchor.insert_chart; discover them via
doc.charts. A chart isn't plain text, so
set_text raises.
Source code in src/wordlive/_anchors/_chart_anchors.py
chart_type
property
¶
The chart kind β "bar" / "pie" / "line" / "scatter" (or the raw int).
format ¶
format(*, title: Any = _charts._UNSET, legend: bool | None = None, legend_position: str | None = None, chart_style: int | None = None, background: Any = None, plot_background: Any = None, font: str | None = None, font_size: Any = None, font_color: Any = None, data_labels: bool | None = None, data_label_format: str | None = None, chart_type: str | None = None, gap_width: int | None = None, overlap: int | None = None, data_table: bool | None = None) -> ChartAnchor
Apply whole-chart / design formatting β Word's chart "Design" tab.
All kwargs are optional and tri-state; only the ones you pass are written.
title=None clears the chart title (omit it to leave it). legend
toggles the legend; legend_position ("right"/"left"/"top"/
"bottom"/"corner") implies it's shown. chart_style is the built-in
design-gallery int. background / plot_background fill the chart and
plot areas; font / font_size / font_color set the whole-chart font.
data_labels toggles point labels on every series, data_label_format
is their number format. chart_type ("bar"/"pie"/"line"/
"scatter") re-types the chart in place. gap_width / overlap tune bar
spacing (bar/column charts only); data_table toggles the data-table grid
beneath the plot. Operates on the static chart β no Excel needed. Returns
self (chainable); wrap in doc.edit(...) for atomic undo. Bad input
raises OpError.
Source code in src/wordlive/_anchors/_chart_anchors.py
set_axis ¶
set_axis(which: str, *, title: Any = _charts._UNSET, minimum: Any = None, maximum: Any = None, scale: str | None = None, number_format: str | None = None, gridlines: bool | None = None) -> ChartAnchor
Format one axis. which is "value"/"y" or "category"/"x".
Tri-state: title=None clears the axis title; minimum/maximum set the
scale bounds; scale is "linear" or "log" (log is ideal for
order-of-magnitude data); number_format is the tick-label format string;
gridlines toggles major gridlines. Returns self. Bad input raises
OpError.
Source code in src/wordlive/_anchors/_chart_anchors.py
add_trendline ¶
add_trendline(*, series: int = 1, kind: str = 'linear', display_equation: bool = False, display_r_squared: bool = False, forward: Any = None, backward: Any = None, order: int | None = None, period: int | None = None) -> ChartAnchor
Fit a trendline to a series (1-based series).
kind is "linear", "exponential", "logarithmic",
"moving_average", "polynomial", or "power". display_equation
/ display_r_squared annotate the fit β a power/exponential fit with the
equation literally draws the law of best fit. forward / backward
extend the line that many units past the data. order is the polynomial
degree (2β6, with kind="polynomial"); period is the moving-average
window (with kind="moving_average"). Returns self. Bad input raises
OpError.
Source code in src/wordlive/_anchors/_chart_anchors.py
set_series_color ¶
Recolour a whole series, or a single 1-based point (bar / pie slice).
color is a named colour, hex ("#2E86C1"), or (r, g, b). Omit point
to colour the entire series; pass it to vary one bar / slice / marker.
Sets the line/marker colour too where the series has one (line/scatter).
Returns self. Bad input raises OpError.
Source code in src/wordlive/_anchors/_chart_anchors.py
format_series ¶
format_series(*, series: int = 1, point: int | None = None, marker: Any = None, marker_size: int | None = None, smooth: bool | None = None, explosion: int | None = None, data_labels: bool | None = None, data_label_size: Any = None, data_label_color: Any = None) -> ChartAnchor
Format one series, or a single 1-based point within it.
marker is a glyph name ("circle"/"square"/"diamond"/
"triangle"/"x"/"star"/"dot"/"dash"/"plus"/
"none"/"auto") or a raw XlMarkerStyle int, with marker_size
(2β72) β both for line/scatter. smooth curves a line/scatter through its
points. explosion (0β400) pulls a pie slice out. data_labels toggles
this series' point labels; data_label_size / data_label_color style
their font. With point set, marker / explosion / the data-label font
target that one point; marker_size / smooth / the data_labels toggle
stay series-wide. Returns self. Bad input raises OpError.
Source code in src/wordlive/_anchors/_chart_anchors.py
add_error_bars ¶
add_error_bars(*, series: int = 1, kind: str = 'fixed', amount: Any = None, include: str = 'both', axis: str = 'y') -> ChartAnchor
Draw error bars on a series (1-based series).
kind is "fixed" (an absolute amount), "percent" (of each value),
"stdev" (multiples of the standard deviation), or "sterror" (the
standard error β Word computes it, so amount is ignored). amount is the
magnitude (required for all kinds but "sterror"). include is which
side(s) to draw ("both" / "plus" / "minus"); axis is
"y"/"value" (the usual) or "x"/"category" for scatter
x-uncertainty. Returns self. Bad input raises OpError.
Source code in src/wordlive/_anchors/_chart_anchors.py
wordlive.ChartCollection ¶
Read-only, iterable view over the document's charts (doc.charts).
Index a chart by 1-based position (doc.charts[2]) to get a
ChartAnchor (chart:N); list() summarises every
chart β id, kind, title, and the para:N it sits in. Positions follow
document order. Metadata only β charts are inserted with their data link
broken (static data), so reading the series back is deferred. The write
mirror is any anchor's insert_chart.
Source code in src/wordlive/_anchors/_chart_anchors.py
list ¶
Every chart as {index, anchor_id, kind, title, chart_style, has_legend, para}.
kind is the chart-type string; title the chart title (or None);
chart_style the design-gallery id; has_legend whether a legend shows;
para the para:N the chart sits in. Touches only ChartType /
ChartTitle / ChartStyle / HasLegend (never the series data), so it's
cheap and Word-stable.
Source code in src/wordlive/_anchors/_chart_anchors.py
References, linking & layout¶
Notes and the TOC family, cross-references, durable pins, hyperlinks, and section layout.
Footnotes, endnotes & TOC¶
Notes and the table of contents are reference structures built from anchors.
anchor.insert_footnote(text) / insert_endnote(text) drop a reference mark at
the anchor and put the body in the note story; they return a
Footnote / Endnote addressed
footnote:N / endnote:N, so note.set_text(...) edits the body and
note.delete() removes the mark and body together. Discover existing notes with
doc.footnotes / doc.endnotes (read-only collections whose list() reports
each note's number, text, and the para:N it's anchored at).
anchor.insert_toc(levels=(1, 3), use_heading_styles=True, hyperlinks=True)
inserts a table of contents over the document's headings and returns a
Toc; doc.add_toc(...) is the sugar for placing one at the
document start. A TOC's page numbers only populate after repagination β call
toc.update(), Document.update_fields(), or take a
snapshot (which forces print layout) before reading them.
anchor.insert_table_of_figures(label="Figure", include_label=True, hyperlinks=True,
right_align_page_numbers=True) is the same field-block pattern over the captions
wordlive inserts: it lists every caption of one label ("Figure"/"Table"/
"Equation"/custom) with its page number and returns a
TableOfFigures with update() / update_page_numbers().
A back-of-book index is two steps. anchor.mark_index_entry(entry,
cross_reference=β¦, bold=β¦, italic=β¦) marks the anchor's range as an XE index
field β entry uses "main:sub" to nest a subentry β then
anchor.insert_index(columns=2, run_in=False, right_align_page_numbers=False)
builds the index from those marks and returns an Index;
doc.add_index(...) is the sugar for one at the document end. Like the TOC, the
TableOfFigures and Index are field blocks: their page numbers populate only
after repagination (update(), update_fields(), or a snapshot).
Citations and a bibliography are a source-then-cite-then-build workflow.
doc.sources is a SourceCollection over the
document's master source list: doc.sources.add(source_type="book", author=β¦,
title=β¦, year=β¦, β¦) registers a Source (source_type is
"book" / "journal_article" / "web_site" / "case" / β¦ β author is
"Last, First" or a list, and tag auto-derives from the first author's surname +
year when omitted), doc.sources.add_xml("<b:Source>β¦") is the raw-OOXML escape
hatch, and the collection is list/index/in/len-able by tag. doc.bibliography_style
is the read/write style property ("APA" / "MLA" / "Chicago" / "IEEE" /
"Turabian", build-dependent β an unsupported value raises
OpError). anchor.insert_citation(tag, pages=β¦,
suppress_author=β¦, β¦) inserts an in-text CITATION field rendering per that
style (e.g. (Smith 2020, 15)) and returns a Citation β a
tag with no registered source still inserts but renders "Invalid source
specified.". anchor.insert_bibliography() inserts the works-cited block and
returns a Bibliography; doc.add_bibliography() is the
sugar for one at the document end.
A table of authorities (the legal-citation index) is the same two-step,
mark-then-build pattern as the back-of-book index.
anchor.mark_citation(long_citation, short_citation=β¦, category="cases") marks the
anchor's range as a TA field (category is "cases" / "statutes" / "other"
/ β¦ or an int 1β16; short_citation defaults to long_citation), then
anchor.insert_table_of_authorities(category="all", passim=True,
keep_entry_formatting=True) builds the table from those marks and returns a
TableOfAuthorities; doc.add_table_of_authorities(...)
is the sugar for one at the document end. Like the TOC, the Bibliography and
TableOfAuthorities are field blocks: their entries and page numbers populate only
after repagination (update(), update_fields(), or a snapshot). (Word's
table of authorities has no per-field page-number refresh, so unlike the TOC and
TableOfFigures there's no update_page_numbers() β use a full update().)
The document theme is the document-wide brand primitive β the colour scheme,
font scheme, and effects the Design tab drives. doc.theme is a
DocumentTheme: doc.theme.apply("Facet") applies a
whole theme by built-in name (see doc.theme.list_available()) or .thmx path;
doc.theme.set_colors(scheme="Blue", accent1="#1A73E8", text1="navy") loads a named
colour scheme and/or overrides individual brand colours (keys text1 /
background1 / text2 / background2 / accent1βaccent6 / hyperlink /
followed_hyperlink, values a colour name / hex / (r, g, b)); and
doc.theme.set_fonts(scheme="Garamond", major="Arial", minor="Calibri") sets the
heading/body fonts. doc.theme.colors / .major_font / .minor_font /
.to_dict() read the current theme back. Wrap theme mutations in doc.edit(...).
wordlive.Footnote ¶
wordlive.Endnote ¶
wordlive.FootnoteCollection ¶
wordlive.EndnoteCollection ¶
wordlive.Toc ¶
A table of contents created by insert_toc / add_toc.
Source code in src/wordlive/_toc.py
update ¶
Rebuild the TOC's entries and page numbers from the current document.
Call this after edits that change headings or pagination β or use
Document.update_fields to refresh
the TOC together with every other field. Wrap in doc.edit(...) for
atomic undo.
Source code in src/wordlive/_toc.py
update_page_numbers ¶
wordlive.TableOfFigures ¶
A table of figures created by insert_table_of_figures.
The caption-driven sibling of Toc: it lists every caption
of one label ("Figure" / "Table" / "Equation" / a custom label)
with its page number, built over Word's TablesOfFigures. Like a TOC it is a
field block, not a single addressable range β refresh it with update() /
update_page_numbers(), or with Document.update_fields(). Page numbers
populate only after repagination.
Source code in src/wordlive/_toc.py
wordlive.Index ¶
A back-of-book index created by insert_index / add_index.
Source code in src/wordlive/_index.py
update ¶
Rebuild the index's entries and page numbers from the marked entries.
Call this after adding or moving XE marks β or use
Document.update_fields to refresh the
index together with every other field. Wrap in doc.edit(...) for atomic
undo.
Source code in src/wordlive/_index.py
wordlive.Source ¶
One bibliography source in doc.sources, addressed by its tag.
Source code in src/wordlive/_sources.py
delete ¶
wordlive.Citation ¶
An in-text citation field created by insert_citation.
Source code in src/wordlive/_citations.py
update ¶
wordlive.Bibliography ¶
A generated bibliography field created by insert_bibliography / add_bibliography.
Source code in src/wordlive/_citations.py
update ¶
Rebuild the reference list from the cited sources.
Call this after adding citations or changing
Document.bibliography_style β or
use Document.update_fields to refresh
it with every other field. Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_citations.py
wordlive.TableOfAuthorities ¶
A table of authorities created by insert_table_of_authorities.
Source code in src/wordlive/_toa.py
update ¶
Rebuild the table from the marked TA citations.
Call this after adding or moving citation marks β or use
Document.update_fields to refresh it
with every other field. Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_toa.py
wordlive.DocumentTheme ¶
The document's theme β doc.theme.
A read/mutate view over Word's OfficeTheme: read the current colours/fonts,
apply(...) a whole theme, or set_colors(...)/set_fonts(...) to brand a
document. Mutations should be wrapped in doc.edit(...) for atomic undo and
to preserve the user's selection/scroll.
Source code in src/wordlive/_themes.py
colors
property
¶
The 12 theme colours as {friendly_name: "#RRGGBB"}.
Keys are text1, background1, text2, background2, accent1β
accent6, hyperlink, followed_hyperlink.
set_colors ¶
Set the theme's colour scheme and/or individual brand colours.
scheme loads a named built-in colour scheme ("Blue", "Orange",
β¦) or a Theme-Colors .xml path; then each keyword override
(accent1="#1A73E8", text1="navy", β¦ β friendly names from
theme.colors) is applied. Colour values take any form to_bgr accepts
(a colour name, a hex string, or an (r, g, b) tuple).
Wrap in doc.edit(...). Unknown scheme name or colour key/value raises
OpError. Returns the resulting colors dict.
Source code in src/wordlive/_themes.py
set_fonts ¶
set_fonts(scheme: str | None = None, *, major: str | None = None, minor: str | None = None) -> dict[str, str]
Set the theme's fonts via a named scheme and/or explicit names.
scheme loads a named built-in font scheme ("Garamond", "Arial",
β¦) or a Theme-Fonts .xml path; then major (heading font) and
minor (body font) override individual names. Wrap in doc.edit(...).
An unknown scheme name raises OpError. Returns
{"major_font", "minor_font"}.
Source code in src/wordlive/_themes.py
apply ¶
Apply a whole document theme (colours + fonts + effects).
theme is a built-in name ("Facet", "Ion", β¦ β see
doc.theme discovery via the list-themes surfaces) or a .thmx
file path. Wrap in doc.edit(...) for atomic undo. An unknown name
raises OpError. Returns the resolved theme display name.
Source code in src/wordlive/_themes.py
list_available ¶
The built-in themes, colour schemes, and font schemes Office ships.
Returns {"themes": [...], "color_schemes": [...], "font_schemes":
[...]} (names without extension) β the values apply, set_colors,
and set_fonts accept. Empty lists if the library directory is absent.
Source code in src/wordlive/_themes.py
to_dict ¶
The current theme as {colors, major_font, minor_font}.
Anchoring & linking¶
Create a named anchor, then point at it. doc.bookmarks.add(name, anchor)
creates a bookmark over an anchor's range (the name is validated against
Word's rules first) β the prerequisite for internal navigation. anchor.link_to(
address=β¦) makes the anchor an external hyperlink (URL / mailto: / file
path); anchor.link_to(bookmark=β¦) makes it an internal jump to a bookmark.
With text=None the anchor's existing range becomes the link; text=β¦ instead
inserts new linked text at the end of the range (so a heading keeps its content). anchor.insert_cross_reference(
target, kind=β¦) inserts a reference to another anchor β target is a
bookmark:NAME, heading:N, footnote:N, or endnote:N id, and kind is
"text" / "page" / "number" / "above_below". anchor.insert_caption(
label="Figure", text=β¦, position=None) adds an auto-numbered caption in its own
Caption-styled paragraph (never fused into the target); position is
"above"/"below", defaulting to above for a Table and below otherwise, and
on a table cell the caption attaches to the whole table. Pair it with a
cross-reference for "see Figure 2". Cross-references and TOC/page-number fields
go stale when the document shifts β refresh them with
Document.update_fields().
Content controls are the structured-document fill-in fields (the
read/write side is doc.content_controls["NAME"]). anchor.insert_content_control(
kind="rich_text", title=β¦, tag=β¦, items=β¦, where="wrap", lock_contents=False,
lock_control=False) creates one and returns the
ContentControl: where="wrap" (default) surrounds the
anchor's existing range β e.g. a range:START-END from find β and "before" /
"after" insert a fresh empty control. kind is rich_text (default) / text /
picture / combo_box / dropdown / date / checkbox / building_block /
group / repeating_section; items (combo_box/dropdown only) is a list of
strings or {"text": β¦, "value": β¦} dicts; lock_contents stops edits to the
value and lock_control stops deletion. A title (or, failing that, a tag)
names the control so it's addressable later as cc:TITLE; the returned wrapper
works even unnamed. doc.content_controls.add(anchor, kind=β¦, **kwargs) takes an
Anchor or an anchor-id string.
A control's metadata is editable in place β no delete + reinsert.
cc.set_properties(title=β¦, tag=β¦, lock_contents=β¦, lock_control=β¦) re-sets the
labels and locks (tri-state: omit to leave, None/"" to clear title/tag; a
rename changes the cc:NAME anchor id), and cc.set_items([...]) replaces a
combo_box/dropdown's choice list. Both are chainable and raise OpError on a
wrong-kind control or bad input.
wordlive.BookmarkCollection ¶
Indexable view over a document's bookmarks.
list() and iteration return only user-visible bookmarks. Word's hidden
bookmarks (_Toc..., _Ref..., etc.) are filtered out by default; address
them by their exact name through bookmarks[name] if you need them.
Source code in src/wordlive/_anchors/_bookmarks.py
add ¶
Create a bookmark named name over anchor's range and return it.
anchor is an Anchor or an anchor id string
(resolved via doc.anchor_by_id). name is validated against Word's
rules β it must start with a letter and contain only letters, digits, and
underscores (no spaces), max 40 characters β and an invalid name raises
OpError before anything is created. Adding a bookmark with an existing
name moves it to the new range (Word's own behaviour). This is the
prerequisite for internal hyperlinks
(Anchor.link_to) and cross-references
(Anchor.insert_cross_reference).
Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_anchors/_bookmarks.py
list ¶
Names of every user-visible bookmark in document order.
Set include_hidden=True to also return Word's internal bookmarks
(TOC entries, cross-references, etc.) whose names start with _.
Source code in src/wordlive/_anchors/_bookmarks.py
Durable handles (pins)¶
Positional para:N / heading:N ids renumber when a structural edit shifts the
document. When a positional anchor misses, AnchorNotFoundError.hint says why
(out-of-range vs body-text-not-a-heading, the paragraph count, the nearest
heading) and recommends pinning. doc.pin(anchor, name=None) (alias
doc.stamp) plants a hidden bookmark over an anchor's range and returns a
pin:<code> id β random, or a readable slug via name="budget-intro" β that
Word keeps attached to the same content across inserts / deletes / edits;
resolve it with doc.anchor_by_id("pin:β¦") like any anchor (a deleted target's
pin vanishes). doc.pin_outline(levels=β¦) pins every heading in one call and
returns the {anchor_id: pin} map (idempotent β reuses a heading's existing
handle), and doc.outline(pin=True) adds a pin to each outline row. Wrap pin
calls in doc.edit(...) for atomic undo. In an exec batch, bind: "name" on
an insert op mints a pin on the new content, and $ops[N].field references an
earlier op's output (see CLI / MCP). The methods
are Document.pin / stamp, pin_outline, and
outline(pin=β¦).
Hyperlinks & fields¶
Document.hyperlinks and Document.fields are discovery collections β the read
mirrors of Anchor.link_to /
Anchor.insert_field. doc.hyperlinks.list() reports each
link's visible text, external address or internal sub_address bookmark,
screen tip, and a range:START-END / para:N; doc.fields.list() reports each
field's kind (the code's leading keyword β PAGE / REF / TOC / β¦), raw
code, rendered result, locked, and a range:START-END / para:N. Index
either (doc.hyperlinks[2], doc.fields[2]) for the single-item wrapper.
Hyperlinks are also editable in place β no delete + reinsert. On the indexed
Hyperlink, h.update(address=β¦, sub_address=β¦, text=β¦,
screen_tip=β¦) (or the individual set_address / set_sub_address / set_text /
set_screen_tip) retargets or relabels the link; omitted fields are left
untouched, the setters are chainable, and address / sub_address stay
orthogonal. They retarget, they don't unlink: sub_address / screen_tip
clear with "", but Word keeps every link pointing somewhere with visible text,
so address / text can't be emptied (raises OpError). Fields remain
read-only.
wordlive.HyperlinkCollection ¶
wordlive.Hyperlink ¶
A single hyperlink, located by its 1-based document index.
Source code in src/wordlive/_hyperlinks.py
com
property
¶
Raw COM Hyperlink object β escape hatch (Follow, Delete, sub-ranges, β¦).
address
property
¶
The external destination (URL / mailto / file path), or "" for an internal link.
sub_address
property
¶
The in-document target β a bookmark name for an internal jump, else "".
to_dict ¶
{index, text, address, sub_address, screen_tip, anchor_id, para} β the list() shape.
anchor_id is a range:START-END over the link's range; para is the
para:N the link sits in (or None). For an internal link address
is empty and sub_address holds the bookmark name it points at.
Source code in src/wordlive/_hyperlinks.py
update ¶
update(*, address: str | None = None, sub_address: str | None = None, text: str | None = None, screen_tip: str | None = None) -> Hyperlink
Retarget / relabel this link in place β no delete + reinsert.
Pass a string to set a field; omit it (or pass None) to leave it.
address is the external destination (URL / mailto / file path);
sub_address is the in-document target (a bookmark name); text is the
visible clickable text; screen_tip is the hover tooltip. address and
sub_address stay orthogonal β setting one does not clear the other.
These setters retarget, they don't unlink. sub_address and
screen_tip can be emptied with "", but Word keeps every link
pointing somewhere with visible text, so address and text cannot
be cleared (passing "" raises OpError β delete the link via .com
to remove it). Returns self (chainable); wrap in doc.edit(...) for
atomic undo. Bad input raises OpError.
Source code in src/wordlive/_hyperlinks.py
set_address ¶
set_sub_address ¶
set_text ¶
wordlive.FieldCollection ¶
Indexable, iterable, read-only view over a document's fields (doc.fields).
Scope is the main text story (doc.Fields); fields that live only in
headers/footers are reached through the section's header/footer range on the
.com escape hatch for now.
Source code in src/wordlive/_fields.py
list ¶
wordlive.Field ¶
A single field, located by its 1-based document index.
Source code in src/wordlive/_fields.py
to_dict ¶
{index, kind, type, code, result, locked, anchor_id, para} β the list() shape.
kind is the leading code keyword; type is Word's numeric field type;
anchor_id is a range:START-END over the field; para is the para:N
it sits in (or None).
Source code in src/wordlive/_fields.py
Sections, headers & footers¶
Document.sections is a SectionCollection. Each
Section reaches its headers and footers as
HeaderFooter anchors β doc.sections[1].header() /
.footer("first") β addressed header:S:WHICH / footer:S:WHICH (WHICH is
primary / first / even). A HeaderFooter is an Anchor, so
set_text, apply_style, and format_paragraph work on it like any other, plus
insert_page_number() sugar for a { PAGE } field. Section.set_page_setup(...)
is the write mirror of page_setup() β margins, orientation, paper size, gutter,
and multi-column layout (columns=N), per section.
wordlive.SectionCollection ¶
Indexable, iterable view over a document's sections (doc.sections).
Index by 1-based position (doc.sections[1]). Every document has at least
one section; doc.sections[1].header() is the common entry point.
Source code in src/wordlive/_sections.py
wordlive.Section ¶
Wraps a Word Section, located by its 1-based document position.
Source code in src/wordlive/_sections.py
header ¶
The section's header for which (primary / first / even).
footer ¶
The section's footer for which (primary / first / even).
page_setup ¶
Read-only {orientation, *_margin, page_width, page_height} in points.
Source code in src/wordlive/_sections.py
set_page_setup ¶
set_page_setup(*, margins: Any = None, top_margin: Any = None, bottom_margin: Any = None, left_margin: Any = None, right_margin: Any = None, gutter: Any = None, orientation: Any = None, paper_size: Any = None, columns: int | None = None, column_spacing: Any = None) -> None
Set this section's page geometry β the write mirror of page_setup().
All kwargs are optional and tri-state; only the ones passed are written.
margins sets all four margins at once; the per-side *_margin kwargs
override it. Lengths (margins, *_margin, gutter, column_spacing)
are in points or a unit string ("1in", "2.5cm"). orientation is
"portrait" / "landscape"; paper_size is "letter" / "legal" /
"tabloid" / "a3" / "a4" / "a5" (setting it resizes the page).
columns (an int β₯ 1) lays the section out in that many equal,
newspaper-style columns β the section counterpart to
insert_break("column") β with
column_spacing as the gap between them.
Per-section: doc.sections[N].set_page_setup(...); for a single-section
document doc.sections[1] is the whole document. Bad input raises
OpError. Wrap in doc.edit(...) for atomic undo.
Deferred: unequal column widths, line numbering, vertical alignment, and different-first-page toggles.
Source code in src/wordlive/_sections.py
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | |
to_dict ¶
wordlive.HeaderFooter ¶
Bases: Anchor
A section's header or footer, addressed as header:S:WHICH / footer:S:WHICH.
Subclasses Anchor, so text, set_text, insert_before/after,
apply_style, and format_paragraph all work unchanged β only the COM
range and anchor id are overridden here. WHICH is primary, first, or
even.
Source code in src/wordlive/_sections.py
exists
property
¶
Whether this header/footer actually has content defined for the section.
linked_to_previous
property
¶
Whether this header/footer inherits from the previous section's.
insert_page_number ¶
Insert a { PAGE } page-number field into this header/footer.
Sugar for insert_field("page") β the
canonical place for a page number. Combine with a literal "Page " /
" of " and an insert_field("numpages") for a "Page X of Y" footer.
Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_sections.py
Review & track changes¶
Comments and tracked-change recording, reading, and resolution.
Comments¶
Document.comments is a CommentCollection.
comments.add(anchor, text, author=...) attaches a review comment to any
anchor's range without changing the text β the polite, side-channel way for
an agent to flag something. Existing comments are addressed by 1-based index
(doc.comments[2]) to resolve() or delete().
wordlive.CommentCollection ¶
Indexable, iterable view over a document's review comments.
Source code in src/wordlive/_comments.py
add ¶
Attach a new comment to anchor's range.
anchor is any wordlive anchor (bookmark, heading, cell, range, β¦); its
COM range becomes the comment's scope and the document text is left
untouched β only an annotation is added. Returns the new Comment.
Source code in src/wordlive/_comments.py
wordlive.Comment ¶
A single review comment, located by its 1-based document index.
Source code in src/wordlive/_comments.py
scope_text
property
¶
The document text the comment is attached to (its anchored range).
resolve ¶
reopen ¶
delete ¶
to_dict ¶
{index, author, text, scope, done} β the JSON shape list() emits.
Source code in src/wordlive/_comments.py
Track Changes¶
Document.tracked_changes() is a context manager that turns Word's Track
Changes on for the scope and restores the prior setting on exit β pair it with
edit() to make a batch of edits visibly, as revisions the user can accept or
reject. Document.track_changes is the underlying read/write property for the
persistent flag. Both are documented on Document.
Document.revisions is a RevisionCollection
that reads those tracked changes back as structured data β the way to see what
a tracked batch recorded. revisions.list() reports each change as
{index, type, author, text, anchor_id, start, end, date}, where type is
"insert" / "delete" / "format" / β¦ . The visual counterpart is
snapshot(markup="all") (see Snapshots).
Resolve them, too: revisions[N].accept() / .reject() make a single change
permanent / undo it (and renumber the rest), while
revisions.accept_all(within=anchor) / reject_all(within=anchor) do the whole
document β or just one anchor's range when within is given β and return the
count resolved.
For a read that separates a tracked edit's two sides, the Anchor
helpers text_final (as if accepted), text_original (as if rejected), and
revision_segments() (the ordered {text, change} breakdown) reconstruct both:
Word's plain text read is the final view (inserted runs present, deleted runs
gone), so the original wording lives only on the delete revisions.
wordlive.RevisionCollection ¶
Indexable, iterable, read-only view over a document's tracked changes (doc.revisions).
Source code in src/wordlive/_revisions.py
accept_all ¶
Accept every tracked change at once and report how many were resolved.
With no within, accepts the whole document; pass any anchor (heading,
section range, cell, range:START-END, β¦) as within to accept only the
tracked changes inside that range β "accept all my edits in this section".
Returns the count accepted (read before the operation, since accepting
empties the collection). Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_revisions.py
reject_all ¶
Reject every tracked change at once and report how many were resolved.
The mirror of accept_all:
whole-document by default, or scoped to within's range. Returns the
count rejected. Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_revisions.py
list ¶
All tracked changes as {index, type, author, text, anchor_id, start, end, date} dicts.
wordlive.Revision ¶
A single tracked change, located by its 1-based document index.
Source code in src/wordlive/_revisions.py
type
property
¶
The revision kind: "insert", "delete", "format", β¦ ("other" if unknown).
date
property
¶
When the revision was made, ISO-8601 β None if Word doesn't report it.
accept ¶
Accept this tracked change β make it permanent.
For an insertion the inserted text stays and loses its revision mark; for
a deletion the struck-through text is removed. Accepting renumbers the
remaining revisions (this one is consumed), so cached doc.revisions[N]
indices past it shift down by one β re-list between resolves, or use the
bulk accept_all. Wrap in
doc.edit(...) for atomic undo.
Source code in src/wordlive/_revisions.py
reject ¶
Reject this tracked change β undo it.
For an insertion the inserted text is removed; for a deletion the
struck-through text is restored. Like accept
this consumes the revision and renumbers the rest. Wrap in doc.edit(...)
for atomic undo.
Source code in src/wordlive/_revisions.py
to_dict ¶
{index, type, author, text, anchor_id, start, end, date} β the list() shape.
anchor_id is a range:START-END over the revision's run (so a hit can
be fed back into read/comments.add); text is the inserted or
deleted text.
Source code in src/wordlive/_revisions.py
Inspecting, exporting & verifying¶
Metadata and proofing, linting, Markdown / HTML export, checkpoints, and snapshots.
Document metadata, variables & proofing¶
Document.properties is a read/write PropertyCollection
over the document's metadata: read() returns {builtin, custom} (the Title /
Author / Subject / Keywords / β¦ bag plus any custom name/value pairs), set(name,
value) writes a built-in property, set(name, value, custom=True) a custom one,
and delete(name) removes a custom one. Document.variables is a
VariableCollection over the invisible named
string storage that backs { DOCVARIABLE } fields β list() returns
{name: value}; set / get / delete manage them. Wrap writes in
doc.edit(...) for atomic undo.
Document.proofing() runs Word's proofing tools and returns
{spelling, grammar, readability}: spelling/grammar each give a count plus a
(capped) list of {text, anchor_id, para} for the flagged runs, and readability
gives the Flesch Reading Ease, Flesch-Kincaid Grade Level, passive-sentence %, and
averages. It's a pure read but heavier than stats() β it
asks Word to (re)check the document. Documented on Document.
wordlive.PropertyCollection ¶
Read/write view over a document's built-in and custom properties.
doc.properties.read() returns {"builtin": {β¦}, "custom": {β¦}}. Write a
built-in property with set("Title", "β¦") and a custom one with
set("Project", "Apollo", custom=True) (created if it doesn't exist). The
built-in stat properties (word count, creation date, β¦) are read-only;
Word raises if you try to set one, surfaced as an OpError.
Source code in src/wordlive/_properties.py
builtin ¶
The built-in properties that carry a value, as {name: value}.
custom ¶
The custom (user-defined) properties, as {name: value}.
read ¶
get ¶
Look up one property's value by name (built-in first, then custom).
Raises AnchorNotFoundError (kind "property") if no built-in or custom
property of that name carries a value.
Source code in src/wordlive/_properties.py
set ¶
Set property name to value (a built-in by default, or a custom one).
With custom=False (default) this writes a built-in property β the
writable ones are Title, Subject, Author, Keywords, Comments, Category,
Manager, Company, Content status, and Hyperlink base; the stat/date
properties are read-only and raise OpError. With custom=True it sets
the custom property, creating it if absent (the type is inferred from
value: bool/int/float/str). Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_properties.py
delete ¶
Delete a custom property by name.
Only custom properties can be removed β built-in ones are part of the
format. Raises AnchorNotFoundError (kind "property") if no custom
property of that name exists. Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_properties.py
wordlive.VariableCollection ¶
Read/write view over a document's variables (doc.variables).
doc.variables.list() returns {name: value}. set(name, value) creates or
updates a variable; get(name) reads one; delete(name) removes it. Values
are stored as strings. Wrap writes in doc.edit(...) for atomic undo.
Source code in src/wordlive/_variables.py
list ¶
Every variable as a {name: value} dict (Word's Variables order).
Source code in src/wordlive/_variables.py
get ¶
Read one variable's value by name.
Raises AnchorNotFoundError (kind "variable") if no variable of that
name exists.
Source code in src/wordlive/_variables.py
set ¶
Create or update variable name with value (stored as a string).
Word's Variables.Add errors on a name that already exists, so an
existing variable is updated in place and a new one is added. Wrap in
doc.edit(...) for atomic undo.
Source code in src/wordlive/_variables.py
delete ¶
Delete variable name.
Raises AnchorNotFoundError (kind "variable") if it doesn't exist.
Wrap in doc.edit(...) for atomic undo.
Source code in src/wordlive/_variables.py
Linting & regularizing¶
New to the linter? The Linting & regularizing guide has the mental model, a guided walkthrough, and the full rule catalog; this section is the API-level reference.
Document.lint(rules=None, within=None) audits the document for formatting
inconsistency, structural slips, and policy breaches β the "what's off before I
hand this over" read. It returns a severity-ranked list of findings, each a dict
{rule, kind, severity, anchor_id, message, fixable, fix, observed, expected}
where kind is "consistency" / "structural" / "policy", severity is
"error" / "warning" / "info", and fix (present iff fixable) is an
op-shaped dict β literally the exec op regularize would
run. rules=None runs the default set (every on-by-default consistency +
structural rule; policy and opinionated rules are off); pass a list of rule
ids/tags to include just those, or {"exclude": [ids/tags]} to drop some. A rule
that's off by default still runs when named or via its tag β rules=["typography"]
lights up the whole typography cluster including its off-by-default members.
within scopes the audit to one anchor (heading:N / range:S-E /
table:N:R:C, or an Anchor). It's a pure read: layout rules
repaginate content-neutrally, leaving selection, scroll, and Saved untouched.
Foundation rules β structural: heading-keep-with-next, table-repeat-header,
list-numbering-continuity; consistency: heading-font-consistent,
heading-spacing-consistent, body-font-consistent (name/size/bold, body prose
only β table cells belong to table-style-consistent), and mixed-run-format
(report-only). Typography rules (tag typography) β on by default:
trailing-whitespace, leading-whitespace, space-before-punctuation,
double-space, manual-heading-formatting (report-only; skips table cells),
table-style-consistent; off by default: hyphen-as-range, em-dash-usage,
tabs-for-layout, manual-line-break. The fixable typography rules write via
find_replace's regex mode scoped to the offending paragraph, so they stay
idempotent.
Finalization rules (tag finalization, all off by default β an opt-in
"is-this-ready-to-send?" check): comments-present, unaccepted-revisions,
track-changes-on, hidden-text-present, and stale-fields (updatable
TOC/SEQ/REF/PAGE fields present β a refresh nudge) are report-only;
leftover-highlight is the one fixable rule (clears the highlight, idempotent).
Enable the cluster with rules=["finalization"].
Field-code rules (the P1 cross-reference/caption backbone) β on by default:
broken-cross-reference (a REF/PAGEREF field rendering Word's "reference
source not found" error) and caption-manual-numbering (a Caption paragraph
whose figure/table number is literal text, not a SEQ field); off by default
(tag layout): page-numbers-present (no PAGE field in any header/footer);
off by default (tag crossref / academia): xref-as-literal-text (a body
paragraph mentioning a figure/table by literal number with no REF field β
heuristic, so opt-in). All are report-only. The cross-reference/caption rules
carry the academia tag, so rules=["academia"] selects the cluster.
Hyperlink rules (a walk over doc.hyperlinks) β on by default:
hyperlink-broken-internal (an internal HYPERLINK \l jump whose target bookmark
no longer exists β a dead link); off by default (tags hyperlinks / print):
hyperlink-bare-for-print (an external link whose visible text doesn't contain its
URL, so the destination is invisible on paper) and hyperlink-display-is-raw-url
(a link whose whole label is a bare URL). All report-only. rules=["hyperlinks"]
selects the cluster; rules=["print"] selects just the two print/sharing rules.
Heading & document-structure rules (Β§B β a walk over doc.outline()) β on by
default: heading-level-skip (the outline jumps a level β an H1 followed by an H3
with no H2) and empty-heading (a heading paragraph with no text); off by default
(tags headings / structure): adjacent-headings (two headings in a row with no
body between), heading-numbering-manual (a heading numbered by hand, 3.1 Methods,
not by automatic numbering), heading-trailing-period (a heading ending in a period
β the one fixable rule, strips it in place), and toc-present-and-current (top-level
headings but no table-of-contents field β presence-only, since Word exposes no
field-staleness flag). rules=["structure"] (or rules=["headings"]) selects the
cluster.
Layout / document-level rules (Β§H β a walk over doc.sections / doc.properties
plus the doc.watermark() read), all off by default and report-only:
header-footer-consistent (the primary header/footer text disagrees across the
document's own sections) and draft-watermark-present (a leftover DRAFT /
CONFIDENTIAL watermark, also tagged finalization); and three policy rules β
document-properties-filled (a required built-in property left empty; required
defaults to ["Title", "Author"]), confidentiality-notice / copyright-notice
(a profile-supplied notice string β copyright-notice defaults to "Β©" β missing
from every header/footer and the body). rules=["layout"] selects the whole
cluster; rules=["notices"] selects just the two notice rules.
Policy rules (off unless a profile enables them β spec-linter.md Β§6):
body-justified (body paragraphs not justified β fix justifies them),
body-line-spacing (line spacing β the profile's target, e.g. "1.5" β
fix sets it), table-numeric-right-align (a table column that's mostly numbers,
above threshold, but not right-aligned β fix right-aligns those cells). All
three fix idempotently through format_paragraph. A profile is a path to a
wordlive.lint.json file or an inline dict; it opts policy rules in, supplies
their targets, and can override a rule's severity or disable a default rule:
profile = {
"rules": {
"body-justified": {"enabled": True, "severity": "warning"},
"body-line-spacing": {"enabled": True, "target": "1.5"},
"table-numeric-right-align": {"enabled": True, "threshold": 0.8},
"double-space": {"enabled": False}, # disable a default rule
}
}
doc.lint(profile=profile) # or profile="wordlive.lint.json"
doc.regularize(profile=profile) # applies the policy fixes too
Document.regularize(rules=None, within=None, profile=None, dry_run=False, allow_content=False)
is the write side: it applies the fixable findings in one
doc.edit("Regularize formatting") (one Ctrl-Z reverts them all; selection and
scroll preserved) and returns {applied, skipped, deferred, findings} (plus
ops_run, and dry_run when set). The default fixes are targeted and
idempotent β each writes the style's own value back as a direct property, so a
second regularize applies nothing (a tested invariant). dry_run=True plans
without writing; rules / within / profile select the same way as lint.
Fixes that change content rather than formatting (deleting a stray paragraph,
inserting a caption, stripping a watermark) are flagged adds_content on the
finding and withheld into deferred unless you pass allow_content=True β
so a default pass never silently rewrites what the document says. It's
Track-Changes-aware (the edits are tracked when Track Changes is on).
findings = doc.lint(within="heading:3") # audit one section
for f in findings:
print(f["rule"], f["severity"], f["fixable"])
doc.regularize(rules=["heading-keep-with-next"], dry_run=True) # preview
doc.regularize(within="heading:3") # apply, one atomic undo
A finding is also available as the exported Finding
dataclass (from wordlive import Finding) β a frozen dataclass carrying the
fields above, with a .to_dict(). lint / regularize are documented on
Document.
wordlive.Finding
dataclass
¶
Finding(rule: str, kind: str, severity: str, anchor_id: str, message: str, fixable: bool = False, fix: FixOps | None = None, adds_content: bool = False, observed: str | None = None, expected: str | None = None)
One linter result. fix is present iff fixable β an op-shaped dict (or
list of them) regularize runs verbatim through the batch op loop.
Markdown & HTML export¶
Document.to_markdown(within=None) and Document.to_html(within=None) are the
read mirror of insert_markdown β they serialise the whole
document (or one anchor's range) to clean Markdown or an HTML fragment.
Both render from one document walk, so they agree on structure: headings, bullet
/ numbered lists (nested), **bold** / *italic* / `code` (a monospace
run; HTML keeps underline too), GFM pipe tables, inline images as
, and hyperlinks as [text](url). Round-tripping is a fixed
point: to_markdown escapes exactly what insert_markdown unescapes, so
read-modify-write neither drops nor accretes backslashes. Export is lossy by
design, like the constrained-subset import:
it round-trips the dialect import speaks and reads the rest richer (deeper
headings, tables), but colours, merged table cells, and (in Markdown) underline
don't survive.
within scopes to an anchor's literal range β pass a range:START-END (e.g.
from find), an anchor id, or an Anchor. A heading:N
covers only the heading line, not its section body β use
between or a range: for "the section under X".
within=None (the default) serialises the whole document. Both are pure reads.
md = doc.to_markdown() # the whole document as Markdown
section = doc.to_markdown(within="heading:3") # one heading's range
html = doc.to_html(within="range:120-540") # a found span as HTML
Document.read(budget=6000, depth=None) is the token-budgeted read of the
whole document β load an 80-page doc into context cheaply while every anchor
stays addressable. Headings are verbatim (each tagged <!-- heading:N -->),
tables become one-line shape stubs, and body text is sampled to fit budget
(~4 chars/token), weighted so shallower sections keep more than deep ones;
overflow elides to markers that name the para: range, so an agent can drill in
with to_markdown(within=β¦). depth caps how deep a section keeps body.
overview = doc.read(budget=4000) # the whole doc, budgeted + addressable
shallow = doc.read(budget=4000, depth=1) # outline + only top-level bodies
Documented on Document.
Checkpoint & diff¶
Document.checkpoint(include="text+style", within=None) fingerprints the
document's structure right now and returns an opaque, serialisable
Checkpoint (from wordlive import Checkpoint). Store
the token, let edits happen (agent or user), then ask what changed β the only
reliable way to do so, since Word emits no content-change event, and how an agent
verifies its own edits landed without re-reading the whole document. include
sets the fingerprint depth: "text" (cheapest β a restyle is invisible),
"text+style" (default β folds the applied paragraph style in, so a restyle
surfaces), or "text+format" (also hashes each paragraph's format_info, so a
pure direct-formatting edit surfaces as a reformat). within=anchor
fingerprints one section/range. A pure read β selection, scroll, and Saved are
untouched.
Document.changes_since(cp) diffs a stored checkpoint against the document
now; Document.diff(cp_a, cp_b) diffs two stored checkpoints. Both accept a
Checkpoint, its to_json() string, or the parsed dict (so a token round-tripped
through a file works directly), and return a structured change list. Each change
is one of replace (text edit), insert, delete, restyle (same text, style
changed), or reformat (same text+style, direct formatting changed β only with
include="text+format"), carrying {op, anchor_id, index_before, index_after,
text_before, text_after, style_before, style_after} as applicable. Inserts /
replaces / restyles carry the current para:N (anchor_id) so the caller can
act on the change immediately; a delete references only the old index/text (its
anchor is gone). Table edits are reported coarsely (per-cell diffing is deferred)
as table_change / table_insert / table_delete, each carrying the
table:N anchor_id and the before/after shape ([rows, cols]). Alignment is
by paragraph content (difflib.SequenceMatcher), not index β para:N
renumbers under inserts/deletes β and an unchanged document returns [] via a
whole-document doc_hash fast-path. Because alignment is content-only, paragraphs
with identical text (blank lines, repeated boilerplate) can mis-pair amid an edit
(usually spurious blank-line churn, not a misclassified real change). A
within=range:START-END scope cannot be re-derived by changes_since (offsets
shift under edits β it raises a clear error); use a stable anchor (heading:N /
bookmark: / cc:) or diff() two stored checkpoints.
cp = doc.checkpoint() # fingerprint now
# β¦ agent or user edits β¦
changes = doc.changes_since(cp) # structured change list
touched = {c["anchor_id"] for c in changes if "anchor_id" in c}
assert touched == {"para:4", "para:7"} # verify my edits landed where I meant
token = cp.to_json() # persist the token (e.g. to a file)
later = Checkpoint.from_json(token)
Pure reads β not exec ops (the token round-trips through the caller, not Word).
Deferred: pin-backed exact identity (track=True), move detection
(moves=True), per-cell table diffing, and an in-document checkpoint store.
wordlive.Checkpoint
dataclass
¶
Checkpoint(version: int, include: str, scope: str | None, paragraphs: list[dict[str, Any]], tables: list[dict[str, Any]] = list(), doc_hash: str = '')
An opaque, serialisable structural fingerprint of the document at one
moment. Build with Document.checkpoint; the
caller holds the token and feeds it back to changes_since / diff.
paragraphs is one dict per paragraph in document order
({i, text, style, level, list, fmt, key, hash}): key is the alignment
identity (normalised text only β deliberately not style/level, so a
restyled paragraph still aligns as equal and surfaces as a restyle rather
than a delete+insert), and hash is the change key (normalised text plus, per
include, style and the format fingerprint). tables fingerprints each table
coarsely ({index, shape, cells_hash} β detects a cell changed); doc_hash
is the whole-fingerprint fast-path (equal β no changes).
Because key is text-only, paragraphs with identical normalised text (blank
lines, repeated boilerplate) share a key and are interchangeable to the
aligner, so an edit amid many identical lines can mis-pair them (usually a
spurious blank-line insert/delete, not a misclassified real change). Exact
per-paragraph identity is the deferred track=True (pin-backed) mode.
to_json ¶
from_json
classmethod
¶
Rebuild a Checkpoint from to_json() output (a JSON string or the
already-parsed dict).
Source code in src/wordlive/_checkpoint.py
Snapshots¶
Document.snapshot(...) and
Anchor.snapshot(...) render page(s) of the live document
to PNG so a vision model can see the layout β Word exports a pixel-faithful
PDF and wordlive rasterises the requested pages. Document.snapshot selects
pages (all, one, or a span); Anchor.snapshot (and
Document.snapshot_anchor) renders the page(s) an anchor
occupies, expanding a heading to its whole section. Both return a list of
Snapshot (one per page) and optionally write the image(s) to out. Pass
markup="all" to render tracked changes and comments as visible revision marks
and balloons instead of the final document (the structured counterpart is
Document.revisions). dpi (default 150) sets
resolution; max_dim caps each page's long edge in pixels (only ever lowering
it) β the lever for a cheap whole-document layout check, since a vision model's
token cost scales with pixel area, so a long-edge cap is a predictable per-page
budget regardless of paper size (~1000 stays legible). This needs the optional
snapshot extra (PyMuPDF); a missing backend raises
SnapshotError.
import wordlive as wl
with wl.attach() as word:
doc = word.documents.active
png = doc.heading("Introduction").snapshot()[0].png # bytes for a model
doc.snapshot("report.png", pages=(1, 3)) # write pages 1-3
doc.snapshot("review.png", markup="all") # show tracked changes
shots = doc.snapshot(max_dim=1000) # whole doc, cheap layout check
wordlive.Snapshot
dataclass
¶
One rendered page of a document.
page is the 1-based document page number; png is the PNG-encoded image
bytes β feed it straight to a vision model, or write it yourself. path is
where the image was written when a snapshot(out=...) call saved it to disk,
otherwise None.
Reference¶
Typed constants and the exception taxonomy.
Constants¶
wordlive.constants re-exports the typed IntEnum mirrors of the Word Wd*
magic numbers wordlive uses internally (alignment, break types, wrap types,
β¦). You rarely need these directly β the high-level API takes plain strings
("center", "page", "square") and maps them β but they're available for
.com escape-hatch code that talks to the raw object model.
Exceptions¶
wordlive.WordliveError ¶
Bases: Exception
Base class for all wordlive errors.
wordlive.WordNotRunningError ¶
Bases: WordliveError
No running Word instance is available.
wordlive.DocumentNotFoundError ¶
Bases: WordliveError
The requested document is not open in Word.
Source code in src/wordlive/exceptions.py
wordlive.AnchorNotFoundError ¶
Bases: WordliveError
The requested anchor (bookmark / content control / heading) does not exist.
Source code in src/wordlive/exceptions.py
wordlive.StyleNotFoundError ¶
Bases: AnchorNotFoundError
The requested paragraph or character style is not defined in the document.
Subclass of AnchorNotFoundError so it shares the same exit code (2) and so
except AnchorNotFoundError catches both bookmark-misses and style-misses.
Retryable after re-reading doc.styles.list().
Source code in src/wordlive/exceptions.py
wordlive.AmbiguousMatchError ¶
Bases: WordliveError
A find/replace pattern matched more than one occurrence without disambiguation.
Carries the list of matches so callers (notably LLM drivers) can pick an
occurrence index and retry.
Source code in src/wordlive/exceptions.py
wordlive.ReplaceVerificationError ¶
Bases: WordliveError
A resolved replacement target didn't match the located text β refused to write.
wordlive verifies each find/replace target against the located match before
writing, and raises this instead of overwriting the wrong span. It means the
document shifted out from under the match between locating and writing β
typically because an earlier edit in the same batch moved later text, or
Track Changes left both the inserted and deleted runs in place (so offsets no
longer line up). Re-read the text (find / paragraphs) and retry, or target
the span directly by anchor id. Maps to the generic exit code (1). Not
retryable as-is β the same call drifts again.
Source code in src/wordlive/exceptions.py
wordlive.ImageSourceError ¶
Bases: WordliveError
An image given to insert_image couldn't be turned into an embeddable file.
Raised for a missing or unreadable path, malformed base64, or bytes whose format isn't a recognised raster image (PNG/JPEG/GIF/BMP/TIFF). It's a bad-input error β not a "named thing is missing" β so it maps to the generic exit code (1) rather than reusing the anchor-not-found code. Not retryable: fix the input.
Source code in src/wordlive/exceptions.py
wordlive.ExcelNotAvailableError ¶
ExcelNotAvailableError(message: str = 'Microsoft Excel is not available; charts require Excel installed')
Bases: WordliveError
insert_chart needs Microsoft Excel, but it isn't available over COM.
Charts are Excel-backed: Range.InlineShapes.AddChart2 embeds a chart whose
data lives in a hidden Excel workbook, so a working Excel.Application COM
server is required. Raised (after a non-mutating probe, before any chart is
inserted β the document is left untouched) when Excel isn't installed or
can't be launched. Unlike WordBusyError this is a one-time environment
problem, not a transient busy state β install Excel, or render the chart
elsewhere and embed it with insert_image. It has its own CLI exit code (6),
parallel to WordNotRunningError's exit 4, so a missing-Excel failure is
distinguishable from generic bad input. Not retryable.
Source code in src/wordlive/exceptions.py
wordlive.OpError ¶
Bases: WordliveError
A batch/exec op (or a single dispatched write) was malformed.
Raised for an unknown op kind, a missing required field, or a mutually
exclusive pair given together (e.g. both path and base64 for an image).
It's a bad-input error β fix the request β so it maps to the generic exit
code (1), the same place click.ClickException used to land. Not retryable.
Source code in src/wordlive/exceptions.py
wordlive.EquationError ¶
Bases: WordliveError
An equation given to insert_equation couldn't be built into Word math.
Raised for malformed input (no input dialect given, or more than one of
unicodemath/latex/mathml), unparseable MathML, a LaTeX source the
optional converter rejects, or a missing LaTeXβMathML backend
(pip install "wordlive[latex]"). It's a bad-input / dependency problem β
not a "named thing is missing" β so it maps to the generic exit code (1),
like its sibling ImageSourceError. Not retryable: fix the input (or install
the extra).
Source code in src/wordlive/exceptions.py
wordlive.PathNotAllowedError ¶
Bases: WordliveError
A filesystem path was refused by the gated CLI / MCP surface's policy.
wordlive's Python API is trusted and ungated, but the CLI and MCP surfaces β
whose inputs can be prompt-injected β run every filesystem path through a
default-deny policy. This is raised when a save / save-as / export-pdf
target falls outside the configured save-directory whitelist (or no whitelist
is configured, so saving is off), or when an image-source path is a
non-local form (UNC \β¦, file://, or a URL) or sits outside the optional
image-directory allowlist. It's a policy denial / bad-input error β not a
"named thing is missing" β so it maps to the generic exit code (1), keeping
the six-code contract untouched. Not retryable: configure a whitelist
(--save-dir / WORDLIVE_SAVE_DIRS, --image-dir / WORDLIVE_IMAGE_DIRS)
or pass a local path inside it.
Source code in src/wordlive/exceptions.py
wordlive.SnapshotError ¶
Bases: WordliveError
A page/section snapshot couldn't be rendered.
Raised when the optional PDF-rendering backend (PyMuPDF) isn't installed, or
when rasterising the exported PDF fails. The PDF export itself goes through
Word's COM, so a busy/modal Word surfaces as WordBusyError, not this. It's
an environment/dependency problem rather than a "named thing is missing", so
it maps to the generic exit code (1). Fix by installing the extra:
pip install "wordlive[snapshot]" (or uv add "wordlive[snapshot]").
Source code in src/wordlive/exceptions.py
wordlive.WordBusyError ¶
Bases: WordliveError
Word rejected the RPC β typically a modal dialog or a transient busy state.
Retryable in principle; caller decides.
Source code in src/wordlive/exceptions.py
wordlive.ComError ¶
Bases: WordliveError
Generic wrapper for an unclassified pywintypes.com_error.