sw/qa/extras/ooxmlexport/data/tdf170908_delText.docx |binary
 sw/qa/extras/ooxmlexport/ooxmlexport25.cxx           |   14 ++++++
 sw/source/filter/ww8/docxattributeoutput.cxx         |   40 ++++++++++---------
 sw/source/filter/ww8/docxattributeoutput.hxx         |    2 
 4 files changed, 37 insertions(+), 19 deletions(-)

New commits:
commit 8a34dc1659f78beab7d2e8cadc0cc4e11ca6113d
Author:     Justin Luth <[email protected]>
AuthorDate: Sat Feb 21 17:43:59 2026 -0500
Commit:     Miklos Vajna <[email protected]>
CommitDate: Wed Feb 25 09:25:03 2026 +0100

    tdf#170908 docx export: stack run's m_pRedlineData
    
    The cached m_pRedlineData was being overwritten
    by the runs in OutFlys.
    
    It should be safe to use a stack for export,
    since internally any run needs to both start and stop.
    
    There are some sdraw things that don't use start/endRun though.
    So what to do about them - is pushing a nullptr needed
    to prevent them from inheritting redline info?
    I believe so - otherwise it might use element XML_delText.
    
    make CppunitTest_sw_ooxmlexport25 \
        CPPUNIT_TEST_NAME=testTdf170908_delText
    
    Change-Id: Ic5f1d68da984a751ccf880718147df0dbc11c654
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199954
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Justin Luth <[email protected]>
    Reviewed-by: Miklos Vajna <[email protected]>

diff --git a/sw/qa/extras/ooxmlexport/data/tdf170908_delText.docx 
b/sw/qa/extras/ooxmlexport/data/tdf170908_delText.docx
new file mode 100644
index 000000000000..bfdd26bc6e0b
Binary files /dev/null and 
b/sw/qa/extras/ooxmlexport/data/tdf170908_delText.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
index 6df37dc9db60..fcbddda5444c 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
@@ -342,6 +342,20 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf167082)
     CPPUNIT_ASSERT_EQUAL(OUString("Heading 1"), aStyleName);
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf170908_delText)
+{
+    // Given a file with deleted text at the end of the paragraph just before 
anchored stuff
+    createSwDoc("tdf170908_delText.docx");
+
+    // When exporting that to 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", 1); // there is a delTree element
+    CPPUNIT_ASSERT_EQUAL(1, countXPathNodes(pXmlDoc, 
"//w:del/w:r/w:delText")); // must be in w:del
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testTdf170952_delText)
 {
     // Given a document with deleted text at the end of the paragraph
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index f89f73ca5385..c51b18ecf662 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -1828,7 +1828,7 @@ void DocxAttributeOutput::StartRun( const SwRedlineData* 
pRedlineData, sal_Int32
 {
     // Don't start redline data here, possibly there is a hyperlink later, and
     // that has to be started first.
-    m_pRedlineData = pRedlineData;
+    m_pRedlineData.push_back(pRedlineData);
 
     // this mark is used to be able to enclose the run inside a sdr tag.
     m_pSerializer->mark(Tag_StartRun_1);
@@ -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);
+            StartRedline(m_pRedlineData.back());
             StartField_Impl( pNode, nPos, *pIt, true );
-            EndRedline(m_pRedlineData);
+            EndRedline(m_pRedlineData.back());
 
             if (m_nHyperLinkCount.back() > 0)
                 ++m_nFieldsInHyperlink;
@@ -2011,16 +2011,17 @@ void DocxAttributeOutput::EndRun(const SwTextNode* 
pNode, sal_Int32 nPos, sal_In
     DoWritePermissionsStart();
 
     // Surround annotation references with redline start/end markup if we're 
inside a delete.
-    bool bHasAnnotationMarkReferencesInDel = !m_rAnnotationMarksEnd.empty() && 
m_pRedlineData
-                                             && m_pRedlineData->GetType() == 
RedlineType::Delete;
+    const bool bHasAnnotationMarkReferencesInDel
+        = !m_rAnnotationMarksEnd.empty() && m_pRedlineData.back()
+            && m_pRedlineData.back()->GetType() == RedlineType::Delete;
     if (bHasAnnotationMarkReferencesInDel)
     {
-        StartRedline(m_pRedlineData);
+        StartRedline(m_pRedlineData.back());
     }
     DoWriteAnnotationMarks();
     if (bHasAnnotationMarkReferencesInDel)
     {
-        EndRedline(m_pRedlineData);
+        EndRedline(m_pRedlineData.back());
     }
 
     // if there is some redlining in the document, output it
@@ -2038,7 +2039,7 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, 
sal_Int32 nPos, sal_In
 
     if (!bSkipRedline)
     {
-        StartRedline(m_pRedlineData);
+        StartRedline(m_pRedlineData.back());
     }
 
     if (m_closeHyperlinkInThisRun && m_nHyperLinkCount.back() > 0 && 
!m_hyperLinkAnchor.isEmpty()
@@ -2112,7 +2113,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);
+        EndRedline(m_pRedlineData.back());
     }
     DoWriteBookmarksEnd(m_rBookmarksEnd, false, true); // Write moverange 
bookmarks
 
@@ -2154,7 +2155,7 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, 
sal_Int32 nPos, sal_In
 
     if ( !m_bWritingField )
     {
-        m_pRedlineData = nullptr;
+        m_pRedlineData.back() = nullptr;
     }
 
     if ( m_closeHyperlinkInThisRun )
@@ -2227,11 +2228,12 @@ void DocxAttributeOutput::EndRun(const SwTextNode* 
pNode, sal_Int32 nPos, sal_In
         }
     }
 
-    if ( m_pRedlineData )
+    if (m_pRedlineData.back())
     {
-        EndRedline(m_pRedlineData);
-        m_pRedlineData = nullptr;
+        EndRedline(m_pRedlineData.back());
     }
+    assert(m_pRedlineData.size());
+    m_pRedlineData.pop_back();
 
     DoWriteBookmarksStart(m_rFinalBookmarksStart);
     DoWriteBookmarksEnd(m_rFinalBookmarksEnd); // Write all final bookmarks
@@ -3055,7 +3057,7 @@ void DocxAttributeOutput::DoWriteCmd( std::u16string_view 
rCmd )
     }
     // Write the Field command
     sal_Int32 nTextToken = XML_instrText;
