Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/docx/oxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
register_element_cls("w:ptab", CT_PTab)
register_element_cls("w:r", CT_R)
register_element_cls("w:t", CT_Text)
register_element_cls("w:delText", CT_Text)

# ---------------------------------------------------------------------------
# header/footer-related mappings
Expand Down
4 changes: 2 additions & 2 deletions src/docx/oxml/text/paragraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def clear_content(self):
@property
def inner_content_elements(self) -> List[CT_R | CT_Hyperlink]:
"""Run and hyperlink children of the `w:p` element, in document order."""
return self.xpath("./w:r | ./w:hyperlink")
return self.xpath("./w:r | ./w:hyperlink | ./w:ins/w:r | ./w:moveTo/w:r")

@property
def lastRenderedPageBreaks(self) -> List[CT_LastRenderedPageBreak]:
Expand Down Expand Up @@ -99,7 +99,7 @@ def text(self): # pyright: ignore[reportIncompatibleMethodOverride]
Inner-content child elements like `w:r` and `w:hyperlink` are translated to
their text equivalent.
"""
return "".join(e.text for e in self.xpath("w:r | w:hyperlink"))
return "".join(e.text for e in self.xpath("w:r | w:hyperlink | w:ins/w:r | w:moveTo/w:r"))

def _insert_pPr(self, pPr: CT_PPr) -> CT_PPr:
self.insert(0, pPr)
Expand Down
4 changes: 3 additions & 1 deletion src/docx/oxml/text/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class CT_R(BaseOxmlElement):
cr = ZeroOrMore("w:cr")
drawing = ZeroOrMore("w:drawing")
t = ZeroOrMore("w:t")
delText = ZeroOrMore("w:delText")
tab = ZeroOrMore("w:tab")

def add_t(self, text: str) -> CT_Text:
Expand Down Expand Up @@ -75,6 +76,7 @@ def iter_items() -> Iterator[str | CT_Drawing | CT_LastRenderedPageBreak]:
" | w:noBreakHyphen"
" | w:ptab"
" | w:t"
" | w:delText"
" | w:tab"
):
if isinstance(e, (CT_Drawing, CT_LastRenderedPageBreak)):
Expand Down Expand Up @@ -134,7 +136,7 @@ def text(self) -> str:
equivalent.
"""
return "".join(
str(e) for e in self.xpath("w:br | w:cr | w:noBreakHyphen | w:ptab | w:t | w:tab")
str(e) for e in self.xpath("w:br | w:cr | w:noBreakHyphen | w:ptab | w:t | w:delText | w:tab")
)

@text.setter
Expand Down
54 changes: 54 additions & 0 deletions tests/test_tracked_changes_fix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from docx.oxml import parse_xml
from docx.text.paragraph import Paragraph
from docx.text.run import Run

class DescribeTrackedChanges:
def it_includes_insertions_in_paragraph_text(self):
"""
paragraph.text includes text within <w:ins> tags.
"""
xml = (
'<w:p xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml">'
' <w:r><w:t>Start </w:t></w:r>'
' <w:ins w:id="1" w:author="Me" w:date="2023-01-01T00:00:00Z">'
' <w:r><w:t>Inserted</w:t></w:r>'
' </w:ins>'
' <w:r><w:t> End</w:t></w:r>'
'</w:p>'
)
p = Paragraph(parse_xml(xml), None)
# Expected: "Start Inserted End"
# Before Fix: "Start End"
assert p.text == "Start Inserted End"

def it_excludes_deletions_in_paragraph_text(self):
"""
paragraph.text still excludes text within <w:del> tags (standard behavior).
"""
xml = (
'<w:p xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
' <w:r><w:t>Start </w:t></w:r>'
' <w:del w:id="2" w:author="Me" w:date="2023-01-01T00:00:00Z">'
' <w:r><w:delText>Deleted</w:delText></w:r>'
' </w:del>'
' <w:r><w:t>End</w:t></w:r>'
'</w:p>'
)
p = Paragraph(parse_xml(xml), None)
assert p.text == "Start End"

def it_includes_moved_text_destination(self):
"""
paragraph.text includes text within <w:moveTo> tags (treated as accepted/visible).
"""
xml = (
'<w:p xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
' <w:r><w:t>Start </w:t></w:r>'
' <w:moveTo w:id="3" w:author="Me" w:date="2023-01-01T00:00:00Z">'
' <w:r><w:t>Moved Text</w:t></w:r>'
' </w:moveTo>'
' <w:r><w:t> End</w:t></w:r>'
'</w:p>'
)
p = Paragraph(parse_xml(xml), None)
assert p.text == "Start Moved Text End"