Skip to content

docx_plus.styles.modify

Style creation, modification, application, removal, and reconciliation. Field names for **properties kwargs match ResolvedFormatting so cascade output round-trips back through the modifier without translation.

Schema-strict child ordering for w:style, w:pPr, and w:rPr is enforced internally — see ARCHITECTURE.md §3.

The Phase 3.5 remap surface — find_matching_style, remap_styles, and ensure_style(match_existing=True) — is documented in ARCHITECTURE.md §4.

docx_plus.styles.modify

Style creation, modification, application, deletion — Word-native workflow.

The companion to :mod:docx_plus.styles.inspect. Where inspect reads the cascade, this module writes the styles that drive it. The intent is to push formatting changes through style definitions rather than scattering direct formatting; SPEC §5 calls this the "Word-native" workflow.

Property kwargs accepted by :func:create_style and :func:modify_style use the same field names as :class:~docx_plus.styles.inspect.ResolvedFormatting so a value resolved by the inspector can be round-tripped back through the modifier without translation. Schema-strict child ordering for CT_Style, CT_PPr, and CT_RPr is enforced internally so the produced styles.xml matches what Word writes.

StyleProxy

StyleProxy(doc: Document, element: _Element)

Lightweight live wrapper around a <w:style> element.

The proxy holds a reference to the live element rather than a snapshot — reads always reflect current state. Mutating methods delegate to :func:modify_style so child-element ordering and toggle semantics stay consistent. The :attr:element attribute is an escape hatch for callers that need direct lxml access (SPEC §5).

Wrap an existing <w:style> element in doc.

Source code in docx_plus/styles/modify.py
def __init__(self, doc: Document, element: etree._Element) -> None:
    """Wrap an existing ``<w:style>`` element in ``doc``."""
    self._doc = doc
    self.element = element

style_id property

style_id: str

The style's w:styleId (machine identifier).

style_type property

style_type: StyleType

The style's w:type (paragraph/character/table/numbering).

name property

name: str | None

The style's display name from w:name.

based_on property

based_on: str | None

The style id this one inherits from via w:basedOn, if any.

next_style property

next_style: str | None

The style id Word applies to the paragraph after one styled with this.

linked_style property

linked_style: str | None

The companion character style id from w:link, if any.

ui_priority property

ui_priority: int | None

The Word style-gallery sort priority from w:uiPriority.

q_format property

q_format: bool

True if the style is shown in Word's quick-style gallery.

modify

modify(**properties: Any) -> StyleProxy

Convenience: thin wrapper around :func:modify_style.

Source code in docx_plus/styles/modify.py
def modify(self, **properties: Any) -> StyleProxy:
    """Convenience: thin wrapper around :func:`modify_style`."""
    return modify_style(self._doc, self.style_id, **properties)

delete

delete(*, force: bool = False) -> None

Convenience: thin wrapper around :func:delete_style.

Source code in docx_plus/styles/modify.py
def delete(self, *, force: bool = False) -> None:
    """Convenience: thin wrapper around :func:`delete_style`."""
    delete_style(self._doc, self.style_id, force=force)

__repr__

__repr__() -> str

Compact repr for diagnostics.

Source code in docx_plus/styles/modify.py
def __repr__(self) -> str:
    """Compact repr for diagnostics."""
    return f"StyleProxy(style_id={self.style_id!r}, type={self.style_type!r})"

StyleInfo dataclass

StyleInfo(
    style_id: str,
    name: str,
    style_type: StyleType,
    based_on: str | None = None,
    is_default: bool = False,
    is_latent: bool = False,
)

Lightweight summary of a style for :func:list_styles.

Attributes:

Name Type Description
style_id str

The w:styleId value (machine identifier).

name str

The w:name value (human-readable display name).

style_type StyleType

"paragraph" | "character" | "table" | "numbering".

based_on str | None

The id of the parent style in the basedOn chain, if any.

is_default bool

True if the style carries w:default="1".

is_latent bool

True only for entries returned with include_latent=True that aren't materialised in styles.xml.

StyleExistsError

Bases: DocxPlusError

Raised by :func:create_style when the style id is already defined.

StyleNotFoundError

Bases: DocxPlusError

Raised when an operation references a style id that does not exist.

StyleInUseError

Bases: DocxPlusError

Raised by :func:delete_style when the style is referenced and force=False.

