Skip to content

docx_plus.revisions.mark

Author tracked insertions and deletions by wrapping run(s) already in the document. mark_insertion wraps the target in <w:ins>; mark_deletion wraps it in <w:del> and retags each <w:t> to <w:delText>. Both take the same target shapes as comments.add_comment — a single run, a whole paragraph, or a (start_run, end_run) range — but a range must lie within one paragraph, since w:ins / w:del cannot span a paragraph boundary.

Runs wrapped in a revision are not visible through python-docx's paragraph.runs; read them back with read_revisions.

docx_plus.revisions.mark

Author tracked insertions and deletions — wrap existing runs.

python-docx writes runs but cannot mark them as tracked changes. This module wraps run(s) already present in the document in the revision container OOXML python-docx skips:

  • :func:mark_insertion wraps the target run(s) in <w:ins> — the text is shown as an inserted revision; accepting it makes the text permanent.
  • :func:mark_deletion wraps the target run(s) in <w:del> and retags each <w:t> to <w:delText> — the text is shown struck-through; accepting the deletion removes it.

Both target a single run, a whole paragraph, or a (start_run, end_run) range — the same target shapes as comments.add_comment. A range must lie within one paragraph: w:ins / w:del are run-level containers and cannot span a paragraph boundary.

Runs wrapped in w:ins / w:del are not visible through python-docx's paragraph.runs (its CT_P descriptor only sees direct w:r children); read them back with :func:~docx_plus.revisions.read_revisions.

This module imports only from docx_plus.core and the sibling docx_plus.revisions.registry (SPEC §9.1).

RevisionTarget module-attribute

RevisionTarget = Run | Paragraph | tuple[Run, Run]

RevisionRef dataclass

RevisionRef(revision_id: int, body_element: _Element)

Handle for an authored revision.

Attributes:

Name Type Description
revision_id int

The w:id assigned to the wrapping element.

body_element _Element

The <w:ins> or <w:del> lxml element placed in the document body. Mutate it directly for advanced edits the v0.3 surface does not yet expose.

RevisionNotFoundError

Bases: DocxPlusError, KeyError

Raised when no revision element with the requested w:id exists.

Subclasses :class:KeyError so existing except KeyError: clauses still catch it; also :class:DocxPlusError per SPEC §9.7.

mark_insertion

mark_insertion(
    target: RevisionTarget,
    *,
    author: str = "",
    date: datetime | None = None,
    id_registry: RevisionIdRegistry | None = None,
) -> RevisionRef

Mark existing run(s) as a tracked insertion.

Wraps the target run(s) in <w:ins w:id w:author w:date>. The run content (including each <w:t>) is preserved unchanged; only the wrapping container is added.

Parameters:

Name Type Description Default
target RevisionTarget

Where the insertion applies.

  • A :class:~docx.text.run.Run wraps that one run.
  • A :class:~docx.text.paragraph.Paragraph wraps the contiguous span from its first run to its last run; the paragraph must contain at least one run.
  • A (start_run, end_run) tuple wraps the contiguous span from start_run to end_run inclusive. Both runs must share one paragraph and start_run must not appear after end_run.
required
author str

Author recorded in w:author. The empty string is legal.

''
date datetime | None

Timestamp recorded in w:date. None uses the current UTC time (millisecond precision). A supplied :class:datetime is formatted the same way.

None
id_registry RevisionIdRegistry | None

Pre-existing registry to share across an editing session. A fresh :class:RevisionIdRegistry is built from the target's document if not supplied.

None

Returns:

Name Type Description
A RevisionRef

class:RevisionRef with the assigned id and the <w:ins>

RevisionRef

element.

Raises:

Type Description
ValueError

If a paragraph target has no runs, or a range is reversed or spans more than one paragraph.

TypeError

If target is not a Run, Paragraph, or (Run, Run) tuple.

Example

from docx import Document from docx_plus.revisions import mark_insertion doc = Document() p = doc.add_paragraph() run = p.add_run("freshly added") ref = mark_insertion(run, author="Reviewer")

