Skip to content

copy

copy

Copying a style definition from one document into another.

The public entry point is Styles.copy_style_from; this module holds the work.

The hard part is not moving the w:style element — a deep copy does that, and it is what people already do by hand. The hard part is the closure around it. A style names other styles (w:basedOn, w:next, w:link) and may name a numbering definition in a different part altogether, and every one of those references is an id that means something else, or nothing, in the destination document. A copy that carries the element but not the closure produces a style that renders wrongly, or a document Word rejects.

So: resolve the graph, copy what is reachable, and rewrite every id that changed.

copy_style

copy_style(
    styles: Styles,
    style: BaseStyle,
    *,
    name: str | None = None,
    on_collision: str = "skip",
    include_dependencies: bool = True,
    include_numbering: bool = True,
) -> BaseStyle

Copy style into styles; see Styles.copy_style_from.

Source code in src/docx/styles/copy.py
def copy_style(
    styles: Styles,
    style: BaseStyle,
    *,
    name: str | None = None,
    on_collision: str = "skip",
    include_dependencies: bool = True,
    include_numbering: bool = True,
) -> BaseStyle:
    """Copy `style` into `styles`; see :meth:`.Styles.copy_style_from`."""
    source_styles = style._element.getparent()  # pyright: ignore[reportPrivateUsage]
    if source_styles is None:
        raise ValueError("the style being copied is not attached to a styles part")

    dest = styles._element  # pyright: ignore[reportPrivateUsage]
    if source_styles is dest:
        raise ValueError("the style being copied is already in this document")

    to_copy = (
        _dependency_closure(style._element, source_styles)  # pyright: ignore[reportPrivateUsage]
        if include_dependencies
        else [style._element]  # pyright: ignore[reportPrivateUsage]
    )

    # -- style ids that changed on the way in, so references can be rewritten --
    id_map: Dict[str, str] = {}
    copied: Dict[str, CT_Style] = {}
    result: CT_Style | None = None

    for source_style in to_copy:
        is_target = source_style is style._element  # pyright: ignore[reportPrivateUsage]
        new_name = name if (is_target and name is not None) else source_style.name_val

        existing = _find_existing(dest, new_name)
        if existing is not None:
            # -- `on_collision` is about the style asked for. A dependency that already
            # -- exists is reused whatever the policy says: renaming or overwriting
            # -- "Normal" because a copied style happens to be based on it would be a
            # -- surprising thing to do to the destination document. --
            policy = on_collision if is_target else "skip"
            if policy == "raise":
                raise ValueError(f"document already contains style '{new_name}'")
            if policy == "skip":
                if is_target:
                    result = existing
                if source_style.styleId and existing.styleId:
                    id_map[source_style.styleId] = existing.styleId
                continue
            if policy == "rename":
                new_name = _free_name(dest, new_name or "Style")
            else:  # -- "overwrite" --
                dest.remove(existing)

        new_style = copymod.deepcopy(source_style)
        new_style.name_val = new_name
        new_style.styleId = _free_style_id(dest, new_style.styleId or "Style")
        if source_style.styleId:
            id_map[source_style.styleId] = new_style.styleId
        dest.append(new_style)
        copied[new_style.styleId] = new_style
        if is_target:
            result = new_style

    _rewrite_style_references(copied.values(), id_map)
    _copy_latent_style_exceptions(source_styles, dest, to_copy)

    if include_numbering:
        _copy_numbering(copied.values(), style.document_part, styles._doc_part)  # pyright: ignore[reportPrivateUsage]

    if result is None:  # pragma: no cover -- every branch above assigns it
        raise ValueError("the style could not be copied")
    return StyleFactory(result, styles._doc_part)  # pyright: ignore[reportPrivateUsage]