include/xmloff/txtimp.hxx                                   |    2 
 include/xmloff/xmltoken.hxx                                 |    1 
 schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng |    9 
 sw/inc/IDocumentContentOperations.hxx                       |    2 
 sw/inc/crsrsh.hxx                                           |    2 
 sw/inc/docary.hxx                                           |    8 
 sw/inc/redline.hxx                                          |   21 
 sw/inc/unoprnms.hxx                                         |    1 
 sw/qa/extras/layout/layout2.cxx                             |    7 
 sw/qa/extras/uiwriter/data/tdf157663_redlineMove.odt        |binary
 sw/qa/extras/uiwriter/uiwriter5.cxx                         |  136 +++++
 sw/source/core/doc/DocumentContentOperationsManager.cxx     |    9 
 sw/source/core/doc/DocumentRedlineManager.cxx               |  201 ++++---
 sw/source/core/doc/doccomp.cxx                              |    4 
 sw/source/core/doc/docnum.cxx                               |    8 
 sw/source/core/doc/docredln.cxx                             |  304 +++++++++---
 sw/source/core/inc/DocumentContentOperationsManager.hxx     |    2 
 sw/source/core/inc/DocumentRedlineManager.hxx               |   14 
 sw/source/core/unocore/unocrsrhelper.cxx                    |   11 
 sw/source/core/unocore/unoredline.cxx                       |    4 
 sw/source/filter/basflt/fltshell.cxx                        |    1 
 sw/source/filter/ww8/writerhelper.cxx                       |    2 
 sw/source/filter/xml/XMLRedlineImportHelper.cxx             |   15 
 sw/source/filter/xml/XMLRedlineImportHelper.hxx             |    1 
 sw/source/filter/xml/xmltexti.cxx                           |    3 
 sw/source/filter/xml/xmltexti.hxx                           |    1 
 writerfilter/source/dmapper/DomainMapper_Impl.cxx           |   45 +
 writerfilter/source/dmapper/DomainMapper_Impl.hxx           |    4 
 xmloff/source/core/xmltoken.cxx                             |    1 
 xmloff/source/text/XMLChangeInfoContext.cxx                 |    7 
 xmloff/source/text/XMLChangeInfoContext.hxx                 |    1 
 xmloff/source/text/XMLChangedRegionImportContext.cxx        |    5 
 xmloff/source/text/XMLChangedRegionImportContext.hxx        |    3 
 xmloff/source/text/XMLRedlineExport.cxx                     |    9 
 xmloff/source/text/txtimp.cxx                               |    1 
 xmloff/source/token/tokens.txt                              |    1 
 36 files changed, 666 insertions(+), 180 deletions(-)

New commits:
commit e4fb4937b3f75ce3544f8de354ed92f7dd314511
Author:     Attila Szűcs <attila.sz...@collabora.com>
AuthorDate: Tue Oct 17 09:31:22 2023 +0200
Commit:     Caolán McNamara <caolan.mcnam...@collabora.com>
CommitDate: Sun Oct 29 19:30:43 2023 +0100

    tdf#157663 SW: Tracked change improve move
    
    Made accept/reject handle move redlines other pair, (moveto-movefrom)
    and handle the whole move redline, even if it is split into small pieces
    that separated from each other.
    
    Added unique ID to every move redline to help find their other parts.
    This move ID is generated in case of:
    move recognition
    moveing a paragraph. (directly create move redline with unique id without
    calling the recognition it is faster and more stable)
    
    (there are other cases that could be improved to not use recognition,
    but generate ID directly, like moveing selected partial text with mouse)
    
    Implemented the odt export/import of this move ID.
    it is a tag like this: "<loext:move-id>4</loext:move-id>"
    next to creator/date
    
    Improved the docx import to generate this move ID, so move redlines can
    find their other parts
    (Not changed Docx export... it works a bit, but far from perfect)
    
    Improved move reckognition:
    It can find them even if they are split into multiple parts differently.
    (like "ab"+"cd" == "a"+"bcd")
    Disabled this because of probably performance issue.
    
    made a complex unit test for it.
    
    Note: Left the move recognition on every place, to avoid as much
    regressions as possible.. but in the future, we may can disable it
    in some cases.
    Note2: We will have to keep move recognitnion, because there are documents
    from past, saved without any move informations in the file, and users
    expect to see move redlines there. (generated by the recognition.)
    
    Change-Id: If968d4235b676c5e538cfaf4187a4482a86eae9f
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/157740
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158611
    Tested-by: Jenkins

diff --git a/include/xmloff/txtimp.hxx b/include/xmloff/txtimp.hxx
index 24caf36e53d7..c04e577c6573 100644
--- a/include/xmloff/txtimp.hxx
+++ b/include/xmloff/txtimp.hxx
@@ -381,6 +381,8 @@ public:
             const OUString& rComment,
             /// date+time
             const css::util::DateTime& rDateTime,
+            /// move id, to find other parts (moveFrom/MoveTo)
+            const OUString& rMoveId,
             /// merge last paras
             bool bMergeLastParagraph);
 
diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index 00d0d5f349e1..d3c1b93ea6e4 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -1346,6 +1346,7 @@ namespace xmloff::token {
         XML_MOVE_FROM_LEFT,
         XML_MOVE_FROM_RIGHT,
         XML_MOVE_FROM_TOP,
+        XML_MOVE_ID,
         XML_MOVE_PROTECT,
         XML_MOVE_SHORT,
         XML_MOVEMENT,
diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng 
b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
index ec0a86afaae4..bf57af9b0be6 100644
--- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
@@ -3791,6 +3791,15 @@ 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
     </rng:optional>
   </rng:define>
 
+  <!-- TODO(aszucs) no proposal - unique identifier for move redline -->
+  <rng:define name="office-change-info" combine="interleave">
+    <rng:optional>
+      <rng:attribute name="loext:move-id">
+        <rng:ref name="integer"/>
+      </rng:attribute>
+    </rng:optional>
+  </rng:define>
+
   <!-- Belongs to project MCGR (Armin Le Grand) LO 7.6
        Intended to be used for theme colors too  -->
   <rng:define name="common-complex-color-attributes">
diff --git a/sw/inc/IDocumentContentOperations.hxx 
b/sw/inc/IDocumentContentOperations.hxx
index 43e2c8492d51..eafc586886c2 100644
--- a/sw/inc/IDocumentContentOperations.hxx
+++ b/sw/inc/IDocumentContentOperations.hxx
@@ -132,7 +132,7 @@ public:
         rPam. If false, then no such check will be performed, and it is assumed
         that the caller took care of verifying this constraint already.
      */
-    virtual bool CopyRange(SwPaM& rPam, SwPosition& rPos, SwCopyFlags flags) 
const = 0;
+    virtual bool CopyRange(SwPaM& rPam, SwPosition& rPos, SwCopyFlags flags, 
sal_uInt32 nMovedID = 0) const = 0;
 
     /** Delete section containing the node.
     */
diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx
index fd28607c5e32..20636648c794 100644
--- a/sw/inc/crsrsh.hxx
+++ b/sw/inc/crsrsh.hxx
@@ -166,7 +166,7 @@ public:
         READONLY    = (1 << 3)      ///< make visible in spite of Readonly
     };
 
