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.
API reference
The complete paper-docx 0.2.0 public API, generated from docstrings.
python-docx API
The inherited document, paragraph, run, style, table, section, picture, header, and footer APIs.
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 operationThe 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.