Skip to content

copy

copy

Duplicating content — a paragraph, a run, a table row, a whole table.

copy.deepcopy(paragraph._p) followed by an addnext() works for plain text and quietly breaks for anything interesting. Everything this module does beyond the deep copy is a repair of one of those breakages:

  • a picture's r:embed names a relationship id belonging to the source part, so a copied image is either the wrong image or a dangling reference;
  • a hyperlink's r:id has the same problem, and points at an external target that may not exist in the destination package;
  • wp:docPr/@id must be unique document-wide, and a deep copy duplicates it;
  • bookmark ids and names collide, and Word treats a duplicate bookmark name as a second bookmark competing for anything that refers to it;
  • a copy into a different document carries a w:pStyle naming a style that may not be there, and a w:numPr naming a numId that certainly is not.

The two hard pieces already existed: Styles.copy_style_from() resolves the style closure and carries numbering across, and StoryPart.next_id allocates non-colliding drawing ids. This is what uses them.

copy_content

copy_content(
    element: BaseOxmlElement,
    source_part: StoryPart,
    dest_part: StoryPart,
    *,
    missing_style: str = "copy",
) -> BaseOxmlElement

A deep copy of element fit to be inserted into dest_part.

The copy is not attached to anything; the caller places it. See Paragraph.copy_to for what missing_style means.

Source code in src/docx/copy.py
def copy_content(
    element: BaseOxmlElement,
    source_part: StoryPart,
    dest_part: StoryPart,
    *,
    missing_style: str = "copy",
) -> BaseOxmlElement:
    """A deep copy of `element` fit to be inserted into `dest_part`.

    The copy is not attached to anything; the caller places it. See
    :meth:`.Paragraph.copy_to` for what `missing_style` means.
    """
    if missing_style not in _MISSING_STYLE_POLICIES:
        raise ValueError(
            "missing_style must be one of %s, got %r"
            % (", ".join(repr(p) for p in _MISSING_STYLE_POLICIES), missing_style)
        )

    new_element = copymod.deepcopy(element)

    _remap_relationships(new_element, source_part, dest_part)
    _reassign_drawing_ids(new_element, dest_part)
    _strip_bookmarks(new_element)

    if source_part is not dest_part:
        _carry_styles(new_element, source_part, dest_part, missing_style)
        _carry_numbering(new_element, source_part, dest_part)

    return new_element

destination_for

destination_for(
    container: object,
) -> tuple[StoryPart, BaseOxmlElement]

The (part, element) a copy goes into for container.

A Document is not itself a block-item container — its body is — so it is unwrapped here rather than at each call site.

Source code in src/docx/copy.py
def destination_for(container: object) -> tuple[StoryPart, BaseOxmlElement]:
    """The `(part, element)` a copy goes into for `container`.

    A |Document| is not itself a block-item container — its body is — so it is
    unwrapped here rather than at each call site.
    """
    from docx.document import Document

    if isinstance(container, Document):
        body = container._body  # pyright: ignore[reportPrivateUsage]
        return container.part, body._element  # pyright: ignore[reportPrivateUsage]
    return (
        container.part,  # pyright: ignore[reportAttributeAccessIssue]
        container._element,  # pyright: ignore[reportAttributeAccessIssue,reportPrivateUsage]
    )

place

place(
    new_element: BaseOxmlElement,
    dest_element: BaseOxmlElement,
    before: object = None,
    after: object = None,
) -> None

Insert new_element into dest_element, relative to before or after.

With neither, the copy is appended. A body ending in a w:sectPr appends before it, since the section properties must stay last.

Source code in src/docx/copy.py
def place(
    new_element: BaseOxmlElement,
    dest_element: BaseOxmlElement,
    before: object = None,
    after: object = None,
) -> None:
    """Insert `new_element` into `dest_element`, relative to `before` or `after`.

    With neither, the copy is appended. A body ending in a `w:sectPr` appends before it,
    since the section properties must stay last.
    """
    if before is not None and after is not None:
        raise ValueError("pass at most one of `before` and `after`")

    if before is not None:
        _element_of(before).addprevious(new_element)
        return
    if after is not None:
        _element_of(after).addnext(new_element)
        return

    sectPr = dest_element.find(qn("w:sectPr"))
    if sectPr is not None:
        sectPr.addprevious(new_element)
    else:
        dest_element.append(new_element)