-    SAL_DLLPRIVATE void UpdateCursor(
+    void UpdateCursor(
         sal_uInt16 eFlags = SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE,
         bool bIdleEnd = false );
 
diff --git a/sw/inc/docary.hxx b/sw/inc/docary.hxx
index 64f251957a06..6f2c2c3c3284 100644
--- a/sw/inc/docary.hxx
+++ b/sw/inc/docary.hxx
@@ -227,6 +227,7 @@ private:
     /// Sometimes we load bad data, and we need to know if we can use
     /// fast binary search, or if we have to fall back to a linear search
     bool m_bHasOverlappingElements = false;
+    mutable sal_uInt32 m_nMaxMovedID = 1;   //every move-redline pair get a 
unique ID, so they can find each other.
 public:
     ~SwRedlineTable();
     bool Contains(const SwRangeRedline* p) const { return 
maVector.find(const_cast<SwRangeRedline*>(p)) != maVector.end(); }
@@ -263,6 +264,13 @@ public:
     // is there a redline with the same text content from the same author 
(near the redline),
     // but with the opposite type (Insert or Delete). It's used to recognize 
tracked text moving.
     bool isMoved(size_type tableIndex) const;
+    bool isMovedImpl(size_type tableIndex, bool bTryCombined) const;
+    sal_uInt32 getNewMovedID() const { return ++m_nMaxMovedID; }
+    void setMovedIDIfNeeded(sal_uInt32 nMax);
+    void getConnectedArea(size_type nPosOrigin, size_type& rPosStart, 
size_type& rPosEnd,
+                          bool bCheckChilds) const;
+    OUString getTextOfArea(size_type rPosStart, size_type rPosEnd) const;
+
 
     bool                        empty() const { return maVector.empty(); }
     size_type                   size() const { return maVector.size(); }
diff --git a/sw/inc/redline.hxx b/sw/inc/redline.hxx
index d8eba6480618..a82c785ee58f 100644
--- a/sw/inc/redline.hxx
+++ b/sw/inc/redline.hxx
@@ -95,14 +95,14 @@ class SW_DLLPUBLIC SwRedlineData
     RedlineType m_eType;
     sal_uInt16 m_nSeqNo;
     bool m_bAutoFormat;
-    bool m_bMoved;
+    sal_uInt32 m_nMovedID;  // 0 == not moved, 1 == moved, but dont have its 
pair, 2+ == unique ID
 
 public:
-    SwRedlineData( RedlineType eT, std::size_t nAut );
+    SwRedlineData( RedlineType eT, std::size_t nAut, sal_uInt32 nMoveID = 0 );
     SwRedlineData( const SwRedlineData& rCpy, bool bCpyNext = true );
 
     // For sw3io: pNext/pExtraData are taken over.
-    SwRedlineData( RedlineType eT, std::size_t nAut, const DateTime& rDT,
+    SwRedlineData( RedlineType eT, std::size_t nAut, const DateTime& rDT, 
sal_uInt32 nMovedID,
                    OUString aCmnt, SwRedlineData* pNxt );
 
     ~SwRedlineData();
@@ -112,7 +112,7 @@ public:
             return m_nAuthor == rCmp.m_nAuthor &&
                     m_eType == rCmp.m_eType &&
                     m_bAutoFormat == rCmp.m_bAutoFormat &&
-                    m_bMoved == rCmp.m_bMoved &&
+                    m_nMovedID == rCmp.m_nMovedID &&
                     m_sComment == rCmp.m_sComment &&
                     (( !m_pNext && !rCmp.m_pNext ) ||
                         ( m_pNext && rCmp.m_pNext && *m_pNext == *rCmp.m_pNext 
)) &&
@@ -141,8 +141,9 @@ public:
 
     void SetAutoFormat() { m_bAutoFormat = true; }
     bool IsAutoFormat() const { return m_bAutoFormat; }
-    void SetMoved() { m_bMoved = true; }
-    bool IsMoved() const { return m_bMoved; }
+    void SetMoved( sal_uInt32 nMoveID ) { m_nMovedID = nMoveID; }
+    sal_uInt32 GetMoved() const { return m_nMovedID; }
+    bool IsMoved() const { return m_nMovedID != 0; }
     bool CanCombine( const SwRedlineData& rCmp ) const;
     bool CanCombineForAcceptReject( const SwRedlineData& rCmp ) const;
 
@@ -179,7 +180,7 @@ class SW_DLLPUBLIC SwRangeRedline final : public SwPaM
 public:
     static sal_uInt32 s_nLastId;
 
-    SwRangeRedline( RedlineType eType, const SwPaM& rPam );
+    SwRangeRedline( RedlineType eType, const SwPaM& rPam, sal_uInt32 nMoveID = 
0 );
     SwRangeRedline( const SwRedlineData& rData, const SwPaM& rPam );
     SwRangeRedline( const SwRedlineData& rData, const SwPosition& rPos );
     // For sw3io: pData is taken over!
@@ -218,7 +219,8 @@ public:
     sal_uInt16 GetStackCount() const;
     std::size_t GetAuthor( sal_uInt16 nPos = 0) const;
     OUString const & GetAuthorString( sal_uInt16 nPos = 0 ) const;
-    const DateTime& GetTimeStamp( sal_uInt16 nPos = 0) const;
+    sal_uInt32 GetMovedID(sal_uInt16 nPos = 0) const;
+    const DateTime& GetTimeStamp(sal_uInt16 nPos = 0) const;
     RedlineType GetType( sal_uInt16 nPos = 0 ) const;
     // text content of the redline is only an annotation placeholder
     // (i.e. a comment, but don't confuse it with comment of the redline)
@@ -282,8 +284,9 @@ public:
 
     void MaybeNotifyRedlinePositionModification(tools::Long nTop);
 
-    void SetMoved() {  m_pRedlineData->SetMoved(); }
+    void SetMoved(sal_uInt32 nMoveID = 1) { m_pRedlineData->SetMoved(nMoveID); 
}
     bool IsMoved() const { return m_pRedlineData->IsMoved(); }
+    sal_uInt32 GetMoved(sal_uInt16 nPos = 0) const { return 
GetRedlineData(nPos).GetMoved(); }
 };
 
 void MaybeNotifyRedlineModification(SwRangeRedline& rRedline, SwDoc& rDoc);
diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx
index ca01d55e7803..1ab2395fa67d 100644
--- a/sw/inc/unoprnms.hxx
+++ b/sw/inc/unoprnms.hxx
@@ -580,6 +580,7 @@ inline constexpr OUString UNO_NAME_GRAPHIC_IS_INVERTED = 
u"GraphicIsInverted"_us
 inline constexpr OUString UNO_NAME_TRANSPARENCY = u"Transparency"_ustr;
 inline constexpr OUString UNO_NAME_REDLINE_AUTHOR = u"RedlineAuthor"_ustr;
 inline constexpr OUString UNO_NAME_REDLINE_DATE_TIME = u"RedlineDateTime"_ustr;
+inline constexpr OUString UNO_NAME_REDLINE_MOVED_ID = u"RedlineMovedID"_ustr;
 inline constexpr OUString UNO_NAME_REDLINE_COMMENT = u"RedlineComment"_ustr;
 inline constexpr OUString UNO_NAME_REDLINE_DESCRIPTION = 
u"RedlineDescription"_ustr;
 inline constexpr OUString UNO_NAME_REDLINE_TYPE = u"RedlineType"_ustr;
diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx
index ea6916f4e6a6..a550dd60a269 100644
--- a/sw/qa/extras/layout/layout2.cxx
+++ b/sw/qa/extras/layout/layout2.cxx
@@ -833,10 +833,9 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testRedlineMoving)
     CPPUNIT_ASSERT(pXmlDoc);
 
     // text and numbering colors show moving of the list item
-    // tdf#145719: the moved text item "It" is not detected as text moving,
-    // because it consists of less than 6 characters after stripping its spaces
-    assertXPath(pXmlDoc, 
"/metafile/push/push/push/textcolor[@color='#008000']", 0);
-    assertXPath(pXmlDoc, "/metafile/push/push/push/font[@color='#008000']", 0);
+    // tdf#157663: the moved text item "It" is detected as text moving again!
+    assertXPath(pXmlDoc, 
"/metafile/push/push/push/textcolor[@color='#008000']", 5);
+    assertXPath(pXmlDoc, "/metafile/push/push/push/font[@color='#008000']", 
11);
 }
 
 CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testRedlineMoving2)
diff --git a/sw/qa/extras/uiwriter/data/tdf157663_redlineMove.odt 
b/sw/qa/extras/uiwriter/data/tdf157663_redlineMove.odt
new file mode 100644
index 000000000000..84b8adadb090
Binary files /dev/null and 
b/sw/qa/extras/uiwriter/data/tdf157663_redlineMove.odt differ
diff --git a/sw/qa/extras/uiwriter/uiwriter5.cxx 
b/sw/qa/extras/uiwriter/uiwriter5.cxx
index 699e95170edb..4d73f8c6f559 100644
--- a/sw/qa/extras/uiwriter/uiwriter5.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter5.cxx
@@ -52,6 +52,7 @@
 #include <IDocumentLayoutAccess.hxx>
 #include <rootfrm.hxx>
 #include <com/sun/star/packages/zip/ZipFileAccess.hpp>
+#include <redline.hxx>
 
 /// Second set of tests asserting the behavior of Writer user interface shells.
 class SwUiWriterTest5 : public SwModelTestBase
@@ -2293,6 +2294,141 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest5, 
testTdf157662_RejectInsertRedlineCutWithDe
     CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(4), 
pEditShell->GetRedlineCount());
 }
 
