schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng |    2 
 sw/qa/core/doc/doc.cxx                                      |   22 ++++++++
 sw/source/core/doc/DocumentRedlineManager.cxx               |   31 ++++++++++--
 sw/source/core/inc/rolbck.hxx                               |    1 
 sw/source/core/undo/rolbck.cxx                              |   19 +++++++
 sw/source/core/undo/undel.cxx                               |   11 ++++
 6 files changed, 82 insertions(+), 4 deletions(-)

New commits:
commit b6cba0119f8028b426f5eee55be9761971dbfdee
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Tue May 27 08:37:14 2025 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Tue May 27 14:51:55 2025 +0200

    tdf#166319 sw interdependent redlines: handle reject of ins-then-fmt's fmt
    
    The bugdoc has <ins>AA<format>BB</format>CC</ins> in it, rejecting the
    BB part resulted in <ins>AA</ins>BB<ins>CC</ins>, while the expected
    result would be to reject the underlying insert of BB.
    
    The problem is that the reject code is not aware that the format has an
    underlying insert, so the format gets rejected, and as a side effect the
    insert redline is gone, too.
    
    Fix this by extending sw::DocumentRedlineManager::RejectRedlineRange()
    to recognize this insert-then-format case: act like rejecting the
    insert, not like rejecting the format.
    
    The surrounding plain inserts are not yet rejected, though. Also update
    extend sw doc model xml dump code that was useful to see while working
    on this.
    
    Change-Id: I6718b9c306239837392ae7cd3f0d28aef6b4f71e
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185891
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng 
b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
index e03c3dd2d256..6144cfebf93b 100644
--- a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
@@ -4136,7 +4136,7 @@ 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
     </rng:element>
   </rng:define>
 
-  <!-- TODO(vmiklos) no proposal for insert/delete redline under another 
redline -->
+  <!-- https://lists.freedesktop.org/archives/libreoffice/2025-May/093327.html 
Hierarchical tracked changes -->
   <rng:define name="text-changed-region" combine="choice">
     <rng:element name="text:changed-region">
       <rng:ref name="text-changed-region-attr"/>
diff --git a/sw/qa/core/doc/doc.cxx b/sw/qa/core/doc/doc.cxx
index 6488b9d12500..2e0497aa76da 100644
--- a/sw/qa/core/doc/doc.cxx
+++ b/sw/qa/core/doc/doc.cxx
@@ -821,6 +821,28 @@ CPPUNIT_TEST_FIXTURE(SwCoreDocTest, testInsThenFormat)
     // - Actual  : 3
     // i.e. the insert redlines before/after BBB were not accepted.
     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
+
+    // And when rejecting the format-on-insert:
+    pWrtShell->Undo();
+    CPPUNIT_ASSERT_EQUAL(u"AAABBBCCC"_ustr, pTextNode->GetText());
+    // Undo() creates a new cursor.
+    pCursor = pWrtShell->GetCursor();
+    pCursor->DeleteMark();
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    // Move inside "BBB".
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, 
/*bBasicCall=*/false);
+    nRedline = 0;
+    rRedlines.FindAtPosition(*pCursor->Start(), nRedline);
+    // A redline is found.
+    CPPUNIT_ASSERT_LESS(rRedlines.size(), nRedline);
+    pWrtShell->RejectRedline(nRedline);
+
+    // Then make sure the format-on-insert is rejected, i.e. BBB is not in the 
text anymore:
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: AAACCC
+    // - Actual  : AAABBBCCC
+    // i.e. only the format part of BBB was rejected, it wasn't removed from 
the document.
+    CPPUNIT_ASSERT_EQUAL(u"AAACCC"_ustr, pTextNode->GetText());
 }
 
 CPPUNIT_TEST_FIXTURE(SwCoreDocTest, testDelThenFormat)
diff --git a/sw/source/core/doc/DocumentRedlineManager.cxx 
b/sw/source/core/doc/DocumentRedlineManager.cxx
index 8501838444c2..557380025ba4 100644
--- a/sw/source/core/doc/DocumentRedlineManager.cxx
+++ b/sw/source/core/doc/DocumentRedlineManager.cxx
@@ -3594,10 +3594,22 @@ bool 
DocumentRedlineManager::RejectRedlineRange(SwRedlineTable::size_type nPosOr
         }
         else if (pTmp->GetRedlineData(0).CanCombineForAcceptReject(aOrigData))
         {
+            bool bFormatOnInsert = pTmp->GetType() == RedlineType::Format
+                                   && pTmp->GetStackCount() > 1
+                                   && pTmp->GetType(1) == RedlineType::Insert;
             if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
             {
-                std::unique_ptr<SwUndoRejectRedline> pUndoRdl
-                    = std::make_unique<SwUndoRejectRedline>(*pTmp);
+                std::unique_ptr<SwUndoRedline> pUndoRdl;
+                if (bFormatOnInsert)
+                {
+                    // Format on insert: this is rejected by accepting the 
format + deleting the
+                    // range.
+                    pUndoRdl = std::make_unique<SwUndoAcceptRedline>(*pTmp);
+                }
+                else
+                {
+                    pUndoRdl = std::make_unique<SwUndoRejectRedline>(*pTmp);
+                }
 #if OSL_DEBUG_LEVEL > 0
                 pUndoRdl->SetRedlineCountDontCheck(true);
 #endif
@@ -3605,7 +3617,20 @@ bool 
DocumentRedlineManager::RejectRedlineRange(SwRedlineTable::size_type nPosOr
             }
             nPamEndtNI = pTmp->Start()->GetNodeIndex();
             nPamEndCI = pTmp->Start()->GetContentIndex();
-            bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, bCallDelete);
+
+            if (bFormatOnInsert)
+            {
+                // Accept the format itself and then reject the insert by 
deleting the range.
+                SwPaM aPam(*pTmp->Start(), *pTmp->End());
+                bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, 
bCallDelete);
+                // Handles undo/redo itself.
+                m_rDoc.getIDocumentContentOperations().DeleteRange(aPam);
+            }
+            else
+            {
+                bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, 
bCallDelete);
+            }
+
             nRdlIdx++; //we will decrease it in the loop anyway.
         }
         else if (CanCombineTypesForAcceptReject(aOrigData, *pTmp)
diff --git a/sw/source/core/inc/rolbck.hxx b/sw/source/core/inc/rolbck.hxx
index 111b752e3781..a96712fe305e 100644
--- a/sw/source/core/inc/rolbck.hxx
+++ b/sw/source/core/inc/rolbck.hxx
@@ -129,6 +129,7 @@ public:
     virtual ~SwHistorySetText() override;
     virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override;
 
+    void dumpAsXml(xmlTextWriterPtr pWriter) const override;
 };
 
 class SwHistorySetTextField final : public SwHistoryHint
diff --git a/sw/source/core/undo/rolbck.cxx b/sw/source/core/undo/rolbck.cxx
index ad90afca7dae..7c2634932da3 100644
--- a/sw/source/core/undo/rolbck.cxx
+++ b/sw/source/core/undo/rolbck.cxx
@@ -282,6 +282,25 @@ void SwHistorySetText::SetInDoc( SwDoc* pDoc, bool )
     }
 }
 
+void SwHistorySetText::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwHistorySetText"));
+    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("node-index"),
+                                      
BAD_CAST(OString::number(sal_Int32(m_nNodeIndex)).getStr()));
+    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("start"),
+                                      
BAD_CAST(OString::number(sal_Int32(m_nStart)).getStr()));
+    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("end"),
+                                      
BAD_CAST(OString::number(sal_Int32(m_nEnd)).getStr()));
+    SwHistoryHint::dumpAsXml(pWriter);
+
+    if (m_pAttr)
+    {
+        m_pAttr->dumpAsXml(pWriter);
+    }
+
+    (void)xmlTextWriterEndElement(pWriter);
+}
+
 SwHistorySetTextField::SwHistorySetTextField( const SwTextField* pTextField, 
SwNodeOffset nNodePos )
     : SwHistoryHint( HSTRY_SETTXTFLDHNT )
     , m_pField( new SwFormatField( *pTextField->GetFormatField().GetField() ) )
diff --git a/sw/source/core/undo/undel.cxx b/sw/source/core/undo/undel.cxx
index 8e6caf96d426..6d71d440ef1d 100644
--- a/sw/source/core/undo/undel.cxx
+++ b/sw/source/core/undo/undel.cxx
@@ -1376,6 +1376,17 @@ void SwUndoDelete::dumpAsXml(xmlTextWriterPtr pWriter) 
const
 {
     (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoDelete"));
     (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", 
this);
+    if (m_aSttStr)
+    {
+        (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("stt-str"),
+                                          
BAD_CAST(m_aSttStr->toUtf8().getStr()));
+    }
+    if (m_aEndStr)
+    {
+        (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("end-str"),
+                                          
BAD_CAST(m_aEndStr->toUtf8().getStr()));
+    }
+
     SwUndo::dumpAsXml(pWriter);
     SwUndoSaveContent::dumpAsXml(pWriter);
     (void)xmlTextWriterEndElement(pWriter);

Reply via email to