-    if ( m_pRedlineData && m_pRedlineData->GetType() == RedlineType::Delete )
+    if (m_pRedlineData.size() && m_pRedlineData.back() && 
m_pRedlineData.back()->GetType() == RedlineType::Delete)
         nTextToken = XML_delInstrText;
 
     m_pSerializer->startElementNS(XML_w, nTextToken, FSNS(XML_xml, XML_space), 
"preserve");
@@ -4070,11 +4072,12 @@ void DocxAttributeOutput::RunText( const OUString& 
rText, rtl_TextEncoding /*eCh
             break;
         }
     }
-    bool bMoved = isInMoveBookmark && m_pRedlineData && 
m_pRedlineData->IsMoved() &&
+    const bool bHasRedlineData = m_pRedlineData.size() && 
m_pRedlineData.back();
+    const bool bMoved = isInMoveBookmark && bHasRedlineData && 
m_pRedlineData.back()->IsMoved() &&
                   // tdf#150166 save tracked moving around TOC as w:ins, w:del
                   SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == 
nullptr;
-
-    if (GetRedlineTypeForTextToken(m_pRedlineData) == RedlineType::Delete && 
!bMoved)
+    if (!bMoved && bHasRedlineData
+        && GetRedlineTypeForTextToken(m_pRedlineData.back()) == 
RedlineType::Delete)
     {
         nTextToken = XML_delText;
     }
@@ -6815,6 +6818,7 @@ void DocxAttributeOutput::WriteOutliner(const 
OutlinerParaObject& rParaObj)
 {
     const EditTextObject& rEditObj = rParaObj.GetTextObject();
     MSWord_SdrAttrIter aAttrIter( m_rExport, rEditObj, TXT_HFTXTBOX );
+    m_pRedlineData.push_back(nullptr);
 
     sal_Int32 nPara = rEditObj.GetParagraphCount();
 
@@ -6872,6 +6876,7 @@ void DocxAttributeOutput::WriteOutliner(const 
OutlinerParaObject& rParaObj)
         while( nCurrentPos < nEnd );
         EndParagraph(ww8::WW8TableNodeInfoInner::Pointer_t());
     }
+    m_pRedlineData.pop_back();
     m_pSerializer->endElementNS( XML_w, XML_txbxContent );
 }
 
@@ -10781,7 +10786,6 @@ DocxAttributeOutput::DocxAttributeOutput( DocxExport 
&rExport, const FSHelperPtr
       m_pFootnotesList( new ::docx::FootnotesList() ),
       m_pEndnotesList( new ::docx::FootnotesList() ),
       m_footnoteEndnoteRefTag( 0 ),
-      m_pRedlineData( nullptr ),
       m_nRedlineId( 0 ),
       m_bOpenedSectPr( false ),
       m_bHadSectPr(false),
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx 
b/sw/source/filter/ww8/docxattributeoutput.hxx
index a65bded20481..ca9db3e30ece 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -869,7 +869,7 @@ private:
     std::unique_ptr< const WW8_SepInfo > m_pSectionInfo;
 
     /// Redline data to remember in the text run.
-    const SwRedlineData *m_pRedlineData;
+    std::vector<const SwRedlineData*> m_pRedlineData;
 
     /// Id of the redline
     sal_Int32 m_nRedlineId;

Reply via email to