sw/qa/extras/ooxmlexport/data/tdf170952_delText.docx |binary
 sw/qa/extras/ooxmlexport/ooxmlexport25.cxx           |   15 +++++++
 sw/source/filter/ww8/docxattributeoutput.cxx         |   38 +++++++++----------
 sw/source/filter/ww8/docxattributeoutput.hxx         |    5 +-
 4 files changed, 35 insertions(+), 23 deletions(-)

New commits:
commit afeb7f3dc431ddbd85b454730e5775c0d410d2ff
Author:     Justin Luth <[email protected]>
AuthorDate: Sat Feb 21 17:08:34 2026 -0500
Commit:     Justin Luth <[email protected]>
CommitDate: Tue Feb 24 13:14:33 2026 +0100

    tdf#170952 docx export redlines: treat LastRun same as all others
    
    This fixes a 25.8.4 exposure of an existing problem
    that dates back to 7.4.7.
    
    There shouldn't be any good reason why the last run
    would be treated any differently in change-tracking.
    
    This basically reverts
    commit 382892341a63e400f221e1a533fd5a5d6b4d9d70
    Author: László Németh on Sat Feb 11 21:06:21 2023 +0000
        tdf#147892 DOCX: fix corrupt export at para marker revision history
        Reviewed-on: https://gerrit.libreoffice.org/c/core/+/146782
    
    which was basically replaced earlier in 25.2.3 by
    commit 7b57004cf20ae1715eabd6623ad9007a68a0a32e
    Author: Jaume Pujantell on Fri Mar 7 17:31:26 2025 +0100
        tdf#165059 sw fix not valid moveFrom/moveTo tag
        Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182503
    
    The only time bParagraphProps is true is the time
    that Laszlo was trying for, so bIsLastRun can be removed.
    
    I assume that what Jaume did was to implement Laszlo's comment
        Note: it's possible to optimize the fix to keep the change
        tracking history of the characters of the last run of the para,
        except the paragraph marker.
    
    The problem is that GetRedlineTypeForTextToken
    had determined that a w:delText should be used,
    but now that is not being wrapped in a w:del
    since the full history is not being written out.
    
    make CppunitTest_sw_ooxmlexport25 \
        CPPUNIT_TEST_NAME=testTdf170952_delText
    
    Change-Id: Ia6ee78a63c5f392d7c7a7eaaa65ff289944954d1
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199972
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Justin Luth <[email protected]>

