Paper Office
paper-docx

Guide

How paper-docx handles targeting, guarded edits, protection, comparison, and saving.

This guide covers the Paper-specific behavior you need when using the familiar docx API. The generated API reference documents the complete public surface and exact signatures.

Safety contract

Guarded Paper operations raise a PaperRefusal when the requested edit cannot be completed safely. A PaperRefusal leaves the document and destination files unchanged. Invalid arguments still raise TypeError or ValueError, and I/O failures use the applicable Python exception.

from docx.errors import AmbiguousTargetError, PaperRefusal
from docx.search import find_one

try:
    find_one(doc, "the")
except AmbiguousTargetError:
    ...  # narrow the story or choose an explicit occurrence
except PaperRefusal:
    ...  # inspect the refusal and choose another operation

The refusal hierarchy includes MalformedPackageError, AmbiguousTargetError, TargetNotFoundError, UnsupportedStructureError, BoundaryViolationError, RelationshipPolicyError, and DocumentProtectedError.

Text targeting

Search assembles text across fragmented runs. Matching is literal and exact by default; pass match="normalized" to fold case, typography, and whitespace.

find_text() returns every matching span. story= limits the search to one story, view= selects the revision projection, and near= ranks the complete candidate set without hiding matches. One-based nth= selects an occurrence by document order and cannot be combined with near=.

find_one() accepts story=, view=, nth=, and match=. It does not accept near=. Zero matches raise TargetNotFoundError; multiple matches raise AmbiguousTargetError.

from docx.search import find_one

span = find_one(doc, "Payment is due in 30 days", view="current")
result = span.replace(
    "Payment is due in 45 days",
    tracked=True,
    author="Reviewer",
)

A Span holds live references and revalidates before each operation. A successful text-changing replacement consumes it; reacquire the target before another edit. preserve_revision=True permits a current-view span wholly owned by one existing insertion to be corrected without changing that revision's identity or accept/reject behavior.

Structural editing

Block operations accept a text string, a live Block, or a live Span as a BlockTarget. A stored docx.story.Anchor is location evidence for reports; it is not a mutation target. Live destinations must come from view="current".

Document.revisions.accept_all() and .reject_all() resolve supported revisions atomically, with optional author filtering. remaining_unsupported() reports revision forms that require another tool.

Content-control setters support text, rich text, checkboxes, dates, and choice controls. Supported data-bound text controls update both the visible text and their custom XML store. Unsupported bound types, locked controls, and unsafe structures refuse.

insert_blocks_from() copies a bounded source range. Its source endpoints are included by default and can be controlled with include_start and include_end. append_document(headers="source") also carries the source letterhead; the default keeps destination headers and footers.

Paper also provides native comment-thread operations, hyperlinks, captions, footnotes, endnotes, and isolated picture replacement. See the generated reference for their signatures and refusal conditions.

Protection

set_protection(document, edit=...) turns on Word Restrict Editing using readOnly, comments, forms, or trackedChanges. Protection checks depend on both that mode and the operation: comments-only protection permits comment operations, and forms protection permits supported form-field updates.

acknowledge_protection(document) records an override for the open package. It does not remove or rewrite the protection setting.

Compare and save

compare() creates a Word-native tracked-change document. Before returning, it verifies on private copies that accepting and rejecting the redline produce the expected semantic package states. compare refuses differences it cannot represent as a clean redline.

from docx.package import compare

result = compare("original.docx", "revised.docx", author="Reviewer")
result.document.save("redline.docx")

Document.save() performs normal serialization and validates the package it writes. Path saves use staged replacement. Stream saves require a destination whose previous contents can be restored after failure.

Use patch_save(original_path, document, out_path) when unchanged package-part bytes matter. It restores the original bytes of semantically unchanged parts. It does not preserve byte ranges inside a changed XML part or the original ZIP container bytes after a real edit. A no-op produces a verbatim copy.

On this page