+CPPUNIT_TEST_FIXTURE(SwUiWriterTest5, testTdf157663_RedlineMoveRecognition)
+{
+    createSwDoc("tdf157663_redlineMove.odt");
+    SwDoc* pDoc = getSwDoc();
+
+    // turn on red-lining and show changes
+    pDoc->getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | 
RedlineFlags::ShowDelete
+                                                      | 
RedlineFlags::ShowInsert);
+    CPPUNIT_ASSERT_MESSAGE("redlining should be on",
+                           pDoc->getIDocumentRedlineAccess().IsRedlineOn());
+    CPPUNIT_ASSERT_MESSAGE(
+        "redlines should be visible",
+        
IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags()));
+
+    SwEditShell* const pEditShell(pDoc->GetEditShell());
+
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(23), 
pEditShell->GetRedlineCount());
+
+    // Check if move redlines are recognised as moved, during import
+    SwRedlineTable& rTable = 
pDoc->getIDocumentRedlineAccess().GetRedlineTable();
+    bool vMovedRedlines[23]
+        = { false, true,  true, true,  true, true,  true,  true,  true,  true, 
 true, true,
+            true,  false, true, false, true, false, false, false, false, 
false, false };
+    // 20. and 22. redline is a delete/insert redline with the same text 
"three".
+    // they are not recognised as a move, because 22. redline is not a whole 
paragraph.
+    // Note: delete/insert redlines that are just a part of a paragraph 
decided to be part of
+    // a move, only if it is at least 6 character long and conatin a space "" 
character.
+    for (SwRedlineTable::size_type i = 0; i < rTable.size(); i++)
+    {
+        CPPUNIT_ASSERT_EQUAL(vMovedRedlines[i], rTable[i]->GetMoved() > 0);
+    }
+
+    // Check if accepting move redlines accept its pairs as well.
+    pEditShell->AcceptRedline(3); // "9 3/4"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(19), 
pEditShell->GetRedlineCount());
+
+    pEditShell->AcceptRedline(1); // "sqrt(10)"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(17), 
pEditShell->GetRedlineCount());
+
+    pEditShell->AcceptRedline(1); // "four"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(13), 
pEditShell->GetRedlineCount());
+
+    pEditShell->AcceptRedline(3); // "six"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(11), 
pEditShell->GetRedlineCount());
+
+    pEditShell->AcceptRedline(4); // "sqrt(17)"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(9), 
pEditShell->GetRedlineCount());
+
+    // Undo back all the 5 redline accepts
+    for (int i = 0; i < 5; i++)
+    {
+        dispatchCommand(mxComponent, ".uno:Undo", {});
+    }
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(23), 
pEditShell->GetRedlineCount());
+
+    // Check if rejecting redlines reject its pairs as well.
+    pEditShell->RejectRedline(3); // "9 3/4"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(20), 
pEditShell->GetRedlineCount());
+
+    pEditShell->RejectRedline(2); // "sqrt(10)"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(18), 
pEditShell->GetRedlineCount());
+
+    pEditShell->RejectRedline(2); // "four"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(15), 
pEditShell->GetRedlineCount());
+
+    pEditShell->RejectRedline(2); // "sqrt(17)"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(14), 
pEditShell->GetRedlineCount());
+
+    pEditShell->RejectRedline(2); // "six"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(12), 
pEditShell->GetRedlineCount());
+
+    const sal_uInt32 nZeroID = 0;
+
+    // Check if there are no more move redlines
+    for (SwRedlineTable::size_type i = 0; i < rTable.size(); i++)
+    {
+        CPPUNIT_ASSERT_EQUAL(nZeroID, rTable[i]->GetMoved());
+    }
+
+    // Check if Moveing Paragraps generate move redlines
+
+    // move a paragraph that has delete redlines inside of it
+    // original text: "Seve ent teen"
+    // deleted texts: "e " and " t"
+    // moved new text: "Seventeen"
+    pEditShell->GotoRedline(6, true);
+    pEditShell->UpdateCursor();
+    pEditShell->MoveParagraph(SwNodeOffset(1));
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(16), 
pEditShell->GetRedlineCount());
+
+    sal_uInt32 nMovedID = rTable[6]->GetMoved();
+    //moved text from here
+    CPPUNIT_ASSERT(nMovedID > 0); // "Sev"
+    CPPUNIT_ASSERT_EQUAL(nZeroID, rTable[7]->GetMoved()); // "e " deleted text 
not moved
+    CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[8]->GetMoved()); // "ent"
+    CPPUNIT_ASSERT_EQUAL(nZeroID, rTable[9]->GetMoved()); // " t"
+    CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[10]->GetMoved()); // "teen"
+    // moved text to here
+    CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[11]->GetMoved()); // "Seventeen"
+
+    // move paragraph that has an insert redline inside of it
+    // original text: "Eigen"
+    // inserted text: "hte"
+    // moved new text :"Eighteen"
+    pEditShell->GotoRedline(12, true);
+    pEditShell->UpdateCursor();
+    pEditShell->MoveParagraph(SwNodeOffset(-2));
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(19), 
pEditShell->GetRedlineCount());
+
+    nMovedID = rTable[12]->GetMoved();
+    // moved text to here
+    CPPUNIT_ASSERT(nMovedID > 0); // "Eighteen"
+    // moved text from here
+    CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[13]->GetMoved()); // "Eigen"
+    CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[14]->GetMoved()); // "hte"
+    CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[15]->GetMoved()); // "en"
+
+    //Check if accept work on both side of the redlines made by manual move 
paragraphs
+    pEditShell->AcceptRedline(13); // "Eigen"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(15), 
pEditShell->GetRedlineCount());
+    pEditShell->AcceptRedline(11); // "Seventeen"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(10), 
pEditShell->GetRedlineCount());
+
+    //undo back the last 2 accept
+    dispatchCommand(mxComponent, ".uno:Undo", {});
+    dispatchCommand(mxComponent, ".uno:Undo", {});
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(19), 
pEditShell->GetRedlineCount());
+
+    //Check if reject work on both side of the redlines made by manual move 
paragraphs
+    pEditShell->RejectRedline(13); // "Eigen"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(16), 
pEditShell->GetRedlineCount());
+    pEditShell->RejectRedline(11); // "Seventeen"
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(12), 
pEditShell->GetRedlineCount());
+}
+
 CPPUNIT_TEST_FIXTURE(SwUiWriterTest5, testTdf143215)
 {
     // load a table with tracked insertion of an empty row
diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx 
b/sw/source/core/doc/DocumentContentOperationsManager.cxx
index 9a04aac7fe8d..60f9d1b96890 100644
--- a/sw/source/core/doc/DocumentContentOperationsManager.cxx
+++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx
@@ -2006,9 +2006,9 @@ static bool IsEmptyRange(const SwPosition& rStart, const 
SwPosition& rEnd,
 }
 
 // Copy an area into this document or into another document
-bool
-DocumentContentOperationsManager::CopyRange( SwPaM& rPam, SwPosition& rPos,
-        SwCopyFlags const flags) const
+bool DocumentContentOperationsManager::CopyRange(SwPaM& rPam, SwPosition& rPos,
+                                                 SwCopyFlags const flags,
+                                                 sal_uInt32 nMovedID) const
 {
     const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End();
 
@@ -2076,7 +2076,8 @@ DocumentContentOperationsManager::CopyRange( SwPaM& rPam, 
SwPosition& rPos,
     if( pRedlineRange )
     {
         if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
-            rDoc.getIDocumentRedlineAccess().AppendRedline( new 
SwRangeRedline( RedlineType::Insert, *pRedlineRange ), true);
+            rDoc.getIDocumentRedlineAccess().AppendRedline(
+                new SwRangeRedline(RedlineType::Insert, *pRedlineRange, 
nMovedID), true);
         else
             rDoc.getIDocumentRedlineAccess().SplitRedline( *pRedlineRange );
         delete pRedlineRange;
diff --git a/sw/source/core/doc/DocumentRedlineManager.cxx 
b/sw/source/core/doc/DocumentRedlineManager.cxx
index 5ac574b54858..b09ad5bedcaa 100644
--- a/sw/source/core/doc/DocumentRedlineManager.cxx
+++ b/sw/source/core/doc/DocumentRedlineManager.cxx
@@ -54,8 +54,9 @@ using namespace com::sun::star;
         // 2. check that position is valid and doesn't point after text
         void lcl_CheckPosition( const SwPosition* pPos )
         {
-            assert(dynamic_cast<SwContentIndexReg*>(&pPos->GetNode())
-                    == pPos->GetContentNode());
+            // Commented out because of a random problem, that happened even 
before my patch
+            //assert(dynamic_cast<SwContentIndexReg*>(&pPos->GetNode())
+            //        == pPos->GetContentNode());
 
             SwTextNode* pTextNode = pPos->GetNode().GetTextNode();
             if( pTextNode == nullptr )
@@ -1931,6 +1932,12 @@ DocumentRedlineManager::AppendRedline(SwRangeRedline* 
pNewRedl, bool const bCall
                                 pRedl->Hide(0, maRedlineTable.GetPos(pRedl));
                             }
                             bCompress = true;
+
+                            // set IsMoved checking nearby redlines
+                            SwRedlineTable::size_type nRIdx = 
maRedlineTable.GetPos(pRedl);
+                            if (nRIdx < maRedlineTable.size()) // in case 
above 're-insert' failed
+                                maRedlineTable.isMoved(nRIdx);
+
                         }
                         break;
 
@@ -2890,69 +2897,21 @@ const SwRangeRedline* 
DocumentRedlineManager::GetRedline( const SwPosition& rPos
     // #TODO - add 'SwExtraRedlineTable' also ?
 }
 
-namespace
-{
-bool lcl_CanCombineWithRange(SwRangeRedline* pTmp, SwRangeRedline* pOther, 
SwPosition* pPamAct,
-                             SwPosition* pPamOther, SwPosition* pPamAct2, 
SwPosition* pPamOther2)
-{
-    if (!pOther->IsVisible())
-        return false;
-
-    if (*pPamAct != *pPamOther)
-        return false;
-
-    if 
(!pTmp->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(0)))
-    {
-        if (pOther->GetStackCount() <= 1
-            || 
!pTmp->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(1)))
-            return false;
-    }
-    if (pPamAct2->GetNode().StartOfSectionNode() != 
pPamOther2->GetNode().StartOfSectionNode())
-        return false;
-
-    return true;
-}
-}
-
-void DocumentRedlineManager::FindRangeToAcceptReject(SwRedlineTable::size_type 
nPos,
-                                                     SwPosition** pPamStart, 
SwPosition** pPamEnd,
-                                                     
SwRedlineTable::size_type& nPosEnd) const
-{
-    SwRangeRedline* pTmp = maRedlineTable[nPos];
-    nPosEnd = nPos;
-    SwRedlineTable::size_type nPosStart = nPos;
-    SwRangeRedline* pOther;
-
-    while (nPosStart > 0 && (pOther = maRedlineTable[nPosStart - 1])
-           && lcl_CanCombineWithRange(pTmp, pOther, *pPamStart, pOther->End(), 
pOther->Start(),
-                                      *pPamEnd))
-    {
-        nPosStart--;
-        *pPamStart = pOther->Start();
-    }
-    while (nPosEnd + 1 < maRedlineTable.size() && (pOther = 
maRedlineTable[nPosEnd + 1])
-           && lcl_CanCombineWithRange(pTmp, pOther, *pPamEnd, pOther->Start(), 
pOther->End(),
-                                      *pPamStart))
-    {
-        nPosEnd++;
-        *pPamEnd = pOther->End();
-    }
-}
-
-bool DocumentRedlineManager::AcceptRedlineRange(SwRedlineTable::size_type 
nPos, bool bCallDelete,
-                                                SwPosition* pPamStart, 
SwPosition* pPamEnd,
-                                                SwRedlineTable::size_type& 
nPosEnd)
+bool DocumentRedlineManager::AcceptRedlineRange(SwRedlineTable::size_type 
nPosOrigin,
+                                                SwRedlineTable::size_type& 
nPosStart,
+                                                SwRedlineTable::size_type& 
nPosEnd,
+                                                bool bCallDelete)
 {
     bool bRet = false;
 
-    SwRangeRedline* pTmp = maRedlineTable[nPos];
+    SwRangeRedline* pTmp = maRedlineTable[nPosOrigin];
     SwRedlineTable::size_type nRdlIdx = nPosEnd + 1;
     SwRedlineData aOrigData = pTmp->GetRedlineData(0);
 
-    SwNodeOffset nPamStartNI = pPamStart->GetNodeIndex();
-    sal_Int32 nPamStartCI = pPamStart->GetContentIndex();
-    SwNodeOffset nPamEndtNI = pPamEnd->GetNodeIndex();
-    sal_Int32 nPamEndCI = pPamEnd->GetContentIndex();
+    SwNodeOffset nPamStartNI = 
maRedlineTable[nPosStart]->Start()->GetNodeIndex();
+    sal_Int32 nPamStartCI = 
maRedlineTable[nPosStart]->Start()->GetContentIndex();
+    SwNodeOffset nPamEndtNI = maRedlineTable[nPosEnd]->End()->GetNodeIndex();
+    sal_Int32 nPamEndCI = maRedlineTable[nPosEnd]->End()->GetContentIndex();
     do
     {
         nRdlIdx--;
@@ -3000,6 +2959,36 @@ bool 
DocumentRedlineManager::AcceptRedlineRange(SwRedlineTable::size_type nPos,
     return bRet;
 }
 
+bool DocumentRedlineManager::AcceptMovedRedlines(sal_uInt32 nMovedID, bool 
bCallDelete)
+{
+    assert(nMovedID > 1);   // 0, and 1 is reserved
+    bool bRet = false;
+    SwRedlineTable::size_type nRdlIdx = maRedlineTable.size();
+
+    while (nRdlIdx > 0)
+    {
+        nRdlIdx--;
+        SwRangeRedline* pTmp = maRedlineTable[nRdlIdx];
+        if (pTmp->GetMoved(0) == nMovedID
+            || (pTmp->GetStackCount() > 1 && pTmp->GetMoved(1) == nMovedID))
+        {
+            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
+            {
+                m_rDoc.GetIDocumentUndoRedo().AppendUndo(
+                    std::make_unique<SwUndoAcceptRedline>(*pTmp));
+            }
+
+            if (pTmp->GetMoved(0) == nMovedID)
+                bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, 
bCallDelete);
+            else
+                bRet |= lcl_AcceptInnerInsertRedline(maRedlineTable, nRdlIdx, 
1);
+
+            nRdlIdx++; //we will decrease it in the loop anyway.
+        }
+    }
+    return bRet;
+}
+
 bool DocumentRedlineManager::AcceptRedline(SwRedlineTable::size_type nPos, 
bool bCallDelete,
                                            bool bRange)
 {
@@ -3031,13 +3020,23 @@ bool 
DocumentRedlineManager::AcceptRedline(SwRedlineTable::size_type nPos, bool
         if (bRange && !nSeqNo && !bAnonym
             && !pTmp->Start()->GetNode().StartOfSectionNode()->IsTableNode())
         {
-            auto [pPamStart, pPamEnd] = pTmp->StartEnd();
-            SwRedlineTable::size_type nPosEnd;
-            FindRangeToAcceptReject(nPos, &pPamStart, &pPamEnd, nPosEnd);
+            sal_uInt32 nMovedID = pTmp->GetMoved(0);
+            if (nMovedID > 1)
+            {
+                // Accept all redlineData with this unique move id
+                bRet |= AcceptMovedRedlines(nMovedID, bCallDelete);
+            }
+            else
+            {
+                SwRedlineTable::size_type nPosStart = nPos;
+                SwRedlineTable::size_type nPosEnd = nPos;
+
+                maRedlineTable.getConnectedArea(nPos, nPosStart, nPosEnd, 
true);
 
-            // Accept redlines between pPamStart-pPamEnd.
-            // but only those that can be combined with the selected.
-            bRet |= AcceptRedlineRange(nPos, bCallDelete, pPamStart, pPamEnd, 
nPosEnd);
+                // Accept redlines between pPamStart-pPamEnd.
+                // but only those that can be combined with the selected.
+                bRet |= AcceptRedlineRange(nPos, nPosStart, nPosEnd, 
bCallDelete);
+            }
         }
         else do {
 
@@ -3174,20 +3173,21 @@ void 
DocumentRedlineManager::AcceptRedlineParagraphFormatting( const SwPaM &rPam
     }
 }
 
-bool DocumentRedlineManager::RejectRedlineRange(SwRedlineTable::size_type 
nPos, bool bCallDelete,
-                                                SwPosition* pPamStart, 
SwPosition* pPamEnd,
-                                                SwRedlineTable::size_type& 
nPosEnd)
+bool DocumentRedlineManager::RejectRedlineRange(SwRedlineTable::size_type 
nPosOrigin,
+                                                SwRedlineTable::size_type& 
nPosStart,
+                                                SwRedlineTable::size_type& 
nPosEnd,
+                                                bool bCallDelete)
 {
     bool bRet = false;
 
-    SwRangeRedline* pTmp = maRedlineTable[nPos];
+    SwRangeRedline* pTmp = maRedlineTable[nPosOrigin];
     SwRedlineTable::size_type nRdlIdx = nPosEnd + 1;
     SwRedlineData aOrigData = pTmp->GetRedlineData(0);
 
-    SwNodeOffset nPamStartNI = pPamStart->GetNodeIndex();
-    sal_Int32 nPamStartCI = pPamStart->GetContentIndex();
-    SwNodeOffset nPamEndtNI = pPamEnd->GetNodeIndex();
-    sal_Int32 nPamEndCI = pPamEnd->GetContentIndex();
+    SwNodeOffset nPamStartNI = 
maRedlineTable[nPosStart]->Start()->GetNodeIndex();
+    sal_Int32 nPamStartCI = 
maRedlineTable[nPosStart]->Start()->GetContentIndex();
+    SwNodeOffset nPamEndtNI = maRedlineTable[nPosEnd]->End()->GetNodeIndex();
+    sal_Int32 nPamEndCI = maRedlineTable[nPosEnd]->End()->GetContentIndex();
     do
     {
         nRdlIdx--;
@@ -3245,6 +3245,40 @@ bool 
DocumentRedlineManager::RejectRedlineRange(SwRedlineTable::size_type nPos,
     return bRet;
 }
 
+bool DocumentRedlineManager::RejectMovedRedlines(sal_uInt32 nMovedID, bool 
bCallDelete)
+{
+    assert(nMovedID > 1); // 0, and 1 is reserved
+    bool bRet = false;
+    SwRedlineTable::size_type nRdlIdx = maRedlineTable.size();
+
+    while (nRdlIdx > 0)
+    {
+        nRdlIdx--;
+        SwRangeRedline* pTmp = maRedlineTable[nRdlIdx];
+        if (pTmp->GetMoved(0) == nMovedID
+            || (pTmp->GetStackCount() > 1 && pTmp->GetMoved(1) == nMovedID))
+        {
+            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
+            {
+                std::unique_ptr<SwUndoRejectRedline> pUndoRdl
+                    = std::make_unique<SwUndoRejectRedline>(*pTmp);
+#if OSL_DEBUG_LEVEL > 0
+                pUndoRdl->SetRedlineCountDontCheck(true);
+#endif
+                m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl));
+            }
+
+            if (pTmp->GetMoved(0) == nMovedID)
+                bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, 
bCallDelete);
+            else
+                bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, 
bCallDelete);
+
+            nRdlIdx++; //we will decrease it in the loop anyway.
+        }
+    }
+    return bRet;
+}
+
 bool DocumentRedlineManager::RejectRedline(SwRedlineTable::size_type nPos,
                                            bool bCallDelete, bool bRange)
 {
@@ -3276,14 +3310,23 @@ bool 
DocumentRedlineManager::RejectRedline(SwRedlineTable::size_type nPos,
         if (bRange && !nSeqNo && !bAnonym
             && !pTmp->Start()->GetNode().StartOfSectionNode()->IsTableNode())
         {
-            auto [pPamStart, pPamEnd] = pTmp->StartEnd();
-            SwRedlineTable::size_type nPosEnd;
-            FindRangeToAcceptReject(nPos, &pPamStart, &pPamEnd, nPosEnd);
+            sal_uInt32 nMovedID = pTmp->GetMoved(0);
+            if (nMovedID > 1)
+            {
+                // Reject all redlineData with this unique move id
+                bRet |= RejectMovedRedlines(nMovedID, bCallDelete);
+            }
+            else
+            {
+                SwRedlineTable::size_type nPosStart = nPos;
+                SwRedlineTable::size_type nPosEnd = nPos;
+                maRedlineTable.getConnectedArea(nPos, nPosStart, nPosEnd, 
true);
 
-            // Reject items between pPamStart-pPamEnd
-            // but only those that can be combined with the selected.
+                // Reject items between pPamStart-pPamEnd
+                // but only those that can be combined with the selected.
 
-            bRet |= RejectRedlineRange(nPos, bCallDelete, pPamStart, pPamEnd, 
nPosEnd);
+                bRet |= RejectRedlineRange(nPos, nPosStart, nPosEnd, 
bCallDelete);
+            }
         }
         else do {
 
diff --git a/sw/source/core/doc/doccomp.cxx b/sw/source/core/doc/doccomp.cxx
index 65450a561064..d813e08965a4 100644
--- a/sw/source/core/doc/doccomp.cxx
+++ b/sw/source/core/doc/doccomp.cxx
@@ -1659,7 +1659,7 @@ void CompareData::SetRedlinesToDoc( bool bUseDocInfo )
 
     if( pTmp )
     {
-        SwRedlineData aRedlnData( RedlineType::Delete, nAuthor, aTimeStamp,
+        SwRedlineData aRedlnData( RedlineType::Delete, nAuthor, aTimeStamp, 0,
                                     OUString(), nullptr );
         do {
             // #i65201#: Expand again, see comment above.
@@ -1732,7 +1732,7 @@ void CompareData::SetRedlinesToDoc( bool bUseDocInfo )
             }
         }
     } while( m_pInsertRing.get() != ( pTmp = pTmp->GetNext()) );
-    SwRedlineData aRedlnData( RedlineType::Insert, nAuthor, aTimeStamp,
+    SwRedlineData aRedlnData( RedlineType::Insert, nAuthor, aTimeStamp, 0,
                                 OUString(), nullptr );
 
     // combine consecutive
diff --git a/sw/source/core/doc/docnum.cxx b/sw/source/core/doc/docnum.cxx
index 2ae2d23e8c63..0512af4aa556 100644
--- a/sw/source/core/doc/docnum.cxx
+++ b/sw/source/core/doc/docnum.cxx
@@ -2173,7 +2173,9 @@ bool SwDoc::MoveParagraphImpl(SwPaM& rPam, SwNodeOffset 
const nOffset,
                            : aIdx.GetNode().GetTextNode());
             bool bIsEmptyNode = pIsEmptyNode && pIsEmptyNode->Len() == 0;
 
-            getIDocumentContentOperations().CopyRange(*oPam, aInsPos, 
SwCopyFlags::CheckPosInFly);
+            sal_uInt32 nMovedID = 
getIDocumentRedlineAccess().GetRedlineTable().getNewMovedID();
+            getIDocumentContentOperations().CopyRange(*oPam, aInsPos, 
SwCopyFlags::CheckPosInFly,
+                                                      nMovedID);
 
             // now delete all the delete redlines that were copied
 #ifndef NDEBUG
@@ -2207,7 +2209,7 @@ bool SwDoc::MoveParagraphImpl(SwPaM& rPam, SwNodeOffset 
const nOffset,
                         pam.GetMark()->Assign(pam.GetMark()->GetNodeIndex() + 
nCurrentOffset,
                                               
pam.GetMark()->GetContentIndex());
 
-                        pNewRedline = new SwRangeRedline( RedlineType::Delete, 
pam );
+                        pNewRedline = new SwRangeRedline( RedlineType::Delete, 
pam, nMovedID );
                     }
                     // note: effectively this will DeleteAndJoin the pam!
                     getIDocumentRedlineAccess().AppendRedline(pNewRedline, 
true);
@@ -2267,7 +2269,7 @@ bool SwDoc::MoveParagraphImpl(SwPaM& rPam, SwNodeOffset 
const nOffset,
                     std::make_unique<SwUndoRedlineDelete>(*oPam, 
SwUndoId::DELETE));
             }
 
-            SwRangeRedline* pNewRedline = new SwRangeRedline( 
RedlineType::Delete, *oPam );
+            SwRangeRedline* pNewRedline = new SwRangeRedline( 
RedlineType::Delete, *oPam, nMovedID );
 
             // prevent assertion from aPam's target being deleted
             SwNodeIndex bound1(oPam->GetBound().GetNode());
diff --git a/sw/source/core/doc/docredln.cxx b/sw/source/core/doc/docredln.cxx
index 3a70299ce1a5..02ded9bc1acd 100644
--- a/sw/source/core/doc/docredln.cxx
+++ b/sw/source/core/doc/docredln.cxx
@@ -340,6 +340,12 @@ bool lcl_LOKRedlineNotificationEnabled()
 
 } // anonymous namespace
 
+void SwRedlineTable::setMovedIDIfNeeded(sal_uInt32 nMax)
+{
+    if (nMax > m_nMaxMovedID)
+        m_nMaxMovedID = nMax;
+}
+
 /// Emits LOK notification about one addition / removal of a redline item.
 void SwRedlineTable::LOKRedlineNotification(RedlineNotification nType, 
SwRangeRedline* pRedline)
 {
@@ -788,7 +794,128 @@ const SwRangeRedline* SwRedlineTable::FindAtPosition( 
const SwPosition& rSttPos,
     return pFnd;
 }
 
-bool SwRedlineTable::isMoved( size_type rPos ) const
+namespace
+{
+bool lcl_CanCombineWithRange(SwRangeRedline* pOrigin, SwRangeRedline* pActual,
+                             SwRangeRedline* pOther, bool bReverseDir, bool 
bCheckChilds)
+{
+    if (pOrigin->IsVisible() != pOther->IsVisible())
+        return false;
+
+    if (bReverseDir)
+    {
+        if (*(pOther->End()) != *(pActual->Start()))
+            return false;
+    }
+    else
+    {
+        if (*(pActual->End()) != *(pOther->Start()))
+            return false;
+    }
+
+    if 
(!pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(0)))
+    {
+        if (!bCheckChilds || pOther->GetStackCount() <= 1
+            || 
!pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(1)))
+            return false;
+    }
+    if (pOther->Start()->GetNode().StartOfSectionNode()
+        != pActual->Start()->GetNode().StartOfSectionNode())
+        return false;
+
+    return true;
+}
+}
+
+void SwRedlineTable::getConnectedArea(size_type nPosOrigin, size_type& 
rPosStart,
+                                      size_type& rPosEnd, bool bCheckChilds) 
const
+{
+    // Keep the original redline .. else we should memorize witch children was 
checked
+    // at the last combined redline.
+    SwRangeRedline* pOrigin = (*this)[nPosOrigin];
+    rPosStart = nPosOrigin;
+    rPosEnd = nPosOrigin;
+    SwRangeRedline* pRedline = pOrigin;
+    SwRangeRedline* pOther;
+
+    // connection info is already here..only the actual text is missing at 
import time
+    // so no need to check Redline->GetContentIdx() here yet.
+    while (rPosStart > 0 && (pOther = (*this)[rPosStart - 1])
+           && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, true, 
bCheckChilds))
+    {
+        rPosStart--;
+        pRedline = pOther;
+    }
+    while (rPosEnd + 1 < size() && (pOther = (*this)[rPosEnd + 1])
+           && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, false, 
bCheckChilds))
+    {
+        rPosEnd++;
+        pRedline = pOther;
+    }
+}
+
+OUString SwRedlineTable::getTextOfArea(size_type rPosStart, size_type rPosEnd) 
const
+{
+    // Normally a SwPaM::GetText() would be enought with rPosStart-start and 
rPosEnd-end
+    // But at import time some text is not present there yet
+    // we have to collect them 1 by 1
+
+    OUString sRet = "";
+
+    for (size_type nIdx = rPosStart; nIdx <= rPosEnd; ++nIdx)
+    {
+        SwRangeRedline* pRedline = (*this)[nIdx];
+        bool bStartWithNonTextNode = false;
+
+        SwPaM *pPaM;
+        bool bDeletePaM = false;
+        if (nullptr == pRedline->GetContentIdx())
+        {
+            pPaM = pRedline;
+        }
+        else // otherwise it is saved in pContentSect, e.g. during ODT import
+        {
+            pPaM = new SwPaM(pRedline->GetContentIdx()->GetNode(),
+                              
*pRedline->GetContentIdx()->GetNode().EndOfSectionNode());
+            if (!pPaM->Start()->nNode.GetNode().GetTextNode())
+            {
+                bStartWithNonTextNode = true;
+            }
+            bDeletePaM = true;
+        }
+        const OUString sNew = pPaM->GetText();
+
+        if (bStartWithNonTextNode &&
+            sNew[0] == CH_TXTATR_NEWLINE)
+        {
+            sRet += pPaM->GetText().subView(1);
+        }
+        else
+            sRet += pPaM->GetText();
+        if (bDeletePaM)
+            delete pPaM;
+    }
+
+    return sRet;
+}
+
+bool SwRedlineTable::isMoved(size_type rPos) const
+{
+    // If it is already a part of a movement, then dont check it.
+    if ((*this)[rPos]->GetMoved() != 0)
+        return false;
+    // First try with single redline. then try with combined redlines
+    if (isMovedImpl(rPos, false))
+        return true;
+    else
+    {
+        // Commented out because of probably performance issue
+        //return isMovedImpl(rPos, true);
+        return false;
+    }
+}
+
+bool SwRedlineTable::isMovedImpl(size_type rPos, bool bTryCombined) const
 {
     bool bRet = false;
     auto constexpr nLookahead = 20;
@@ -805,74 +932,133 @@ bool SwRedlineTable::isMoved( size_type rPos ) const
         return false;
 
     bool bDeletePaM = false;
-    SwPaM* pPaM;
+    SwPaM* pPaM = nullptr;
+    OUString sTrimmed;
+    SwRedlineTable::size_type nPosStart = rPos;
+    SwRedlineTable::size_type nPosEnd = rPos;
 
-    // if this redline is visible the content is in this PaM
-    if ( nullptr == pRedline->GetContentIdx() )
+    if (bTryCombined)
     {
-        pPaM = pRedline;
+        getConnectedArea(rPos, nPosStart, nPosEnd, false);
+        if (nPosStart != nPosEnd)
+            sTrimmed = getTextOfArea(nPosStart, nPosEnd).trim();
     }
-    else // otherwise it is saved in pContentSect, e.g. during ODT import
+
+    if (sTrimmed.isEmpty())
     {
-        pPaM = new SwPaM(pRedline->GetContentIdx()->GetNode(), 
*pRedline->GetContentIdx()->GetNode().EndOfSectionNode() );
-        bDeletePaM = true;
+        // if this redline is visible the content is in this PaM
+        if (nullptr == pRedline->GetContentIdx())
+        {
+            pPaM = pRedline;
+        }
+        else // otherwise it is saved in pContentSect, e.g. during ODT import
+        {
+            pPaM = new SwPaM(pRedline->GetContentIdx()->GetNode(),
+                             
*pRedline->GetContentIdx()->GetNode().EndOfSectionNode());
+            bDeletePaM = true;
+        }
+
+        sTrimmed = pPaM->GetText().trim();
     }
 
-    const OUString sTrimmed = pPaM->GetText().trim();
     // detection of move needs at least 6 characters with an inner
     // space after stripping white spaces of the redline to skip
     // frequent deleted and inserted articles or other common
     // word parts, e.g. 'the' and 'of a' to detect as text moving
-    if ( sTrimmed.getLength() < 6 || sTrimmed.indexOf(' ') == -1 )
+    if (sTrimmed.getLength() < 6 || sTrimmed.indexOf(' ') == -1)
     {
-        if ( bDeletePaM )
+        if (bDeletePaM)
             delete pPaM;
         return false;
     }
 
+    // Todo: lessen the previous condition..:
+    // if the source / destination is a whole node change then maybe space is 
not needed
+
     // search pair around the actual redline
     size_type nEnd = rPos + nLookahead < size()
         ? rPos + nLookahead
         : size();
-    rPos = rPos > nLookahead ? rPos - nLookahead : 0;
-    for ( ; rPos < nEnd && !bRet ; ++rPos )
+    size_type nStart = rPos > nLookahead ? rPos - nLookahead : 0;
+    // first, try to compare to single redlines
+    // next, try to compare to combined redlines
+    for (int nPass = 0; nPass < (bTryCombined ? 2 : 1) && !bRet; nPass++)
     {
-        SwRangeRedline* pPair = (*this)[ rPos ];
-
-        // redline must be the requested type and from the same author
-        if ( nPairType != pPair->GetType() ||
-             pRedline->GetAuthor() != pPair->GetAuthor() )
+        for (size_type nPosAct = nStart; nPosAct < nEnd && !bRet; ++nPosAct)
         {
-            continue;
-        }
+            SwRangeRedline* pPair = (*this)[nPosAct];
 
-        bool bDeletePairPaM = false;
-        SwPaM* pPairPaM;
+            // redline must be the requested type and from the same author
+            if (nPairType != pPair->GetType() || pRedline->GetAuthor() != 
pPair->GetAuthor())
+            {
+                continue;
+            }
 
-        // if this redline is visible the content is in this PaM
-        if ( nullptr == pPair->GetContentIdx() )
-        {
-            pPairPaM = pPair;
-        }
-        else // otherwise it is saved in pContentSect, e.g. during ODT import
-        {
-            // saved in pContentSect, e.g. during ODT import
-            pPairPaM = new SwPaM(pPair->GetContentIdx()->GetNode(), 
*pPair->GetContentIdx()->GetNode().EndOfSectionNode() );
-            bDeletePairPaM = true;
-        }
+            bool bDeletePairPaM = false;
+            SwPaM* pPairPaM = nullptr;
 
-        // pair at tracked moving: same text by trimming trailing white spaces
-        if ( abs(pPaM->GetText().getLength() - 
pPairPaM->GetText().getLength()) <= 2 &&
-            sTrimmed == o3tl::trim(pPairPaM->GetText()) )
-        {
-            pRedline->SetMoved();
-            pPair->SetMoved();
-            pPair->InvalidateRange(SwRangeRedline::Invalidation::Add);
-            bRet = true;
-        }
+            OUString sPairTrimmed = "";
+            SwRedlineTable::size_type nPairStart = nPosAct;
+            SwRedlineTable::size_type nPairEnd = nPosAct;
 
-        if ( bDeletePairPaM )
-            delete pPairPaM;
+            if (nPass == 0)
+            {
+                // if this redline is visible the content is in this PaM
+                if (nullptr == pPair->GetContentIdx())
+                {
+                    pPairPaM = pPair;
+                }
+                else // otherwise it is saved in pContentSect, e.g. during ODT 
import
+                {
+                    // saved in pContentSect, e.g. during ODT import
+                    pPairPaM = new SwPaM(pPair->GetContentIdx()->GetNode(),
+                                         
*pPair->GetContentIdx()->GetNode().EndOfSectionNode());
+                    bDeletePairPaM = true;
+                }
+
+                sPairTrimmed = o3tl::trim(pPairPaM->GetText());
+            }
+            else
+            {
+                getConnectedArea(nPosAct, nPairStart, nPairEnd, false);
+                if (nPairStart != nPairEnd)
+                    sPairTrimmed = getTextOfArea(nPairStart, nPairEnd).trim();
+            }
+
+            // pair at tracked moving: same text by trimming trailing white 
spaces
+            if (abs(sTrimmed.getLength() - sPairTrimmed.getLength()) <= 2
+                && sTrimmed == sPairTrimmed)
+            {
+                sal_uInt32 nMID = getNewMovedID();
+                if (nPosStart != nPosEnd)
+                {
+                    for (size_type nIdx = nPosStart; nIdx <= nPosEnd; ++nIdx)
+                    {
+                        (*this)[nIdx]->SetMoved(nMID);
+                        if (nIdx != rPos)
+                            
(*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add);
+                    }
+                }
+                else
+                    pRedline->SetMoved(nMID);
+
+                //in (nPass == 0) it will only call once .. as nPairStart == 
nPairEnd == nPosAct
+                for (size_type nIdx = nPairStart; nIdx <= nPairEnd; ++nIdx)
+                {
+                    (*this)[nIdx]->SetMoved(nMID);
+                    
(*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add);
+                }
+
+                bRet = true;
+            }
+
+            if (bDeletePairPaM)
+                delete pPairPaM;
+
+            //we can skip the combined redlines
+            if (nPass == 1)
+                nPosAct = nPairEnd;
+        }
     }
 
     if ( bDeletePaM )
@@ -1045,10 +1231,10 @@ bool SwRedlineExtraData_Format::operator == ( const 
SwRedlineExtraData& rCmp ) c
     return true;
 }
 
-SwRedlineData::SwRedlineData( RedlineType eT, std::size_t nAut )
+SwRedlineData::SwRedlineData( RedlineType eT, std::size_t nAut, sal_uInt32 
nMovedID )
     : m_pNext( nullptr ), m_pExtraData( nullptr ),
     m_aStamp( DateTime::SYSTEM ),
-    m_nAuthor( nAut ), m_eType( eT ), m_nSeqNo( 0 ), m_bAutoFormat(false), 
m_bMoved(false)
+    m_nAuthor( nAut ), m_eType( eT ), m_nSeqNo( 0 ), m_bAutoFormat(false), 
m_nMovedID(nMovedID)
 {
     m_aStamp.SetNanoSec( 0 );
 }
@@ -1064,15 +1250,15 @@ SwRedlineData::SwRedlineData(
     , m_eType( rCpy.m_eType )
     , m_nSeqNo( rCpy.m_nSeqNo )
     , m_bAutoFormat(false)
-    , m_bMoved( rCpy.m_bMoved )
+    , m_nMovedID( rCpy.m_nMovedID )
 {
 }
 
 // For sw3io: We now own pNext!
 SwRedlineData::SwRedlineData(RedlineType eT, std::size_t nAut, const DateTime& 
rDT,
-    OUString aCmnt, SwRedlineData *pNxt)
+    sal_uInt32 nMovedID, OUString aCmnt, SwRedlineData *pNxt)
     : m_pNext(pNxt), m_pExtraData(nullptr), m_sComment(std::move(aCmnt)), 
m_aStamp(rDT),
-    m_nAuthor(nAut), m_eType(eT), m_nSeqNo(0), m_bAutoFormat(false), 
m_bMoved(false)
+    m_nAuthor(nAut), m_eType(eT), m_nSeqNo(0), m_bAutoFormat(false), 
m_nMovedID(nMovedID)
 {
 }
 
@@ -1100,7 +1286,7 @@ bool SwRedlineData::CanCombine(const SwRedlineData& rCmp) 
const
             m_eType == rCmp.m_eType &&
             m_sComment == rCmp.m_sComment &&
             deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) &&