UnknownStylePropertyError

Bases: DocxPlusError, TypeError

Raised when a property kwarg is not a recognised style property.

create_style

create_style(
    doc: Document,
    style_id: str,
    *,
    style_type: StyleType = "paragraph",
    name: str | None = None,
    based_on: str | None = None,
    next_style: str | None = None,
    linked_style: str | None = None,
    ui_priority: int = 99,
    q_format: bool = False,
    custom: bool = True,
    **properties: Any,
) -> StyleProxy

Define a new style in doc.

Parameters:

Name Type Description Default
doc Document

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

required
style_id str

Machine identifier (w:styleId). Must be unique within the document's styles.xml.

required
style_type StyleType

"paragraph" (default), "character", "table", or "numbering".

'paragraph'
name str | None

Display name. Defaults to style_id.

None
based_on str | None

Parent style id in the basedOn chain.

None
next_style str | None

Style applied to the paragraph that follows one styled with this style (e.g. Heading1 -> Normal).

None
linked_style str | None

Companion character style for a paragraph style (Word's Heading1 <-> Heading1Char pairing).

None
ui_priority int

Sort priority in Word's style gallery (lower = higher).

99
q_format bool

Show in Word's quick-style gallery if True.

False
custom bool

Mark as a custom style (w:customStyle="1"). Defaults True because user-defined styles should not be confused with built-ins.

True
**properties Any

Any field name from :class:ResolvedFormatting. See module docstring for the supported set.

{}

Returns:

Name Type Description
A StyleProxy

class:StyleProxy for the new style.

Raises:

Type Description
StyleExistsError

If style_id is already defined.

UnknownStylePropertyError

If **properties contains a key that is not a recognised style property.

Source code in docx_plus/styles/modify.py
def create_style(
    doc: Document,
    style_id: str,
    *,
    style_type: StyleType = "paragraph",
    name: str | None = None,
    based_on: str | None = None,
    next_style: str | None = None,
    linked_style: str | None = None,
    ui_priority: int = 99,
    q_format: bool = False,
    custom: bool = True,
    **properties: Any,
) -> StyleProxy:
    """Define a new style in ``doc``.

    Args:
        doc: The python-docx :class:`~docx.document.Document` to mutate.
        style_id: Machine identifier (``w:styleId``). Must be unique within
            the document's styles.xml.
        style_type: ``"paragraph"`` (default), ``"character"``, ``"table"``,
            or ``"numbering"``.
        name: Display name. Defaults to ``style_id``.
        based_on: Parent style id in the basedOn chain.
        next_style: Style applied to the paragraph that follows one styled
            with this style (e.g. ``Heading1`` -> ``Normal``).
        linked_style: Companion character style for a paragraph style (Word's
            ``Heading1`` <-> ``Heading1Char`` pairing).
        ui_priority: Sort priority in Word's style gallery (lower = higher).
        q_format: Show in Word's quick-style gallery if True.
        custom: Mark as a custom style (``w:customStyle="1"``). Defaults True
            because user-defined styles should not be confused with built-ins.
        **properties: Any field name from :class:`ResolvedFormatting`. See
            module docstring for the supported set.

    Returns:
        A :class:`StyleProxy` for the new style.

    Raises:
        StyleExistsError: If ``style_id`` is already defined.
        UnknownStylePropertyError: If ``**properties`` contains a key that is
            not a recognised style property.
    """
    _validate_property_keys(properties)
    styles_root = doc.styles.element
    if _find_style_element(styles_root, style_id) is not None:
        raise StyleExistsError(f"style {style_id!r} already exists")

    style_el = el("w:style", **{"w:type": style_type, "w:styleId": style_id})
    if custom:
        style_el.set(qn("w:customStyle"), "1")

    _set_simple_child(style_el, "name", {"w:val": name or style_id})
    if based_on is not None:
        _set_simple_child(style_el, "basedOn", {"w:val": based_on})
    if next_style is not None:
        _set_simple_child(style_el, "next", {"w:val": next_style})
    if linked_style is not None:
        _set_simple_child(style_el, "link", {"w:val": linked_style})
    _set_simple_child(style_el, "uiPriority", {"w:val": str(ui_priority)})
    if q_format:
        _set_simple_child(style_el, "qFormat", {})

    for prop_name, value in properties.items():
        _write_property(style_el, prop_name, value)

    styles_root.append(style_el)
    return StyleProxy(doc, style_el)

modify_style

modify_style(
    doc: Document,
    style_id: str,
    *,
    if_missing: Literal["raise", "create"] = "raise",
    **properties: Any,
) -> StyleProxy

Update an existing style's properties in place.

Pass only the properties to change; others are preserved. Per SPEC §5, toggle properties (bold, italic, …) treat True/False as explicit settings (writing w:val="true"/"false") and None as "clear the setting so XOR with the parent resumes". Non-toggle properties treat None as "remove this property".

Parameters:

Name Type Description Default
doc Document

The document containing the style.

required
style_id str

Identifier of the style to modify.

required
if_missing Literal['raise', 'create']

"raise" (default) raises :class:StyleNotFoundError when the style is not defined; "create" falls through to :func:create_style with the supplied properties as the initial definition.

'raise'
**properties Any

Any field name from :class:ResolvedFormatting.

{}

Returns:

Name Type Description
A StyleProxy

class:StyleProxy for the modified style.

Raises:

Type Description
StyleNotFoundError

If style_id is undefined and if_missing="raise".

UnknownStylePropertyError

If **properties contains an unrecognised key.

Source code in docx_plus/styles/modify.py
def modify_style(
    doc: Document,
    style_id: str,
    *,
    if_missing: Literal["raise", "create"] = "raise",
    **properties: Any,
) -> StyleProxy:
    """Update an existing style's properties in place.

    Pass only the properties to change; others are preserved. Per SPEC §5,
    toggle properties (``bold``, ``italic``, …) treat ``True``/``False`` as
    explicit settings (writing ``w:val="true"``/``"false"``) and ``None`` as
    "clear the setting so XOR with the parent resumes". Non-toggle properties
    treat ``None`` as "remove this property".

    Args:
        doc: The document containing the style.
        style_id: Identifier of the style to modify.
        if_missing: ``"raise"`` (default) raises :class:`StyleNotFoundError`
            when the style is not defined; ``"create"`` falls through to
            :func:`create_style` with the supplied properties as the initial
            definition.
        **properties: Any field name from :class:`ResolvedFormatting`.

    Returns:
        A :class:`StyleProxy` for the modified style.

    Raises:
        StyleNotFoundError: If ``style_id`` is undefined and
            ``if_missing="raise"``.
        UnknownStylePropertyError: If ``**properties`` contains an
            unrecognised key.
    """
    _validate_property_keys(properties)
    styles_root = doc.styles.element
    style_el = _find_style_element(styles_root, style_id)
    if style_el is None:
        if if_missing == "create":
            return create_style(doc, style_id, **properties)
        raise StyleNotFoundError(f"style {style_id!r} is not defined")
    for prop_name, value in properties.items():
        _write_property(style_el, prop_name, value)
    return StyleProxy(doc, style_el)

apply_style

apply_style(target: Paragraph | Run | _Cell, style_id: str) -> None

Apply a style by id to a paragraph, run, or cell.

Resolves the style id against the target's owning document and writes the appropriate w:pStyle (paragraph), w:rStyle (run), or — for cells — sets w:pStyle on every paragraph in the cell.

Parameters:

Name Type Description Default
target Paragraph | Run | _Cell

A python-docx :class:~docx.text.paragraph.Paragraph, :class:~docx.text.run.Run, or :class:~docx.table._Cell.

required
style_id str

The id of an already-defined style.

required

Raises:

Type Description
StyleNotFoundError

If style_id is not defined in the target's document.

Source code in docx_plus/styles/modify.py
def apply_style(target: Paragraph | Run | _Cell, style_id: str) -> None:
    """Apply a style by id to a paragraph, run, or cell.

    Resolves the style id against the target's owning document and writes the
    appropriate ``w:pStyle`` (paragraph), ``w:rStyle`` (run), or — for cells —
    sets ``w:pStyle`` on every paragraph in the cell.

    Args:
        target: A python-docx :class:`~docx.text.paragraph.Paragraph`,
            :class:`~docx.text.run.Run`, or :class:`~docx.table._Cell`.
        style_id: The id of an already-defined style.

    Raises:
        StyleNotFoundError: If ``style_id`` is not defined in the target's
            document.
    """
    from docx.table import _Cell as _CellCls
    from docx.text.paragraph import Paragraph as _Paragraph
    from docx.text.run import Run as _Run

    if not isinstance(target, (_Paragraph, _Run, _CellCls)):
        kind = type(target).__name__
        raise TypeError(f"apply_style expects Paragraph, Run, or _Cell; got {kind}")

    part: Any = target.part
    doc = part.document
    if _find_style_element(doc.styles.element, style_id) is None:
        raise StyleNotFoundError(f"style {style_id!r} is not defined")

    if isinstance(target, _Paragraph):
        _set_paragraph_style(target._p, style_id)
    elif isinstance(target, _Run):
        _set_run_style(target._r, style_id)
    else:
        for p in target.paragraphs:
            _set_paragraph_style(p._p, style_id)

delete_style

delete_style(doc: Document, style_id: str, *, force: bool = False) -> None

Remove a style definition from doc.

Parameters:

Name Type Description Default
doc Document

Document containing the style.

required
style_id str

Identifier of the style to remove.

required
force bool

If False (default), refuse to delete a style referenced by any paragraph, run, table, or other style. If True, delete anyway — Word will fall back to Normal for orphaned references.

False

Raises:

Type Description
StyleNotFoundError

If style_id is not defined.

StyleInUseError

If force=False and the style has any references.

Source code in docx_plus/styles/modify.py
def delete_style(doc: Document, style_id: str, *, force: bool = False) -> None:
    """Remove a style definition from ``doc``.

    Args:
        doc: Document containing the style.
        style_id: Identifier of the style to remove.
        force: If False (default), refuse to delete a style referenced by any
            paragraph, run, table, or other style. If True, delete anyway —
            Word will fall back to ``Normal`` for orphaned references.

    Raises:
        StyleNotFoundError: If ``style_id`` is not defined.
        StyleInUseError: If ``force=False`` and the style has any references.
    """
    styles_root = doc.styles.element
    style_el = _find_style_element(styles_root, style_id)
    if style_el is None:
        raise StyleNotFoundError(f"style {style_id!r} is not defined")
    if not force:
        refs = _find_references(doc, style_id)
        if refs:
            raise StyleInUseError(
                f"style {style_id!r} is referenced ({len(refs)} place(s)); "
                "pass force=True to delete anyway"
            )
    styles_root.remove(style_el)

ensure_style

ensure_style(
    doc: Document,
    style_id: str,
    *,
    match_existing: bool = False,
    **defaults_if_creating: Any,
) -> StyleProxy

Idempotent style materialisation.

If style_id is already defined, return a proxy without modifying it. If it names a known built-in (Heading1, Title, ListParagraph, …), materialise it from the built-in table — Word's defaults, not defaults_if_creating. Otherwise create a custom style with defaults_if_creating as the initial properties.

Parameters:

Name Type Description Default
doc Document

Document to ensure the style on.

required
style_id str

Identifier to ensure.

required
match_existing bool

If True, before falling back to creation, search for an existing style whose w:styleId or w:name matches style_id case/space-insensitively (via :func:find_matching_style). If found, that proxy is returned — note its style_id may differ from the requested one, so callers using apply_style should pass proxy.style_id. For document-wide normalisation, use :func:remap_styles which rewrites body references too.

False
**defaults_if_creating Any

Properties to use when creating a custom style. Ignored when materialising a known built-in (the user asked for the built-in; Word's defaults are what matters).

{}

Returns:

Name Type Description
A StyleProxy

class:StyleProxy for the existing-or-newly-created style.

Source code in docx_plus/styles/modify.py
def ensure_style(
    doc: Document,
    style_id: str,
    *,
    match_existing: bool = False,
    **defaults_if_creating: Any,
) -> StyleProxy:
    """Idempotent style materialisation.

    If ``style_id`` is already defined, return a proxy without modifying it.
    If it names a known built-in (Heading1, Title, ListParagraph, …),
    materialise it from the built-in table — Word's defaults, not
    ``defaults_if_creating``. Otherwise create a custom style with
    ``defaults_if_creating`` as the initial properties.

    Args:
        doc: Document to ensure the style on.
        style_id: Identifier to ensure.
        match_existing: If True, before falling back to creation, search for
            an existing style whose ``w:styleId`` or ``w:name`` matches
            ``style_id`` case/space-insensitively (via
            :func:`find_matching_style`). If found, that proxy is returned —
            note its ``style_id`` may differ from the requested one, so
            callers using ``apply_style`` should pass ``proxy.style_id``.
            For document-wide normalisation, use :func:`remap_styles` which
            rewrites body references too.
        **defaults_if_creating: Properties to use when creating a *custom*
            style. Ignored when materialising a known built-in (the user
            asked for the built-in; Word's defaults are what matters).

    Returns:
        A :class:`StyleProxy` for the existing-or-newly-created style.
    """
    styles_root = doc.styles.element
    existing = _find_style_element(styles_root, style_id)
    if existing is not None:
        return StyleProxy(doc, existing)
    if match_existing:
        # When the requested id is a known built-in, constrain the match to
        # the built-in's own type so a wrong-type look-alike can't satisfy it
        # (M11). For unknown custom ids there is no canonical type to filter by.
        builtin_type = _BUILTIN_STYLES.get(style_id, {}).get("style_type")
        matched_id = find_matching_style(doc, style_id, style_type=builtin_type)
        if matched_id is not None:
            matched_el = _find_style_element(styles_root, matched_id)
            if matched_el is not None:
                return StyleProxy(doc, matched_el)
    builtin = _BUILTIN_STYLES.get(style_id)
    if builtin is not None:
        return _materialise_builtin(doc, style_id, builtin)
    return create_style(doc, style_id, **defaults_if_creating)

find_matching_style

find_matching_style(
    doc: Document, target_id: str, *, style_type: StyleType | None = None
) -> str | None

Find an existing style that fulfils the role of target_id.

Matches case/space-insensitively against both the w:styleId and w:name of every defined style. Useful when a document uses a renamed or differently-cased version of a built-in style ("Heading 1" with a space, "heading1" lower-case, …).

Parameters:

Name Type Description Default
doc Document

Document to search.

required
target_id str

The id you want to map onto (e.g. "Heading1").

required
style_type StyleType | None

If given, only consider styles of this w:type ("paragraph", "character", "table", "numbering"). This guards against wrong-type collisions: a document with a character style literally named "Heading 1" must not satisfy a request for the paragraph style "Heading1", since applying it via w:pStyle would point a paragraph at a character style and Word would ignore or "repair" it. When None (default), type is not considered (legacy behaviour).

None

Returns:

Name Type Description
The str | None

attr:w:styleId of the first matching defined style, or

str | None

None if none match. If a style with id target_id is already

str | None

defined exactly and (when style_type is given) is of the right

str | None

type, returns target_id (the trivial match).

Source code in docx_plus/styles/modify.py
def find_matching_style(
    doc: Document, target_id: str, *, style_type: StyleType | None = None
) -> str | None:
    """Find an existing style that fulfils the role of ``target_id``.

    Matches case/space-insensitively against both the ``w:styleId`` and
    ``w:name`` of every defined style. Useful when a document uses a renamed
    or differently-cased version of a built-in style (``"Heading 1"`` with a
    space, ``"heading1"`` lower-case, …).

    Args:
        doc: Document to search.
        target_id: The id you want to map onto (e.g. ``"Heading1"``).
        style_type: If given, only consider styles of this ``w:type``
            (``"paragraph"``, ``"character"``, ``"table"``, ``"numbering"``).
            This guards against wrong-type collisions: a document with a
            *character* style literally named "Heading 1" must not satisfy a
            request for the *paragraph* style ``"Heading1"``, since applying
            it via ``w:pStyle`` would point a paragraph at a character style
            and Word would ignore or "repair" it. When ``None`` (default),
            type is not considered (legacy behaviour).

    Returns:
        The :attr:`w:styleId` of the first matching defined style, or
        ``None`` if none match. If a style with id ``target_id`` is already
        defined exactly *and* (when ``style_type`` is given) is of the right
        type, returns ``target_id`` (the trivial match).
    """
    target_norm = _normalize_style_key(target_id)
    if not target_norm:
        return None
    styles_root = doc.styles.element
    for style_el in styles_root.findall(qn("w:style")):
        sid: str | None = style_el.get(qn("w:styleId"))
        if sid is None:
            continue
        if style_type is not None and (style_el.get(qn("w:type")) or "paragraph") != style_type:
            continue
        if _normalize_style_key(sid) == target_norm:
            return sid
        name_el = style_el.find(qn("w:name"))
        if name_el is not None:
            name: str | None = name_el.get(qn("w:val"))
            if name is not None and _normalize_style_key(name) == target_norm:
                return sid
    return None

remap_styles

remap_styles(
    doc: Document,
    *,
    targets: list[str] | None = None,
    mapping: dict[str, str] | None = None,
    create_missing: bool = False,
) -> dict[str, str]

Reconcile a doc's styles against a set of canonical ids.

For each id in targets, resolve it to an existing style by:

  1. Exact match — the id is already defined in styles.xml.
  2. The supplied mapping if it names this target.
  3. :func:find_matching_style (case/space-insensitive on id and name).
  4. If create_missing=True and the target is in the known built-ins table, materialise it from the table — which declares basedOn="Normal" so the new style inherits the doc's customised Normal (fonts, colours, …) automatically.

Body references (w:pStyle, w:rStyle, w:tblStyle) pointing at the original target id are rewritten in place to the resolved id, so a subsequent :func:apply_style works without further translation. The rewrite spans every part that can carry such references — main body, headers, footers, footnotes, endnotes, comments — and each target is rewritten only through the ref tag that matches the resolved style's type, so a paragraph style is never wired into a w:rStyle slot. Refs between styles in styles.xml (basedOn, next, link) are left untouched — this keeps the remap a non-destructive rewrite.

Parameters:

Name Type Description Default
doc Document

Document to remap.

required
targets list[str] | None

Ids to reconcile. Defaults to every entry in the known-built-ins table.

None
mapping dict[str, str] | None

Optional explicit {target: existing_id} overrides applied before the matcher.

None
create_missing bool

If True, fall back to materialising from the built-ins table when nothing else matches. Only works for ids that have a built-in entry.

False

Returns:

Type Description
dict[str, str]

{target_id: resolved_id} for every target resolved. When

dict[str, str]

target_id == resolved_id, the doc already had it or it was just

dict[str, str]

created. Targets unresolved after all four steps are omitted.

Raises:

Type Description
StyleNotFoundError

If mapping names an existing_id that is not defined in the document.

Source code in docx_plus/styles/modify.py
def remap_styles(
    doc: Document,
    *,
    targets: list[str] | None = None,
    mapping: dict[str, str] | None = None,
    create_missing: bool = False,
) -> dict[str, str]:
    """Reconcile a doc's styles against a set of canonical ids.

    For each id in ``targets``, resolve it to an existing style by:

    1. Exact match — the id is already defined in ``styles.xml``.
    2. The supplied ``mapping`` if it names this target.
    3. :func:`find_matching_style` (case/space-insensitive on id and name).
    4. If ``create_missing=True`` and the target is in the known built-ins
       table, materialise it from the table — which declares
       ``basedOn="Normal"`` so the new style inherits the doc's customised
       Normal (fonts, colours, …) automatically.

    Body references (``w:pStyle``, ``w:rStyle``, ``w:tblStyle``) pointing at
    the original target id are rewritten in place to the resolved id, so a
    subsequent :func:`apply_style` works without further translation. The
    rewrite spans every part that can carry such references — main body,
    headers, footers, footnotes, endnotes, comments — and each target is
    rewritten only through the ref tag that matches the resolved style's
    type, so a paragraph style is never wired into a ``w:rStyle`` slot. Refs
    *between* styles in ``styles.xml`` (``basedOn``, ``next``, ``link``) are
    left untouched — this keeps the remap a non-destructive rewrite.

    Args:
        doc: Document to remap.
        targets: Ids to reconcile. Defaults to every entry in the
            known-built-ins table.
        mapping: Optional explicit ``{target: existing_id}`` overrides
            applied before the matcher.
        create_missing: If True, fall back to materialising from the
            built-ins table when nothing else matches. Only works for ids
            that have a built-in entry.

    Returns:
        ``{target_id: resolved_id}`` for every target resolved. When
        ``target_id == resolved_id``, the doc already had it or it was just
        created. Targets unresolved after all four steps are omitted.

    Raises:
        StyleNotFoundError: If ``mapping`` names an ``existing_id`` that is
            not defined in the document.
    """
    styles_root = doc.styles.element
    target_ids: list[str] = list(targets) if targets is not None else list(_BUILTIN_STYLES)
    explicit: dict[str, str] = dict(mapping) if mapping is not None else {}

    for target, existing_id in explicit.items():
        if _find_style_element(styles_root, existing_id) is None:
            raise StyleNotFoundError(
                f"mapping for {target!r} points at undefined style {existing_id!r}"
            )

    resolved: dict[str, str] = {}
    for target_id in target_ids:
        if _find_style_element(styles_root, target_id) is not None:
            resolved[target_id] = target_id
            continue
        if target_id in explicit:
            resolved[target_id] = explicit[target_id]
            continue
        match = find_matching_style(
            doc, target_id, style_type=_BUILTIN_STYLES.get(target_id, {}).get("style_type")
        )
        if match is not None:
            resolved[target_id] = match
            continue
        if create_missing and target_id in _BUILTIN_STYLES:
            _materialise_builtin(doc, target_id, _BUILTIN_STYLES[target_id])
            resolved[target_id] = target_id
            continue
        # Unresolved — omit from result.

    # Rewrite body references across every part that can carry them — main
    # body, headers, footers, footnotes, endnotes, comments (M12). Each
    # target is rewritten only through the ref tag that matches the resolved
    # style's type (M11): a paragraph style is reached by w:pStyle, a
    # character style by w:rStyle, a table style by w:tblStyle. Pointing the
    # wrong tag at a style is exactly what breaks Word.
    type_to_tag = {"paragraph": "pStyle", "character": "rStyle", "table": "tblStyle"}
    search_roots = _reference_search_roots(doc)
    for target_id, resolved_id in resolved.items():
        if target_id == resolved_id:
            continue
        resolved_type = _style_type_of(styles_root, resolved_id)
        tag = type_to_tag.get(resolved_type or "")
        if tag is None:
            # numbering (or unknown) styles aren't referenced via
            # pStyle/rStyle/tblStyle — nothing to rewrite in the body.
            continue
        for body_root in search_roots:
            for ref in xpath(body_root, f"//w:{tag}[@w:val=$sid]", sid=target_id):
                if isinstance(ref, etree._Element):
                    ref.set(qn("w:val"), resolved_id)
    return resolved

list_styles

list_styles(
    doc: Document,
    *,
    style_type: StyleType | None = None,
    include_latent: bool = False,
) -> list[StyleInfo]

List defined styles in doc.

Parameters:

Name Type Description Default
doc Document

Document to inspect.

required
style_type StyleType | None

If given, restrict to styles of this w:type.

None
include_latent bool

If True, also yield built-ins from the known-built-ins table that aren't materialised in styles.xml. Latent entries have is_latent=True so callers can distinguish them.

False

Returns:

Type Description
list[StyleInfo]

A list of :class:StyleInfo, materialised styles first, latent

list[StyleInfo]

entries (if requested) afterwards. Order within each group is the

list[StyleInfo]

order they appear in styles.xml (or the built-ins table).

Source code in docx_plus/styles/modify.py
def list_styles(
    doc: Document,
    *,
    style_type: StyleType | None = None,
    include_latent: bool = False,
) -> list[StyleInfo]:
    """List defined styles in ``doc``.

    Args:
        doc: Document to inspect.
        style_type: If given, restrict to styles of this ``w:type``.
        include_latent: If True, also yield built-ins from the known-built-ins
            table that aren't materialised in ``styles.xml``. Latent entries
            have ``is_latent=True`` so callers can distinguish them.

    Returns:
        A list of :class:`StyleInfo`, materialised styles first, latent
        entries (if requested) afterwards. Order within each group is the
        order they appear in ``styles.xml`` (or the built-ins table).
    """
    styles_root = doc.styles.element
    materialised: list[StyleInfo] = []
    seen_ids: set[str] = set()
    for style_el in styles_root.findall(qn("w:style")):
        info = _style_info_from_element(style_el)
        if info is None:
            continue
        if style_type is not None and info.style_type != style_type:
            continue
        materialised.append(info)
        seen_ids.add(info.style_id)
    if not include_latent:
        return materialised
    latent: list[StyleInfo] = []
    for sid, spec in _BUILTIN_STYLES.items():
        if sid in seen_ids:
            continue
        spec_type: StyleType = spec.get("style_type", "paragraph")
        if style_type is not None and spec_type != style_type:
            continue
        latent.append(
            StyleInfo(
                style_id=sid,
                name=spec.get("name", sid),
                style_type=spec_type,
                based_on=spec.get("based_on"),
                is_latent=True,
            )
        )
    return materialised + latent