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]
|