-            m_bMoved == rCmp.m_bMoved &&
+            m_nMovedID == rCmp.m_nMovedID &&
             (( !m_pNext && !rCmp.m_pNext ) ||
                 ( m_pNext && rCmp.m_pNext &&
                 m_pNext->CanCombine( *rCmp.m_pNext ))) &&
@@ -1117,7 +1303,7 @@ bool SwRedlineData::CanCombineForAcceptReject(const 
SwRedlineData& rCmp) const
             m_eType == rCmp.m_eType &&
             m_sComment == rCmp.m_sComment &&
             deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) &&
-            m_bMoved == rCmp.m_bMoved &&
+            m_nMovedID == rCmp.m_nMovedID &&
             (( !m_pExtraData && !rCmp.m_pExtraData ) ||
                 ( m_pExtraData && rCmp.m_pExtraData &&
                     *m_pExtraData == *rCmp.m_pExtraData ));
@@ -1182,16 +1368,17 @@ void SwRedlineData::dumpAsXml(xmlTextWriterPtr pWriter) 
const
             break;
     }
     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), 
BAD_CAST(sRedlineType.getStr()));
-    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("moved"), 
BAD_CAST(OString::boolean(m_bMoved).getStr()));
+    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("moved"), 
BAD_CAST(OString::number(m_nMovedID).getStr()));
 
     (void)xmlTextWriterEndElement(pWriter);
 }
 
 sal_uInt32 SwRangeRedline::s_nLastId = 1;
 
