Skip to content

Keep comment anchors when assigning Paragraph.text - #1645

Open
Dmitry-Kov wants to merge 1 commit into
python-openxml:masterfrom
Dmitry-Kov:paragraph-text-keeps-comments
Open

Dmitry-Kov wants to merge 1 commit into
python-openxml:masterfrom
Dmitry-Kov:paragraph-text-keeps-comments

Conversation

@Dmitry-Kov

Copy link
Copy Markdown

Takes over #1605 by @gyanu2507, which was closed unmerged with an open invitation to pick it up. The commit keeps their credit with a Co-authored-by trailer. This fixes the comment half of #1604. Footnotes and tracked changes are still affected, so #1604 should stay open.

Problem

Paragraph.text = ... calls clear(), which removes every child of the paragraph except w:pPr. That includes w:commentRangeStart / w:commentRangeEnd (these are paragraph children, not run content) and the run holding w:commentReference. The <w:comment> stays in comments.xml anchored to nothing. Word opens the file without a warning and doesn't show the comment.

Change

Before clearing, the setter collects the paragraph's comment markers. It then puts them back around the new run: each w:commentRangeStart before it, and each w:commentRangeEnd and comment-reference run after it, both groups in document order. The original elements are moved, not rebuilt, so whatever Word wrote on them (including a localized reference rStyle) is kept.

A comment that covered part of the paragraph now covers the whole new run, not its original word span. For a full-text replacement that's the only outcome that makes sense, and it matches what Word does when you retype a commented passage. The docstring says so.

Differences from #1605:

  • Only markers that were in this paragraph come back. Keep comment anchors when assigning paragraph.text #1605 re-emitted start, end and reference for every commentRangeStart it found. So for a comment spanning two paragraphs (add_comment() supports this), assigning text to the first paragraph gave the comment a second commentRangeEnd and commentReference. Assigning text to the second paragraph still left it orphaned.
  • Markers inside a w:hyperlink are kept too. add_comment() places them there when the anchor run is inside a hyperlink, and the setter removes the hyperlink.
  • Keep comment anchors when assigning paragraph.text #1605's own test is kept unchanged apart from ruff format.

Not fixed here (#1604 stays open)

Tests

  • Unit: it_keeps_only_the_comment_markers_it_contains_when_text_is_assigned is parametrized over:
    • a range that starts here and ends in a later paragraph
    • a range that ends here
    • two comments on different words
    • a comment inside a hyperlink
    • w:pPr staying first
  • Acceptance: a new scenario in par-set-text.feature, run against the existing Word-authored comments-rich-para.docx.
  • Every new test fails on master.
  • pytest: 1615 passed. behave: 651 scenarios passed. ruff check and ruff format --check are clean, and pyright reports nothing new for paragraph.py (Python 3.9, deps from uv.lock).

Checked against the saved files

Before/after pairs produced through paragraph.text and checked with ooxml-integrity check out.docx --against in.docx (0.9.0). Disclosure: I maintain that tool.

Case master (1.2.0) #1605 this PR
#1604 repro: comment on one run CMT005 orphaned comment, FID001 comment anchors 1 → 0 no comment findings no comment findings
Word-authored comments-rich-para.docx, text set on the commented paragraph CMT005, FID001 comment anchors 4 → 3 no comment findings no comment findings
Comment spanning two paragraphs, text set on the first CMT002 commentRangeEnd without start second commentRangeEnd + commentReference for the same id (FID002 anchors 1 → 2) no comment findings, markers as in the source
Same, text set on the second CMT001, CMT003, CMT005, FID001 anchors 1 → 0 CMT001, CMT003, CMT005, FID001 anchors 1 → 0 no comment findings

The only other findings are FID014, which is the intended text change, and STY001 (#1609), which is already present in the python-docx-generated source files.

🤖 Generated with Claude Code

Assigning `Paragraph.text` calls `.clear()`, which removed the paragraph's
`w:commentRangeStart` / `w:commentRangeEnd` markers and the run holding
`w:commentReference`. The comment stayed in comments.xml anchored to
nothing, and Word dropped it from view without a warning (python-openxml#1604).

Keep those markers and put them back around the new run: range starts
before it, range ends and reference runs after it, each in document order.
Only markers that were in this paragraph come back, so a comment range
that starts or ends in another paragraph is neither duplicated nor cut
short. Markers inside a hyperlink, which the setter also removes, are kept
as well. The range now covers the whole new run, not its original span.

Footnote references and `w:ins` / `w:del` are still removed; python-openxml#1604 stays
open for those.

Takes over python-openxml#1605 by Gyanu Mayank, closed unmerged with an invitation for
anyone to pick it up.

Co-authored-by: Gyanu Mayank <gyanumayank0@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant