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 ad2782a447ee6162d98aa3a92484f7971f22863a
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Tue May 27 08:37:14 2025 +0200
Commit:     Caolán McNamara <caolan.mcnam...@collabora.com>
CommitDate: Tue May 27 10:46:03 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/+/185877
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com>

diff --git a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng 
b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
index 10023519509e..c4030642619e 100644
--- a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
@@ -4035,7 +4035,7 @@ 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
       </rng:optional>
   </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 39fdf63fb03c..55607e565f9b 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 82048aa836e4..b9a9f7677115 100644
--- a/sw/source/core/doc/DocumentRedlineManager.cxx
+++ b/sw/source/core/doc/DocumentRedlineManager.cxx
@@ -3595,10 +3595,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
@@ -3606,7 +3618,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 a8877bdc400c..5b8926c1af5c 100644
--- a/sw/source/core/inc/rolbck.hxx
+++ b/sw/source/core/inc/rolbck.hxx
@@ -130,6 +130,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 fdf3dc8f10d6..ba5ba3f9db6d 100644
--- a/sw/source/core/undo/rolbck.cxx
+++ b/sw/source/core/undo/rolbck.cxx
@@ -280,6 +280,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 f72f24c8f40a..93501c9f7b2f 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