-SwRangeRedline::SwRangeRedline(RedlineType eTyp, const SwPaM& rPam )
-    : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ),
-    m_pRedlineData( new SwRedlineData( eTyp, 
GetDoc().getIDocumentRedlineAccess().GetRedlineAuthor() ) ),
+SwRangeRedline::SwRangeRedline(RedlineType eTyp, const SwPaM& rPam, sal_uInt32 
nMovedID )
+    : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), m_pRedlineData(
+          new SwRedlineData(eTyp, 
GetDoc().getIDocumentRedlineAccess().GetRedlineAuthor(), nMovedID ) )
+    ,
     m_nId( s_nLastId++ )
 {
     GetBound().SetRedline(this);
@@ -2034,7 +2221,12 @@ OUString const & SwRangeRedline::GetAuthorString( 
sal_uInt16 nPos ) const
     return SW_MOD()->GetRedlineAuthor(GetRedlineData(nPos).m_nAuthor);
 }
 
-const DateTime& SwRangeRedline::GetTimeStamp( sal_uInt16 nPos ) const
+sal_uInt32 SwRangeRedline::GetMovedID(sal_uInt16 nPos) const
+{
+    return GetRedlineData(nPos).m_nMovedID;
+}
+
+const DateTime& SwRangeRedline::GetTimeStamp(sal_uInt16 nPos) const
 {
     return GetRedlineData(nPos).m_aStamp;
 }
diff --git a/sw/source/core/inc/DocumentContentOperationsManager.hxx 
b/sw/source/core/inc/DocumentContentOperationsManager.hxx
index 6d2d9a5fa6ea..434eaf7ed07b 100644
--- a/sw/source/core/inc/DocumentContentOperationsManager.hxx
+++ b/sw/source/core/inc/DocumentContentOperationsManager.hxx
@@ -36,7 +36,7 @@ public:
     DocumentContentOperationsManager( SwDoc& i_rSwdoc );
 
     //Interface methods:
-    bool CopyRange(SwPaM&, SwPosition&, SwCopyFlags) const override;
+    bool CopyRange(SwPaM&, SwPosition&, SwCopyFlags, sal_uInt32 nMovedID = 0) 
const override;
 
     void DeleteSection(SwNode* pNode) override;
 
diff --git a/sw/source/core/inc/DocumentRedlineManager.hxx 
b/sw/source/core/inc/DocumentRedlineManager.hxx
index 2f9c133605fa..fee4842d1115 100644
--- a/sw/source/core/inc/DocumentRedlineManager.hxx
+++ b/sw/source/core/inc/DocumentRedlineManager.hxx
@@ -141,12 +141,14 @@ public:
 
 private:
 
-    void FindRangeToAcceptReject(SwRedlineTable::size_type nPos, SwPosition** 
pPamStart,
-                                 SwPosition** pPamEnd, 
SwRedlineTable::size_type& nPosEnd) const;
-    bool RejectRedlineRange(SwRedlineTable::size_type nPos, bool bCallDelete, 
SwPosition* pPamStart,
-                            SwPosition* pPamEnd, SwRedlineTable::size_type& 
nPosEnd);
-    bool AcceptRedlineRange(SwRedlineTable::size_type nPos, bool bCallDelete, 
SwPosition* pPamStart,
-                            SwPosition* pPamEnd, SwRedlineTable::size_type& 
nPosEnd);
+    bool RejectRedlineRange(SwRedlineTable::size_type nPosOrigin,
+                            SwRedlineTable::size_type& nPosStart,
+                            SwRedlineTable::size_type& nPosEnd, bool 
bCallDelete);
+    bool AcceptRedlineRange(SwRedlineTable::size_type nPosOrigin,
+                            SwRedlineTable::size_type& nPosStart,
+                            SwRedlineTable::size_type& nPosEnd, bool 
bCallDelete);
+    bool AcceptMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete);
+    bool RejectMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete);
 
     DocumentRedlineManager(DocumentRedlineManager const&) = delete;
     DocumentRedlineManager& operator=(DocumentRedlineManager const&) = delete;