Source code in docx_plus/revisions/mark.py
def mark_insertion(
    target: RevisionTarget,
    *,
    author: str = "",
    date: dt.datetime | None = None,
    id_registry: RevisionIdRegistry | None = None,
) -> RevisionRef:
    """Mark existing run(s) as a tracked insertion.

    Wraps the target run(s) in ``<w:ins w:id w:author w:date>``. The run
    content (including each ``<w:t>``) is preserved unchanged; only the
    wrapping container is added.

    Args:
        target: Where the insertion applies.

            - A :class:`~docx.text.run.Run` wraps that one run.
            - A :class:`~docx.text.paragraph.Paragraph` wraps the contiguous
              span from its first run to its last run; the paragraph must
              contain at least one run.
            - A ``(start_run, end_run)`` tuple wraps the contiguous span
              from ``start_run`` to ``end_run`` inclusive. Both runs must
              share one paragraph and ``start_run`` must not appear after
              ``end_run``.
        author: Author recorded in ``w:author``. The empty string is legal.
        date: Timestamp recorded in ``w:date``. ``None`` uses the current
            UTC time (millisecond precision). A supplied :class:`datetime`
            is formatted the same way.
        id_registry: Pre-existing registry to share across an editing
            session. A fresh :class:`RevisionIdRegistry` is built from the
            target's document if not supplied.

    Returns:
        A :class:`RevisionRef` with the assigned id and the ``<w:ins>``
        element.

    Raises:
        ValueError: If a paragraph target has no runs, or a range is
            reversed or spans more than one paragraph.
        TypeError: If ``target`` is not a Run, Paragraph, or ``(Run, Run)``
            tuple.

    Example:
        >>> from docx import Document
        >>> from docx_plus.revisions import mark_insertion
        >>> doc = Document()
        >>> p = doc.add_paragraph()
        >>> run = p.add_run("freshly added")
        >>> ref = mark_insertion(run, author="Reviewer")
    """
    return _wrap_revision(target, "w:ins", author=author, date=date, id_registry=id_registry)

mark_deletion

mark_deletion(
    target: RevisionTarget,
    *,
    author: str = "",
    date: datetime | None = None,
    id_registry: RevisionIdRegistry | None = None,
) -> RevisionRef

Mark existing run(s) as a tracked deletion.

Wraps the target run(s) in <w:del w:id w:author w:date> and retags every <w:t> in the span to <w:delText> (the OOXML element for deleted run text). Word renders the span struck-through; accepting the deletion removes it, rejecting it restores live <w:t>.

Parameters:

Name Type Description Default
target RevisionTarget

Where the deletion applies — same shapes as :func:mark_insertion.

required
author str

Author recorded in w:author. The empty string is legal.

''
date datetime | None

Timestamp recorded in w:date. None uses the current UTC time (millisecond precision).

None
id_registry RevisionIdRegistry | None

Pre-existing registry to share across an editing session. A fresh :class:RevisionIdRegistry is built from the target's document if not supplied.

None

Returns:

Name Type Description
A RevisionRef

class:RevisionRef with the assigned id and the <w:del>

RevisionRef

element.

Raises:

Type Description
ValueError

If a paragraph target has no runs, or a range is reversed or spans more than one paragraph.

TypeError

If target is not a Run, Paragraph, or (Run, Run) tuple.

Source code in docx_plus/revisions/mark.py
def mark_deletion(
    target: RevisionTarget,
    *,
    author: str = "",
    date: dt.datetime | None = None,
    id_registry: RevisionIdRegistry | None = None,
) -> RevisionRef:
    """Mark existing run(s) as a tracked deletion.

    Wraps the target run(s) in ``<w:del w:id w:author w:date>`` and retags
    every ``<w:t>`` in the span to ``<w:delText>`` (the OOXML element for
    deleted run text). Word renders the span struck-through; accepting the
    deletion removes it, rejecting it restores live ``<w:t>``.

    Args:
        target: Where the deletion applies — same shapes as
            :func:`mark_insertion`.
        author: Author recorded in ``w:author``. The empty string is legal.
        date: Timestamp recorded in ``w:date``. ``None`` uses the current
            UTC time (millisecond precision).
        id_registry: Pre-existing registry to share across an editing
            session. A fresh :class:`RevisionIdRegistry` is built from the
            target's document if not supplied.

    Returns:
        A :class:`RevisionRef` with the assigned id and the ``<w:del>``
        element.

    Raises:
        ValueError: If a paragraph target has no runs, or a range is
            reversed or spans more than one paragraph.
        TypeError: If ``target`` is not a Run, Paragraph, or ``(Run, Run)``
            tuple.
    """
    return _wrap_revision(target, "w:del", author=author, date=date, id_registry=id_registry)