Skip to content

docx_plus.fields.read

Reading the fields a document already contains.

A complex field is not an element — it is a run sequence delimited by w:fldChar markers, with the instruction spread across however many w:instrText elements Word happened to split it into. Reading one back means walking that sequence, which is why this is a capability rather than an xpath at each call site.

The instruction is where a field's meaning lives. The cached result is whatever Word last rendered and can be arbitrarily stale — which is exactly how a REF to a deleted bookmark survives a dozen revisions still showing the text that used to be correct.

docx_plus.fields.read

Read the fields already in a document.

The read half of :mod:docx_plus.fields.simple. A complex field is not a single element — it is a run sequence delimited by w:fldChar markers, with the instruction spread across however many w:instrText elements Word happened to split it into. Reading one back means walking that sequence, which is why this is a capability rather than a two-line xpath at each call site.

Anything auditing a document's cross-references, captions, or table of contents starts here: those are all fields, and their instruction is the only place their meaning is recorded. The cached result text is whatever Word last rendered and may be arbitrarily stale.

This module imports only from docx_plus.core (SPEC §9.1).

FieldInfo dataclass

FieldInfo(
    keyword: str,
    arguments: list[str] = list(),
    instruction: str = "",
    result: str = "",
    paragraph_index: int = -1,
    begin_element: _Element | None = None,
)

One complex field, as recorded in the document.

Attributes:

Name Type Description
keyword str

The field type, upper-cased — "REF", "PAGEREF", "SEQ", "TOC", "PAGE". Empty for a field with no instruction at all, which is malformed but occurs in the wild.

arguments list[str]

The remaining whitespace-separated tokens, with any surrounding quotes stripped. Switches are included in order, so REF fig1 \\h gives ["fig1", "\\h"].

instruction str

The raw instruction text, joined across every w:instrText in the field and stripped.

result str

The cached result text Word last rendered, or "" for a field with no result yet. Not authoritative — it is only what was displayed when the document was saved.

paragraph_index int

0-based position of the owning paragraph among the body's w:p elements.

begin_element _Element | None

The w:r carrying the begin w:fldChar, for callers that need to locate or rewrite the field.

Note

paragraph_index counts every w:p in the body including those inside table cells, matching :func:~docx_plus.bookmarks.read_bookmarks. That is a different numbering from doc.paragraphs.

switches property

switches: list[str]

Just the \x switch arguments, in order.

operands property

operands: list[str]

The arguments that are not switches — a bookmark name, a SEQ id.

read_fields

read_fields(doc: Document, *, keyword: str | None = None) -> list[FieldInfo]

Return every complex field in doc's body, in document order.

Parameters:

Name Type Description Default
doc Document

The python-docx :class:~docx.document.Document to scan.

required
keyword str | None

If given, return only fields of this type, matched case-insensitively ("REF", "SEQ", ...).

None

Returns:

Name Type Description
One list[FieldInfo]

class:FieldInfo per field.

Note

Only the main document body is scanned. A PAGE field in a footer — the usual place for one — is not returned. Headers, footers, and notes live in separate parts.

Note

Nested fields (a field inside another field's instruction, which Word writes for some TOC and IF constructions) are read as one field: the inner instruction text is folded into the outer. The keyword is still the outer field's, which is what a caller filtering by type expects.

Example

from docx import Document from docx_plus.bookmarks import add_bookmark, add_cross_reference from docx_plus.fields import read_fields doc = Document() target = doc.add_paragraph("Chapter One") _ = add_bookmark(target, "chapter1") _ = add_cross_reference(doc.add_paragraph("See "), bookmark="chapter1") for found in read_fields(doc): ... print(found.keyword, found.operands) REF ['chapter1']

Source code in docx_plus/fields/read.py
def read_fields(doc: Document, *, keyword: str | None = None) -> list[FieldInfo]:
    r"""Return every complex field in ``doc``'s body, in document order.

    Args:
        doc: The python-docx :class:`~docx.document.Document` to scan.
        keyword: If given, return only fields of this type, matched
            case-insensitively (``"REF"``, ``"SEQ"``, ...).

    Returns:
        One :class:`FieldInfo` per field.

    Note:
        Only the main document body is scanned. A ``PAGE`` field in a
        footer — the usual place for one — is not returned. Headers,
        footers, and notes live in separate parts.

    Note:
        Nested fields (a field inside another field's instruction, which
        Word writes for some ``TOC`` and ``IF`` constructions) are read as
        one field: the inner instruction text is folded into the outer.
        The keyword is still the outer field's, which is what a caller
        filtering by type expects.

    Example:
        >>> from docx import Document
        >>> from docx_plus.bookmarks import add_bookmark, add_cross_reference
        >>> from docx_plus.fields import read_fields
        >>> doc = Document()
        >>> target = doc.add_paragraph("Chapter One")
        >>> _ = add_bookmark(target, "chapter1")
        >>> _ = add_cross_reference(doc.add_paragraph("See "), bookmark="chapter1")
        >>> for found in read_fields(doc):
        ...     print(found.keyword, found.operands)
        REF ['chapter1']
    """
    wanted = keyword.upper() if keyword is not None else None
    fields: list[FieldInfo] = []

    for paragraph_index, p_element in enumerate(xpath(doc.element.body, ".//w:p")):
        for info in _fields_in_paragraph(p_element, paragraph_index):
            if wanted is None or info.keyword == wanted:
                fields.append(info)
    return fields