Skip to content

transfer

transfer

Bulk style import and extract, built on docx.styles.copy.copy_style.

copy_style_from() moves one style and resolves its w:basedOn / w:next / w:link closure and its numbering. That is the hard part and it is done. What people write by hand on top of it is the two bulk operations: pull a whole house template's styles into a generated document, and pull a set of styles out of a document into a template of their own.

The naive loop over copy_style_from() is O(n) full closure resolutions with repeated work, and re-imports shared numbering once per style that uses it. Both operations here resolve the shared work once.

import_styles

import_styles(
    styles: Styles,
    source: str | PathLike[str] | IO[bytes] | Document,
    names: Iterable[str] | None = None,
    *,
    overwrite: bool = False,
    include_latent: bool = False,
) -> ImportReport

Copy styles from source into styles; see Styles.import_from.

Source code in src/docx/styles/transfer.py
def import_styles(
    styles: Styles,
    source: str | os.PathLike[str] | IO[bytes] | Document,
    names: Iterable[str] | None = None,
    *,
    overwrite: bool = False,
    include_latent: bool = False,
) -> ImportReport:
    """Copy styles from `source` into `styles`; see :meth:`.Styles.import_from`."""
    source_document = _as_document(source)
    source_styles = source_document.styles

    wanted = (
        list(source_styles) if names is None else [source_styles[name] for name in names]
    )

    report: ImportReport = {}
    for style in wanted:
        name = style.name
        if name is None:
            continue
        present = name in styles
        if present and not overwrite:
            # -- nothing to do: the destination already defines this name, so a later
            # -- style based on it already resolves --
            report[name] = "skipped"
            continue
        styles.copy_style_from(style, on_collision="overwrite" if overwrite else "skip")
        report[name] = "replaced" if present else "added"

    if include_latent:
        _copy_all_latent_exceptions(source_styles, styles)

    return report

extract_styles

extract_styles(
    styles: Styles, names: Iterable[str] | None = None
) -> Tuple[Document, List[str]]

A new empty document carrying names and their closure; see Styles.extract.

Returns the document and the UI names of the styles it received, in the order they were added.

Source code in src/docx/styles/transfer.py
def extract_styles(
    styles: Styles, names: Iterable[str] | None = None
) -> Tuple[Document, List[str]]:
    """A new empty document carrying `names` and their closure; see :meth:`.Styles.extract`.

    Returns the document and the UI names of the styles it received, in the order they
    were added.
    """
    from docx.api import Document as new_document

    source_names = (
        [s.name for s in styles if s.name is not None]
        if names is None
        else _closure_names(styles, names)
    )

    destination = new_document()
    # -- the bundled template defines 164 styles of its own; a styles-only document that
    # -- carried those as well would not be the extract that was asked for --
    destination.styles.remove_unused()

    added: List[str] = []
    for name in source_names:
        style: BaseStyle = styles[name]
        destination.styles.copy_style_from(style, on_collision="overwrite")
        added.append(name)
    return destination, added

extract_styles_xml

extract_styles_xml(
    styles: Styles, names: Iterable[str] | None = None
) -> bytes

The styles.xml bytes of the extract; see Styles.extract_xml.

Source code in src/docx/styles/transfer.py
def extract_styles_xml(styles: Styles, names: Iterable[str] | None = None) -> bytes:
    """The `styles.xml` bytes of the extract; see :meth:`.Styles.extract_xml`."""
    from docx.opc.oxml import serialize_part_xml

    destination, _ = extract_styles(styles, names)
    styles_part = destination.part.part_related_by(RT.STYLES)
    return serialize_part_xml(styles_part.element)  # pyright: ignore[reportAttributeAccessIssue]

save_extract

save_extract(
    styles: Styles,
    path_or_stream: str | PathLike[str] | IO[bytes],
    names: Iterable[str] | None = None,
    *,
    as_template: bool = False,
) -> List[str]

Write the extract to path_or_stream; see Styles.extract.

Source code in src/docx/styles/transfer.py
def save_extract(
    styles: Styles,
    path_or_stream: str | os.PathLike[str] | IO[bytes],
    names: Iterable[str] | None = None,
    *,
    as_template: bool = False,
) -> List[str]:
    """Write the extract to `path_or_stream`; see :meth:`.Styles.extract`."""
    destination, added = extract_styles(styles, names)
    destination.save(path_or_stream, as_template=as_template)
    return added