diff --git a/sw/qa/extras/ooxmlexport/data/tdf170952_delText.docx 
b/sw/qa/extras/ooxmlexport/data/tdf170952_delText.docx
new file mode 100644
index 000000000000..57ed7f7de7df
Binary files /dev/null and 
b/sw/qa/extras/ooxmlexport/data/tdf170952_delText.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
index 21f2c1a055b5..6df37dc9db60 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
@@ -342,6 +342,21 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf167082)
     CPPUNIT_ASSERT_EQUAL(OUString("Heading 1"), aStyleName);
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf170952_delText)
+{
+    // Given a document with deleted text at the end of the paragraph
+
+    skipValidation(); // ERROR: The content of element 'w:rPrChange' is not 
complete. One of 
'{"http://schemas.openxmlformats.org/wordprocessingml/2006/main":rPr}' is 
expected.
+    createSwDoc("tdf170952_delText.docx");
+
+    save(TestFilter::DOCX);
+
+    xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr);
+    // delText must be inside a w:del or MS Word considers the document to be 
corrupt
+    assertXPath(pXmlDoc, "//w:delText", 3); // there are three delTree elements
+    CPPUNIT_ASSERT_EQUAL(3, countXPathNodes(pXmlDoc, 
"//w:del/w:r/w:delText")); // all are in w:del
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testRangeCommentInDeleteDocxExport)
 {
     // Given a document with a comment that is inside a delete redline:
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index b8f9524fc1ab..f89f73ca5385 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -1697,13 +1697,13 @@ void DocxAttributeOutput::EndParagraphProperties(const 
SfxItemSet& rParagraphMar
 
     if ( pRedlineParagraphMarkerDeleted )
     {
-        StartRedline(pRedlineParagraphMarkerDeleted, /*bLastRun=*/true, 
/*bParagraphProps=*/true);
-        EndRedline(pRedlineParagraphMarkerDeleted, /*bLastRun=*/true, 
/*bParagraphProps=*/true);
+        StartRedline(pRedlineParagraphMarkerDeleted, /*bParagraphProps=*/true);
+        EndRedline(pRedlineParagraphMarkerDeleted, /*bParagraphProps=*/true);
     }
     if ( pRedlineParagraphMarkerInserted )
     {
-        StartRedline(pRedlineParagraphMarkerInserted, /*bLastRun=*/true, 
/*bParagraphProps=*/true);
-        EndRedline(pRedlineParagraphMarkerInserted, /*bLastRun=*/true, 
/*bParagraphProps=*/true);
+        StartRedline(pRedlineParagraphMarkerInserted, 
/*bParagraphProps=*/true);
+        EndRedline(pRedlineParagraphMarkerInserted, /*bParagraphProps=*/true);
     }
 
     // mergeTopMarks() after paragraph mark properties child elements.
@@ -1843,7 +1843,7 @@ void DocxAttributeOutput::StartRun( const SwRedlineData* 
pRedlineData, sal_Int32
     m_pSerializer->mark(Tag_StartRun_3); // let's call it "postponed text"
 }
 
-void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, 
sal_Int32 nLen, bool bLastRun)
+void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, 
sal_Int32 nLen, bool /*bLastRun*/)
 {
     int nFieldsInPrevHyperlink = m_nFieldsInHyperlink;
     // Reset m_nFieldsInHyperlink if a new hyperlink is about to start
@@ -1936,9 +1936,9 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, 
sal_Int32 nPos, sal_In
             // InputField with extra grabbag params - it is sdt field
             (pIt->eType == ww::eFILLIN && static_cast<const 
SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements())))
         {
-            StartRedline( m_pRedlineData, bLastRun );
+            StartRedline(m_pRedlineData);
             StartField_Impl( pNode, nPos, *pIt, true );
-            EndRedline( m_pRedlineData, bLastRun );
+            EndRedline(m_pRedlineData);
 
             if (m_nHyperLinkCount.back() > 0)
                 ++m_nFieldsInHyperlink;
@@ -2015,12 +2015,12 @@ void DocxAttributeOutput::EndRun(const SwTextNode* 
pNode, sal_Int32 nPos, sal_In
                                              && m_pRedlineData->GetType() == 
RedlineType::Delete;
     if (bHasAnnotationMarkReferencesInDel)
     {
-        StartRedline(m_pRedlineData, bLastRun);
+        StartRedline(m_pRedlineData);
     }
     DoWriteAnnotationMarks();
     if (bHasAnnotationMarkReferencesInDel)
     {
-        EndRedline(m_pRedlineData, bLastRun);
+        EndRedline(m_pRedlineData);
     }
 
     // if there is some redlining in the document, output it
@@ -2038,7 +2038,7 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, 
sal_Int32 nPos, sal_In
 
     if (!bSkipRedline)
     {
-        StartRedline(m_pRedlineData, bLastRun);
+        StartRedline(m_pRedlineData);
     }
 
     if (m_closeHyperlinkInThisRun && m_nHyperLinkCount.back() > 0 && 
!m_hyperLinkAnchor.isEmpty()
@@ -2112,7 +2112,7 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, 
sal_Int32 nPos, sal_In
     // (except in the case of fields with multiple runs)
     if (!bSkipRedline)
     {
-        EndRedline(m_pRedlineData, bLastRun);
+        EndRedline(m_pRedlineData);
     }
     DoWriteBookmarksEnd(m_rBookmarksEnd, false, true); // Write moverange 
bookmarks
 
@@ -2229,7 +2229,7 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, 
sal_Int32 nPos, sal_In
 
     if ( m_pRedlineData )
     {
-        EndRedline( m_pRedlineData, bLastRun );
+        EndRedline(m_pRedlineData);
         m_pRedlineData = nullptr;
     }
 
@@ -4474,15 +4474,14 @@ void DocxAttributeOutput::Redline( const SwRedlineData* 
pRedlineData)
 // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that:
 // 'Redline' is used for tracked changes of formatting information of a run 
like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node)
 // 'StartRedline' is used to output tracked changes of run insertion and 
deletion (the run is inside the '<w:ins>' node)
-void DocxAttributeOutput::StartRedline(const SwRedlineData* pRedlineData, bool 
bLastRun,
-                                       bool bParagraphProps)
+void DocxAttributeOutput::StartRedline(const SwRedlineData* pRedlineData, bool 
bParagraphProps)
 {
     if ( !pRedlineData )
         return;
 
     // write out stack of this redline recursively (first the oldest)
-    if ( !bLastRun )
-        StartRedline( pRedlineData->Next(), false );
+    if (!bParagraphProps)
+        StartRedline( pRedlineData->Next());
 
     OString aId( OString::number( m_nRedlineId++ ) );
 
@@ -4540,8 +4539,7 @@ void DocxAttributeOutput::StartRedline(const 
SwRedlineData* pRedlineData, bool b
     }
 }
 
-void DocxAttributeOutput::EndRedline(const SwRedlineData* pRedlineData, bool 
bLastRun,
-                                     bool bParagraphProps)
+void DocxAttributeOutput::EndRedline(const SwRedlineData* pRedlineData, bool 
bParagraphProps)
 {
     if ( !pRedlineData || m_bWritingField )
         return;
@@ -4576,8 +4574,8 @@ void DocxAttributeOutput::EndRedline(const SwRedlineData* 
pRedlineData, bool bLa
     }
 
     // write out stack of this redline recursively (first the newest)
-    if ( !bLastRun )
-        EndRedline( pRedlineData->Next(), false );
+    if (!bParagraphProps)
+        EndRedline(pRedlineData->Next());
 }
 
 void DocxAttributeOutput::FormatDrop( const SwTextNode& /*rNode*/, const 
SwFormatDrop& /*rSwFormatDrop*/, sal_uInt16 /*nStyle*/, 
ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/, 
ww8::WW8TableNodeInfoInner::Pointer_t )
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx 
b/sw/source/filter/ww8/docxattributeoutput.hxx
index fa6380be64cf..a65bded20481 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -285,13 +285,12 @@ public:
     ///
     /// Start of the tag that encloses the run, fills the info according to
     /// the value of pRedlineData.
-    void StartRedline(const SwRedlineData* pRedlineData, bool bLastRun,
-                      bool bParagraphProps = false);
+    void StartRedline(const SwRedlineData* pRedlineData, bool bParagraphProps 
= false);
 
     /// Output redlining.
     ///
     /// End of the tag that encloses the run.
-    void EndRedline(const SwRedlineData* pRedlineData, bool bLastRun, bool 
bParagraphProps = false);
+    void EndRedline(const SwRedlineData* pRedlineData, bool bParagraphProps = 
false);
 
     virtual bool IsFlyProcessingPostponed() override;
     virtual void ResetFlyProcessingFlag() override;

Reply via email to