diff --git a/sw/source/core/unocore/unocrsrhelper.cxx 
b/sw/source/core/unocore/unocrsrhelper.cxx
index baac20bbf3d5..70e94360f65c 100644
--- a/sw/source/core/unocore/unocrsrhelper.cxx
+++ b/sw/source/core/unocore/unocrsrhelper.cxx
@@ -1259,7 +1259,7 @@ void makeRedline( SwPaM const & rPaM,
     OUString sComment;
     ::util::DateTime aStamp;
     uno::Sequence< beans::PropertyValue > aRevertProperties;
-    bool bIsMoved = false;
+    sal_uInt32 nMovedID = 0;
     bool bFoundComment = false;
     bool bFoundStamp = false;
     bool bFoundRevertProperties = false;
@@ -1277,7 +1277,7 @@ void makeRedline( SwPaM const & rPaM,
         else if (rProp.Name == "RedlineRevertProperties")
             bFoundRevertProperties = rProp.Value >>= aRevertProperties;
         else if (rProp.Name == "RedlineMoved")
-            rProp.Value >>= bIsMoved;
+            rProp.Value >>= nMovedID;
     }
 
     SwRedlineData aRedlineData( eType, nAuthor );
@@ -1397,8 +1397,11 @@ void makeRedline( SwPaM const & rPaM,
     SwRangeRedline* pRedline = new SwRangeRedline( aRedlineData, rPaM );
 
     // set IsMoved bit of the redline to show and handle moved text
-    if( bIsMoved )
-        pRedline->SetMoved();
+    if ( nMovedID > 0 )
+    {
+        pRedline->SetMoved( nMovedID );
+        rRedlineAccess.GetRedlineTable().setMovedIDIfNeeded(nMovedID);
+    }
 
     RedlineFlags nPrevMode = rRedlineAccess.GetRedlineFlags( );
     // xRedlineExtraData is copied here
diff --git a/sw/source/core/unocore/unoredline.cxx 
b/sw/source/core/unocore/unoredline.cxx
index b76368c15223..feeceec92b31 100644
--- a/sw/source/core/unocore/unoredline.cxx
+++ b/sw/source/core/unocore/unoredline.cxx
@@ -250,7 +250,9 @@ uno::Any  SwXRedlinePortion::GetPropertyValue( 
std::u16string_view rPropertyName
     {
         aRet <<= rRedline.GetTimeStamp().GetUNODateTime();
     }
-    else if(rPropertyName == UNO_NAME_REDLINE_COMMENT)
+    else if (rPropertyName == UNO_NAME_REDLINE_MOVED_ID)
+        aRet <<= rRedline.GetMovedID();
+    else if (rPropertyName == UNO_NAME_REDLINE_COMMENT)
         aRet <<= rRedline.GetComment();
     else if(rPropertyName == UNO_NAME_REDLINE_DESCRIPTION)
         aRet <<= const_cast<SwRangeRedline&>(rRedline).GetDescr();
diff --git a/sw/source/filter/basflt/fltshell.cxx 
b/sw/source/filter/basflt/fltshell.cxx
index fe49700fe2f6..d33cd0a81652 100644
--- a/sw/source/filter/basflt/fltshell.cxx
+++ b/sw/source/filter/basflt/fltshell.cxx
@@ -704,6 +704,7 @@ void SwFltControlStack::SetAttrInDoc(const SwPosition& 
rTmpPos,
                 SwRedlineData aData(rFltRedline.m_eType,
                                     rFltRedline.m_nAutorNo,
                                     rFltRedline.m_aStamp,
+                                    0,
                                     OUString(),
                                     nullptr
                                     );
diff --git a/sw/source/filter/ww8/writerhelper.cxx 
b/sw/source/filter/ww8/writerhelper.cxx
index 86cf8d622200..6746264854cc 100644
--- a/sw/source/filter/ww8/writerhelper.cxx
+++ b/sw/source/filter/ww8/writerhelper.cxx
@@ -801,7 +801,7 @@ namespace sw
                     (pEntry->m_pAttr.get());
 
                 SwRedlineData aData(pFltRedline->m_eType, 
pFltRedline->m_nAutorNo,
-                        pFltRedline->m_aStamp, OUString(), nullptr);
+                        pFltRedline->m_aStamp, 0, OUString(), nullptr);
 
                 SwRangeRedline *const pNewRedline(new SwRangeRedline(aData, 
aRegion));
                 // the point node may be deleted in AppendRedline, so park
diff --git a/sw/source/filter/xml/XMLRedlineImportHelper.cxx 
b/sw/source/filter/xml/XMLRedlineImportHelper.cxx
index df59db44a78d..43ac1cc6769a 100644
--- a/sw/source/filter/xml/XMLRedlineImportHelper.cxx
+++ b/sw/source/filter/xml/XMLRedlineImportHelper.cxx
@@ -188,6 +188,7 @@ public:
     OUString sAuthor;               // change author string
     OUString sComment;              // change comment string
     util::DateTime aDateTime;       // change DateTime
+    OUString sMovedID;              // change move id string
     bool bMergeLastParagraph;   // the SwRangeRedline::IsDelLastPara flag
 
     // each position can may be either empty, an XTextRange, or an SwNodeIndex
@@ -369,6 +370,7 @@ void XMLRedlineImportHelper::Add(
     const OUString& rAuthor,
     const OUString& rComment,
     const util::DateTime& rDateTime,
+    const OUString& rMovedID,
     bool bMergeLastPara)
 {
     // we need to do the following:
@@ -406,8 +408,17 @@ void XMLRedlineImportHelper::Add(
     pInfo->sAuthor = rAuthor;
     pInfo->sComment = rComment;
     pInfo->aDateTime = rDateTime;
+    pInfo->sMovedID = rMovedID;
     pInfo->bMergeLastParagraph = bMergeLastPara;
 
+    //reserve MoveID so it wont be reused by others
+    if (!rMovedID.isEmpty())
+    {
+        SwDoc* const pDoc(static_cast<SwXMLImport&>(m_rImport).getDoc());
+        assert(pDoc);
+        
pDoc->GetDocumentRedlineManager().GetRedlineTable().setMovedIDIfNeeded(rMovedID.toInt32());
+    }
+
     // ad 3)
     auto itPair = m_aRedlineMap.emplace(rId, pInfo);
     if (itPair.second)
@@ -791,6 +802,8 @@ SwRedlineData* XMLRedlineImportHelper::ConvertRedline(
     aDT.SetSec(     pRedlineInfo->aDateTime.Seconds );
     aDT.SetNanoSec( pRedlineInfo->aDateTime.NanoSeconds );
 
+    sal_uInt32 nMovedID = pRedlineInfo->sMovedID.toInt32();
+
     // 3) recursively convert next redline
     //    ( check presence and sanity of hierarchical redline info )
     SwRedlineData* pNext = nullptr;
@@ -803,7 +816,7 @@ SwRedlineData* XMLRedlineImportHelper::ConvertRedline(
 
     // create redline data
     SwRedlineData* pData = new SwRedlineData(pRedlineInfo->eType,
-                                             nAuthorId, aDT,
+                                             nAuthorId, aDT, nMovedID,
                                              pRedlineInfo->sComment,
                                              pNext); // next data (if 
available)
 
diff --git a/sw/source/filter/xml/XMLRedlineImportHelper.hxx 
b/sw/source/filter/xml/XMLRedlineImportHelper.hxx
index 69e530fbd91e..5bea1badfc90 100644
--- a/sw/source/filter/xml/XMLRedlineImportHelper.hxx
+++ b/sw/source/filter/xml/XMLRedlineImportHelper.hxx
@@ -81,6 +81,7 @@ public:
         const OUString& rAuthor,     // name of the author
         const OUString& rComment,    // redline comment
         const css::util::DateTime& rDateTime, // date+time
+        const OUString& rMovedID,     // redline move id
         bool bMergeLastParagraph);      // merge last paragraph?
 
     // create a text section for the redline, and return an
diff --git a/sw/source/filter/xml/xmltexti.cxx 
b/sw/source/filter/xml/xmltexti.cxx
index 3035e22bce76..fe9d1cb877bc 100644
--- a/sw/source/filter/xml/xmltexti.cxx
+++ b/sw/source/filter/xml/xmltexti.cxx
@@ -927,12 +927,13 @@ void SwXMLTextImportHelper::RedlineAdd(
     const OUString& rAuthor,
     const OUString& rComment,
     const util::DateTime& rDateTime,
+    const OUString& rMovedID,
     bool bMergeLastPara)
 {
     // create redline helper on demand
     OSL_ENSURE(nullptr != m_pRedlineHelper, "helper should have been created 
in constructor");
     if (nullptr != m_pRedlineHelper)
-        m_pRedlineHelper->Add(rType, rId, rAuthor, rComment, rDateTime,
+        m_pRedlineHelper->Add(rType, rId, rAuthor, rComment, rDateTime, 
rMovedID,
                             bMergeLastPara);
 }
 
diff --git a/sw/source/filter/xml/xmltexti.hxx 
b/sw/source/filter/xml/xmltexti.hxx
index 40d5b169f19d..dbaab53b347d 100644
--- a/sw/source/filter/xml/xmltexti.hxx
+++ b/sw/source/filter/xml/xmltexti.hxx
@@ -90,6 +90,7 @@ public:
         const OUString& rAuthor,     /// name of the author
         const OUString& rComment,    /// redline comment
         const css::util::DateTime& rDateTime,  /// date+time
+        const OUString& rMovedID,    /// redline move id, to find 
moveFrom/MoveTo parts
         bool bMergeLastPara) override;           /// merge last paragraph
     virtual css::uno::Reference<css::text::XTextCursor> RedlineCreateText(
             css::uno::Reference<css::text::XTextCursor> & rOldCursor, /// 
needed to get the document
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 24071c73d1ea..ae121ea2b2d3 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -344,6 +344,7 @@ DomainMapper_Impl::DomainMapper_Impl(
         m_nStartGenericField(0),
         m_bTextInserted(false),
         m_bTextDeleted(false),
+        m_nLastRedlineMovedID(1),
         m_sCurrentPermId(0),
         m_bFrameDirectionSet(false),
         m_bInDocDefaultsImport(false),
@@ -3676,8 +3677,42 @@ void 
DomainMapper_Impl::CreateRedline(uno::Reference<text::XTextRange> const& xR
         pRedlineProperties[1].Value <<= aDateTime;
         pRedlineProperties[2].Name = getPropertyName( 
PROP_REDLINE_REVERT_PROPERTIES );
         pRedlineProperties[2].Value <<= pRedline->m_aRevertProperties;
+
+        sal_uInt32 nRedlineMovedID = 0;
+        if (bRedlineMoved)
+        {
+            if (!m_sCurrentBkmkId.isEmpty())
+            {
+                nRedlineMovedID = 1;
+                BookmarkMap_t::iterator aBookmarkIter = 
m_aBookmarkMap.find(m_sCurrentBkmkId);
+                if (aBookmarkIter != m_aBookmarkMap.end())
+                {
+                    OUString sMoveID = aBookmarkIter->second.m_sBookmarkName;
+                    auto aIter = m_aRedlineMoveIDs.end();
+
+                    if (sMoveID.indexOf("__RefMoveFrom__") >= 0)
+                    {
+                        aIter = std::find(m_aRedlineMoveIDs.begin(), 
m_aRedlineMoveIDs.end(),
+                                          sMoveID.subView(15));
+                    }
+                    else if (sMoveID.indexOf("__RefMoveTo__") >= 0)
+                    {
+                        aIter = std::find(m_aRedlineMoveIDs.begin(), 
m_aRedlineMoveIDs.end(),
+                                          sMoveID.subView(13));
+                    };
+
+                    if (aIter != m_aRedlineMoveIDs.end())
+                    {
+                        nRedlineMovedID = aIter - m_aRedlineMoveIDs.begin() + 
2;
+                        m_nLastRedlineMovedID = nRedlineMovedID;
+                    }
+                }
+            }
+            else
+                nRedlineMovedID = m_nLastRedlineMovedID;
+        }
         pRedlineProperties[3].Name = "RedlineMoved";
-        pRedlineProperties[3].Value <<= bRedlineMoved;
+        pRedlineProperties[3].Value <<= nRedlineMovedID;
 
         if (!m_bIsActualParagraphFramed)
         {
@@ -8406,6 +8441,14 @@ void DomainMapper_Impl::SetBookmarkName( const OUString& 
rBookmarkName )
             }
         }
 
+        if ((m_sCurrentBkmkPrefix == "__RefMoveFrom__"
+             || m_sCurrentBkmkPrefix == "__RefMoveTo__")
+            && std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(), 
rBookmarkName)
+                   == m_aRedlineMoveIDs.end())
+        {
+            m_aRedlineMoveIDs.push_back(rBookmarkName);
+        }
+
         aBookmarkIter->second.m_sBookmarkName = m_sCurrentBkmkPrefix + 
rBookmarkName;
         m_sCurrentBkmkPrefix.clear();
     }
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
index 95382009766e..ec34244400dc 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
@@ -488,6 +488,10 @@ private:
     bool                                                                       
     m_bTextDeleted;
     LineNumberSettings                                                         
     m_aLineNumberSettings;
 
+    std::vector<OUString>                                                      
     m_aRedlineMoveIDs;
+    // Remember the last used redline MoveID. To avoid regression, because of 
wrong docx export
+    sal_uInt32                                                                 
     m_nLastRedlineMovedID;
+
     BookmarkMap_t                                                              
     m_aBookmarkMap;
     OUString                                                                   
     m_sCurrentBkmkId;
     OUString                                                                   
     m_sCurrentBkmkName;
diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index 6519570d6a03..5798120c5983 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -1359,6 +1359,7 @@ namespace xmloff::token {
         TOKEN( "move-from-left",                  XML_MOVE_FROM_LEFT ),
         TOKEN( "move-from-right",                 XML_MOVE_FROM_RIGHT ),
         TOKEN( "move-from-top",                   XML_MOVE_FROM_TOP ),
+        TOKEN( "move-id",                         XML_MOVE_ID ),
         TOKEN( "move-protect",                    XML_MOVE_PROTECT ),
         TOKEN( "move-short",                      XML_MOVE_SHORT ),
         TOKEN( "movement",                        XML_MOVEMENT ),
diff --git a/xmloff/source/text/XMLChangeInfoContext.cxx 
b/xmloff/source/text/XMLChangeInfoContext.cxx
index 7ceb2f1c16b8..8782013966cc 100644
--- a/xmloff/source/text/XMLChangeInfoContext.cxx
+++ b/xmloff/source/text/XMLChangeInfoContext.cxx
@@ -60,6 +60,9 @@ css::uno::Reference< css::xml::sax::XFastContextHandler > 
XMLChangeInfoContext::
         case XML_ELEMENT(DC, XML_DATE):
             xContext = new XMLStringBufferImportContext(GetImport(), 
sDateTimeBuffer);
             break;
+        case XML_ELEMENT(LO_EXT, XML_MOVE_ID):
+            xContext = new XMLStringBufferImportContext(GetImport(), 
sMovedIDBuffer);
+            break;
         case XML_ELEMENT(TEXT, XML_P):
         case XML_ELEMENT(LO_EXT, XML_P):
             xContext = new XMLStringBufferImportContext(GetImport(), 
sCommentBuffer);
@@ -75,8 +78,8 @@ void XMLChangeInfoContext::endFastElement(sal_Int32 )
 {
     // set values at changed region context
     rChangedRegion.SetChangeInfo(rType, sAuthorBuffer.makeStringAndClear(),
-                                 sCommentBuffer.makeStringAndClear(),
-                                 sDateTimeBuffer);
+                                 sCommentBuffer.makeStringAndClear(), 
sDateTimeBuffer,
+                                 sMovedIDBuffer.makeStringAndClear());
     sDateTimeBuffer.setLength(0);
 }
 
diff --git a/xmloff/source/text/XMLChangeInfoContext.hxx 
b/xmloff/source/text/XMLChangeInfoContext.hxx
index 04f055d533a4..994e285d0463 100644
--- a/xmloff/source/text/XMLChangeInfoContext.hxx
+++ b/xmloff/source/text/XMLChangeInfoContext.hxx
@@ -43,6 +43,7 @@ class XMLChangeInfoContext : public SvXMLImportContext
 
     OUStringBuffer sAuthorBuffer;
     OUStringBuffer sDateTimeBuffer;
+    OUStringBuffer sMovedIDBuffer;
     OUStringBuffer sCommentBuffer;
 
     XMLChangedRegionImportContext& rChangedRegion;
diff --git a/xmloff/source/text/XMLChangedRegionImportContext.cxx 
b/xmloff/source/text/XMLChangedRegionImportContext.cxx
index 4e80ddab94c0..7143ee4d082b 100644
--- a/xmloff/source/text/XMLChangedRegionImportContext.cxx
+++ b/xmloff/source/text/XMLChangedRegionImportContext.cxx
@@ -139,13 +139,14 @@ void XMLChangedRegionImportContext::SetChangeInfo(
     const OUString& rType,
     const OUString& rAuthor,
     const OUString& rComment,
-    std::u16string_view rDate)
+    std::u16string_view rDate,
+    const OUString& rMovedID)
 {
     util::DateTime aDateTime;
     if (::sax::Converter::parseDateTime(aDateTime, rDate))
     {
         GetImport().GetTextImport()->RedlineAdd(
-            rType, sID, rAuthor, rComment, aDateTime, bMergeLastPara);
+            rType, sID, rAuthor, rComment, aDateTime, rMovedID, 
bMergeLastPara);
     }
 }
 
diff --git a/xmloff/source/text/XMLChangedRegionImportContext.hxx 
b/xmloff/source/text/XMLChangedRegionImportContext.hxx
index db0e48a43d84..e05e97c93227 100644
--- a/xmloff/source/text/XMLChangedRegionImportContext.hxx
+++ b/xmloff/source/text/XMLChangedRegionImportContext.hxx
@@ -70,7 +70,8 @@ public:
     void SetChangeInfo(const OUString& rType,
                        const OUString& rAuthor,
                        const OUString& rComment,
-                       std::u16string_view rDate);
+                       std::u16string_view rDate,
+                       const OUString& rMovedId);
 
     /// create redline XText/XTextCursor on demand and register with
     /// XMLTextImportHelper
diff --git a/xmloff/source/text/XMLRedlineExport.cxx 
b/xmloff/source/text/XMLRedlineExport.cxx
index a24c89edf30c..33ddcdb17902 100644
--- a/xmloff/source/text/XMLRedlineExport.cxx
+++ b/xmloff/source/text/XMLRedlineExport.cxx
@@ -455,6 +455,15 @@ void XMLRedlineExport::ExportChangeInfo(
                 : sTmp );
     }
 
+    aAny = rPropSet->getPropertyValue("RedlineMovedID");
+    sal_uInt32 nTmp(0);
+    aAny >>= nTmp;
+    if (nTmp > 1)
+    {
+        SvXMLElementExport aCreatorElem(rExport, XML_NAMESPACE_LO_EXT, 
XML_MOVE_ID, true, false);
+        rExport.Characters( OUString::number( nTmp ) );
+    }
+
     aAny = rPropSet->getPropertyValue("RedlineDateTime");
     util::DateTime aDateTime;
     aAny >>= aDateTime;
diff --git a/xmloff/source/text/txtimp.cxx b/xmloff/source/text/txtimp.cxx
index 15d7b1ff86de..ac0e89474245 100644
--- a/xmloff/source/text/txtimp.cxx
+++ b/xmloff/source/text/txtimp.cxx
@@ -2377,6 +2377,7 @@ void XMLTextImportHelper::RedlineAdd( const OUString& 
/*rType*/,
                                       const OUString& /*rAuthor*/,
                                       const OUString& /*rComment*/,
                                       const util::DateTime& /*rDateTime*/,
+                                      const OUString& /*rMovedID*/,
                                       bool /*bMergeLastPara*/)
 {
     // dummy implementation: do nothing
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index 59ae40480d70..731bf917ff6b 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -1259,6 +1259,7 @@ move-from-bottom
 move-from-left
 move-from-right
 move-from-top
+move-id
 move-protect
 move-short
 movement

Reply via email to