Bookmarks and cross-references¶
A bookmark is a paired marker around a span of body text. A cross-reference
is a REF (text) or PAGEREF (page number) field pointing at a bookmark
by name. Module: docx_plus.bookmarks.
Bookmarks themselves are not fields, but cross-references are — so dirty the fields before saving.
Marking a target and pointing at it¶
from docx_plus.bookmarks import add_bookmark, add_cross_reference, read_bookmarks
from docx_plus.fields import mark_fields_dirty
heading = doc.add_heading("Introduction", level=1)
add_bookmark(heading, "intro_section")
p = doc.add_paragraph("See ")
add_cross_reference(p, bookmark="intro_section", kind="text") # REF -> heading text
p.add_run(" on page ")
add_cross_reference(p, bookmark="intro_section", kind="page") # PAGEREF -> page number
mark_fields_dirty(doc)
for b in read_bookmarks(doc):
print(b.name, b.anchored_text, b.paragraph_index)
add_bookmark(target, name, *, id_registry=None) — target is a Run, a
Paragraph (needs ≥1 run), or a (start_run, end_run) tuple.
Bookmark names have a strict grammar
name must match [A-Za-z_][A-Za-z0-9_]{0,39} — no spaces, no
punctuation, 40 characters max. Word's UI silently rejects names
outside this, but raw OOXML accepts them, which produces a
cross-reference that never resolves and no error anywhere.
add_bookmark raises eagerly instead.
add_cross_reference(paragraph, *, bookmark, kind="text", hyperlink=True,
number=None, position=False, suppress_non_delimiters=False,
numeric_format=None, preserve_formatting=False)
kindis"text"(REF) or"page"(PAGEREF). The\hswitch is added by default, so the reference is clickable.numberis"plain"/"relative"/"full"for the target's paragraph number.position=Trueresolves to"above"or"below".- The
REF-only switches raise if paired withkind="page".
Reading and deleting¶
read_bookmarks(doc)returns alist[BookmarkInfo]withbookmark_id,name,anchored_text, andparagraph_index.delete_bookmark(doc, name)removes every bookmark of that name — by name, not id, because that is what cross-references key off. Idempotent.
Batch inserts and generated names¶
Share a BookmarkIdRegistry(doc) via id_registry= when adding several at
once.
BookmarkNameRegistry(doc) guards duplicate names — a duplicate makes a
REF ambiguous — and mints hidden anchors via next_ref_name(). That is
Word's own _Ref + 9-digit form, which stays out of Word's Bookmark
dialog:
from docx_plus.bookmarks import BookmarkNameRegistry
names = BookmarkNameRegistry(doc)
anchor = names.next_ref_name() # e.g. "_Ref418320715"
This is how you make a caption
referenceable — a REF
field cannot point at a SEQ field, only at a bookmark.
Referencing without a bookmark¶
STYLEREF is the exception: it resolves against the nearest paragraph in a
given style and needs no anchor at all. It lives in
fields and is
the right tool for running headers.
See also¶
- How bookmarks work
- Fields —
mark_fields_dirty - Reference:
bookmarks.anchor,bookmarks.crossref,bookmarks.read,bookmarks.registry - Example:
bookmarks_and_xrefs.py