sw/qa/extras/ooxmlexport/ooxmlexport11.cxx        |   86 ++++++++++++++++------
 sw/qa/extras/ooxmlexport/ooxmlexport13.cxx        |   25 +-----
 sw/source/filter/ww8/docxattributeoutput.cxx      |   78 ++++++++++++++++---
 sw/source/filter/ww8/docxattributeoutput.hxx      |    9 +-
 sw/source/filter/ww8/docxexport.cxx               |    5 -
 sw/source/filter/ww8/docxexport.hxx               |    2 
 sw/source/filter/ww8/rtfexport.cxx                |    3 
 sw/source/filter/ww8/rtfexport.hxx                |    3 
 sw/source/filter/ww8/wrtw8nds.cxx                 |   24 +++++-
 sw/source/filter/ww8/wrtww8.cxx                   |    2 
 sw/source/filter/ww8/wrtww8.hxx                   |    6 -
 writerfilter/source/dmapper/DomainMapper.cxx      |    2 
 writerfilter/source/dmapper/DomainMapper_Impl.cxx |   19 ++++
 writerfilter/source/dmapper/DomainMapper_Impl.hxx |    4 +
 14 files changed, 199 insertions(+), 69 deletions(-)

New commits:
commit 9e1e88ad5cf2dc0e9b188c60930445652a6c7519
Author:     László Németh <[email protected]>
AuthorDate: Thu Dec 2 17:45:46 2021 +0100
Commit:     László Németh <[email protected]>
CommitDate: Fri Dec 3 13:10:57 2021 +0100

    tdf#145720 DOCX export: fix loss of tracked moving
    
    of documents created in MSO to keep interoperability.
    
    Export moved redlines as moveFrom/moveTo instead of
    del/ins elements (also for newly created tracked moving).
    
    Export "MoveBookmark" elements moveFromRangeStart,
    moveFromRangeEnd, moveToRangeStart, moveToRangeEnd, which
    imported from DOCX documents created in MSO. Without them,
    moveFrom/moveTo elements were imported as plain deletion
    or insertion in MSO.
    
    Note: MoveBookmark elements were imported and exported as
    collapsed plain bookmarks. Now keep their ranges, also store
    the information of moveFrom/moveTo for correct export.
    In the export filter, mandatory author and date of the tracking
    information restored from RedlineData of the first redline
    within the MoveBookmark, if it's possible.
    
    Follow-up to commit f51fa7534421a195a58b4a737a2e836d8c25ba81
    "tdf#145718 sw, DOCX import: complete tracked text moving".
    
    Change-Id: I54242453a7f7d8f73ea074fc74e8e7bc86d07d01
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/126258
    Tested-by: László Németh <[email protected]>
    Reviewed-by: László Németh <[email protected]>

diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx
index 8cb43ffe3e2c..5fae2dab136d 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx
@@ -885,9 +885,9 @@ DECLARE_OOXMLEXPORT_TEST(testTdf104797, "tdf104797.docx")
     // check moveFrom and moveTo
     CPPUNIT_ASSERT_EQUAL( OUString( "Will this sentence be duplicated?" ), 
getParagraph( 1 )->getString());
     CPPUNIT_ASSERT_EQUAL( OUString( "" ), getRun( getParagraph( 1 ), 1 
)->getString());
-    CPPUNIT_ASSERT(hasProperty(getRun(getParagraph(1), 2), "RedlineType"));
-    
CPPUNIT_ASSERT_EQUAL(OUString("Delete"),getProperty<OUString>(getRun(getParagraph(1),
 2), "RedlineType"));
-    CPPUNIT_ASSERT_EQUAL(true,getProperty<bool>(getRun(getParagraph(1), 2), 
"IsStart"));
+    CPPUNIT_ASSERT(hasProperty(getRun(getParagraph(1), 3), "RedlineType"));
+    
CPPUNIT_ASSERT_EQUAL(OUString("Delete"),getProperty<OUString>(getRun(getParagraph(1),
 3), "RedlineType"));
+    CPPUNIT_ASSERT_EQUAL(true,getProperty<bool>(getRun(getParagraph(1), 3), 
"IsStart"));
     CPPUNIT_ASSERT_EQUAL( OUString( "This is a filler sentence. Will this 
sentence be duplicated ADDED STUFF?" ),
             getParagraph( 2 )->getString());
     CPPUNIT_ASSERT_EQUAL( OUString( "" ), getRun( getParagraph( 2 ), 1 
)->getString());
@@ -897,23 +897,39 @@ DECLARE_OOXMLEXPORT_TEST(testTdf104797, "tdf104797.docx")
     
CPPUNIT_ASSERT_EQUAL(OUString("Insert"),getProperty<OUString>(getRun(getParagraph(2),
 3), "RedlineType"));
     CPPUNIT_ASSERT_EQUAL(true,getProperty<bool>(getRun(getParagraph(2), 3), 
"IsStart"));
 
+    CPPUNIT_ASSERT_EQUAL( OUString( " " ), getRun( getParagraph( 2 ), 4 
)->getString());
+    CPPUNIT_ASSERT_EQUAL( OUString( "" ), getRun( getParagraph( 2 ), 5 
)->getString());
+    CPPUNIT_ASSERT(hasProperty(getRun(getParagraph(2), 6), "RedlineType"));
+    
CPPUNIT_ASSERT_EQUAL(OUString("Insert"),getProperty<OUString>(getRun(getParagraph(2),
 6), "RedlineType"));
+    CPPUNIT_ASSERT_EQUAL( OUString( "" ), getRun( getParagraph( 2 ), 7 
)->getString());
+    CPPUNIT_ASSERT(hasProperty(getRun(getParagraph(2), 7), "RedlineType"));
+    CPPUNIT_ASSERT_EQUAL(true,getProperty<bool>(getRun(getParagraph(2), 7), 
"IsStart"));
+    CPPUNIT_ASSERT_EQUAL( OUString( "Will this sentence be duplicated" ), 
getRun( getParagraph( 2 ), 8 )->getString());
+    CPPUNIT_ASSERT_EQUAL( OUString( " ADDED STUFF" ), getRun( getParagraph( 2 
), 11 )->getString());
+    CPPUNIT_ASSERT_EQUAL( OUString( "?" ), getRun( getParagraph( 2 ), 14 
)->getString());
+}
+
+DECLARE_OOXMLEXPORT_TEST(testTdf145720, "tdf104797.docx")
+{
+    // check moveFromRangeStart/End and moveToRangeStart/End (to keep tracked 
text moving)
+    xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
     if (mbExported)
     {
-        // TODO fix export of moved text
-        CPPUNIT_ASSERT_EQUAL( OUString( " Will this sentence be duplicated 
ADDED STUFF?" ), getRun( getParagraph( 2 ), 4 )->getString());
-    }
-    else
-    {
-        CPPUNIT_ASSERT_EQUAL( OUString( " " ), getRun( getParagraph( 2 ), 4 
)->getString());
-        CPPUNIT_ASSERT_EQUAL( OUString( "" ), getRun( getParagraph( 2 ), 5 
)->getString());
-        CPPUNIT_ASSERT(hasProperty(getRun(getParagraph(2), 5), "RedlineType"));
-        
CPPUNIT_ASSERT_EQUAL(OUString("Insert"),getProperty<OUString>(getRun(getParagraph(2),
 5), "RedlineType"));
-        CPPUNIT_ASSERT_EQUAL( OUString( "" ), getRun( getParagraph( 2 ), 6 
)->getString());
-        CPPUNIT_ASSERT(hasProperty(getRun(getParagraph(2), 6), "RedlineType"));
-        CPPUNIT_ASSERT_EQUAL(true,getProperty<bool>(getRun(getParagraph(2), 
6), "IsStart"));
-        CPPUNIT_ASSERT_EQUAL( OUString( "Will this sentence be duplicated" ), 
getRun( getParagraph( 2 ), 7 )->getString());
-        CPPUNIT_ASSERT_EQUAL( OUString( " ADDED STUFF" ), getRun( 
getParagraph( 2 ), 10 )->getString());
-        CPPUNIT_ASSERT_EQUAL( OUString( "?" ), getRun( getParagraph( 2 ), 13 
)->getString());
+        // These were 0 (missing move*FromRange* elements)
+        assertXPath(pXmlDoc, 
"/w:document/w:body/w:p[1]/w:moveFrom/w:moveFromRangeStart", 1);
+        assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFromRangeEnd", 
1);
+        assertXPath(pXmlDoc, 
"/w:document/w:body/w:p[2]/w:moveTo/w:moveToRangeStart", 1);
+        assertXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveToRangeEnd", 1);
+
+        // paired names
+        assertXPath(pXmlDoc, 
"/w:document/w:body/w:p[1]/w:moveFrom/w:moveFromRangeStart", "name", 
"move471382752");
+        assertXPath(pXmlDoc, 
"/w:document/w:body/w:p[2]/w:moveTo/w:moveToRangeStart", "name", 
"move471382752");
+
+        // mandatory authors and dates
+        assertXPath(pXmlDoc, 
"/w:document/w:body/w:p[1]/w:moveFrom/w:moveFromRangeStart", "author", 
u"Tekijä");
+        assertXPath(pXmlDoc, 
"/w:document/w:body/w:p[2]/w:moveTo/w:moveToRangeStart", "author", u"Tekijä");
+        assertXPath(pXmlDoc, 
"/w:document/w:body/w:p[1]/w:moveFrom/w:moveFromRangeStart", "date", 
"0-00-00T00:00:00Z");
+        assertXPath(pXmlDoc, 
"/w:document/w:body/w:p[2]/w:moveTo/w:moveToRangeStart", "date", 
"0-00-00T00:00:00Z");
     }
 }
 
@@ -1113,8 +1129,21 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf132271)
     loadAndSave("tdf132271.docx");
     xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
     // import change tracking in floating tables
-    assertXPath(pXmlDoc, "//w:del", 2);
-    assertXPath(pXmlDoc, "//w:ins", 2);
+    if (!mbExported)
+    {
+        assertXPath(pXmlDoc, "//w:del", 2);
+        assertXPath(pXmlDoc, "//w:ins", 2);
+        assertXPath(pXmlDoc, "//w:moveFrom", 0);
+        assertXPath(pXmlDoc, "//w:moveTo", 0);
+    }
+    else
+    {
+        assertXPath(pXmlDoc, "//w:del", 1);
+        assertXPath(pXmlDoc, "//w:ins", 1);
+        // tracked text moving recognized during the import
+        assertXPath(pXmlDoc, "//w:moveFrom", 1);
+        assertXPath(pXmlDoc, "//w:moveTo", 1);
+    }
 }
 
 CPPUNIT_TEST_FIXTURE(Test, testTdf136667)
@@ -1122,8 +1151,21 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf136667)
     loadAndSave("tdf136667.docx");
     xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
     // import change tracking in floating tables
-    assertXPath(pXmlDoc, "//w:del", 2);
-    assertXPath(pXmlDoc, "//w:ins", 4);
+    if (!mbExported)
+    {
+        assertXPath(pXmlDoc, "//w:del", 2);
+        assertXPath(pXmlDoc, "//w:ins", 4);
+        assertXPath(pXmlDoc, "//w:moveFrom", 0);
+        assertXPath(pXmlDoc, "//w:moveTo", 0);
+    }
+    else
+    {
+        assertXPath(pXmlDoc, "//w:del", 1);
+        assertXPath(pXmlDoc, "//w:ins", 3);
+        // tracked text moving recognized during the import
+        assertXPath(pXmlDoc, "//w:moveFrom", 1);
+        assertXPath(pXmlDoc, "//w:moveTo", 1);
+    }
 }
 
 CPPUNIT_TEST_FIXTURE(Test, testTdf136850)
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx
index 86473c120cbb..6600928f37e9 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx
@@ -763,24 +763,13 @@ DECLARE_OOXMLEXPORT_TEST(testTdf123460, "tdf123460.docx")
     // check paragraph mark deletion at terminating moveFrom
     CPPUNIT_ASSERT_EQUAL(true,getParagraph( 2 
)->getString().startsWith("Nunc"));
     CPPUNIT_ASSERT_EQUAL( OUString( "" ), getRun( getParagraph( 2 ), 1 
)->getString());
-    CPPUNIT_ASSERT(hasProperty(getRun(getParagraph(2), 1), "RedlineType"));
-    
CPPUNIT_ASSERT_EQUAL(OUString("Delete"),getProperty<OUString>(getRun(getParagraph(2),
 1), "RedlineType"));
-    CPPUNIT_ASSERT_EQUAL(true, getRun( getParagraph( 2 ), 2 
)->getString().endsWith("tellus."));
-    CPPUNIT_ASSERT_EQUAL( OUString( "" ), getRun( getParagraph( 2 ), 3 
)->getString());
-    if (mbExported)
-    {
-        // TODO fix export of moved text
-        bool bCaught = false;
-        try
-        {
-            getRun( getParagraph( 2 ), 4 );
-        }
-        catch (container::NoSuchElementException&)
-        {
-            bCaught = true;
-        }
-        CPPUNIT_ASSERT_EQUAL(true, bCaught);
-    }
+    CPPUNIT_ASSERT(hasProperty(getRun(getParagraph(2), 2), "RedlineType"));
+    
CPPUNIT_ASSERT_EQUAL(OUString("Delete"),getProperty<OUString>(getRun(getParagraph(2),
 2), "RedlineType"));
+    CPPUNIT_ASSERT_EQUAL(true, getRun( getParagraph( 2 ), 3 
)->getString().endsWith("tellus."));
+    // deleted paragraph mark at the end of the second paragraph
+    CPPUNIT_ASSERT(hasProperty(getRun(getParagraph(2), 5), "RedlineType"));
+    
CPPUNIT_ASSERT_EQUAL(OUString("Delete"),getProperty<OUString>(getRun(getParagraph(2),
 5), "RedlineType"));
+    CPPUNIT_ASSERT_EQUAL( OUString( "" ), getRun( getParagraph( 2 ), 6 
)->getString());
 }
 
 //tdf#125298: fix charlimit restrictions in bookmarknames and field references 
if they contain non-ascii characters
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index 51a8ac155cad..3ec3d75732c9 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -1720,7 +1720,7 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, 
sal_Int32 nPos, bool /
     // XML_r node should be surrounded with bookmark-begin and bookmark-end 
nodes if it has bookmarks.
     // The same is applied for permission ranges.
     // But due to unit test "testFdo85542" let's output bookmark-begin with 
bookmark-end.
-    DoWriteBookmarksStart(m_rBookmarksStart);
+    DoWriteBookmarksStart(m_rBookmarksStart, m_pMoveRedlineData);
     DoWriteBookmarksEnd(m_rBookmarksEnd);
     DoWritePermissionsStart();
     DoWriteAnnotationMarks();
@@ -1905,6 +1905,29 @@ void 
DocxAttributeOutput::DoWriteBookmarkTagEnd(sal_Int32 const nId)
         FSNS(XML_w, XML_id), OString::number(nId));
 }
 
+void DocxAttributeOutput::DoWriteMoveRangeTagStart(const OString & 
bookmarkName,
+    bool bFrom, const SwRedlineData* pRedlineData)
+{
+    const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( 
pRedlineData->GetAuthor() ) );
+    OString aDate( DateTimeToOString( pRedlineData->GetTimeStamp() ) );
+
+    m_pSerializer->singleElementNS(XML_w, bFrom
+                ? XML_moveFromRangeStart
+                : XML_moveToRangeStart,
+        FSNS(XML_w, XML_id), OString::number(m_nNextBookmarkId),
+        FSNS(XML_w, XML_author ), OUStringToOString(rAuthor, 
RTL_TEXTENCODING_UTF8),
+        FSNS(XML_w, XML_date ), aDate,
+        FSNS(XML_w, XML_name), bookmarkName);
+}
+
+void DocxAttributeOutput::DoWriteMoveRangeTagEnd(sal_Int32 const nId, bool 
bFrom)
+{
+    m_pSerializer->singleElementNS(XML_w, bFrom
+            ? XML_moveFromRangeEnd
+            : XML_moveToRangeEnd,
+        FSNS(XML_w, XML_id), OString::number(nId));
+}
+
 void DocxAttributeOutput::DoWriteBookmarkStartIfExist(sal_Int32 nRunPos)
 {
     auto aRange = m_aBookmarksOfParagraphStart.equal_range(nRunPos);
@@ -1934,15 +1957,29 @@ void 
DocxAttributeOutput::DoWriteBookmarkEndIfExist(sal_Int32 nRunPos)
 }
 
 /// Write the start bookmarks
-void DocxAttributeOutput::DoWriteBookmarksStart(std::vector<OUString>& rStarts)
+void DocxAttributeOutput::DoWriteBookmarksStart(std::vector<OUString>& 
rStarts, const SwRedlineData* pRedlineData)
 {
     for (const OUString & bookmarkName : rStarts)
     {
-        // Output the bookmark
-        DoWriteBookmarkTagStart(bookmarkName);
+        // Output the bookmark (including MoveBookmark of the tracked moving)
+        bool bMove = false;
+        bool bFrom = false;
+        OString sBookmarkName = OUStringToOString(
+                BookmarkToWord(bookmarkName, &bMove, &bFrom), 
RTL_TEXTENCODING_UTF8);
+        if ( bMove )
+        {
+            // TODO: redline data of MoveBookmark is restored from the first 
redline of the bookmark
+            // range. But a later deletion within a tracked moving is still 
imported as plain
+            // deletion, so check IsMoved() and skip the export of the tracked 
moving to avoid
+            // export with bad author or date
+            if ( pRedlineData && pRedlineData->IsMoved() )
+                DoWriteMoveRangeTagStart(sBookmarkName, bFrom, pRedlineData);
+        }
+        else
+            DoWriteBookmarkTagStart(bookmarkName);
 
         m_rOpenedBookmarksIds[bookmarkName] = m_nNextBookmarkId;
-        m_sLastOpenedBookmark = 
OUStringToOString(BookmarkToWord(bookmarkName), RTL_TEXTENCODING_UTF8);
+        m_sLastOpenedBookmark = sBookmarkName;
         m_nNextBookmarkId++;
     }
     rStarts.clear();
@@ -1955,10 +1992,17 @@ void 
DocxAttributeOutput::DoWriteBookmarksEnd(std::vector<OUString>& rEnds)
     {
         // Get the id of the bookmark
         auto pPos = m_rOpenedBookmarksIds.find(bookmarkName);
+
         if (pPos != m_rOpenedBookmarksIds.end())
         {
-            // Output the bookmark
-            DoWriteBookmarkTagEnd(pPos->second);
+            bool bMove = false;
+            bool bFrom = false;
+            BookmarkToWord(bookmarkName, &bMove, &bFrom);
+            // Output the bookmark (including MoveBookmark of the tracked 
moving)
+            if ( bMove )
+                DoWriteMoveRangeTagEnd(pPos->second, bFrom);
+            else
+                DoWriteBookmarkTagEnd(pPos->second);
 
             m_rOpenedBookmarksIds.erase(bookmarkName);
         }
@@ -3109,10 +3153,13 @@ void DocxAttributeOutput::RunText( const OUString& 
rText, rtl_TextEncoding /*eCh
     const sal_Unicode *pBegin = rText.getStr();
     const sal_Unicode *pEnd = pBegin + rText.getLength();
 
-    // the text run is usually XML_t, with the exception of the deleted text
+    // the text run is usually XML_t, with the exception of the deleted (and 
not moved) text
     sal_Int32 nTextToken = XML_t;
-    if ( m_pRedlineData && m_pRedlineData->GetType() == RedlineType::Delete )
+    if ( m_pRedlineData && !m_pRedlineData->IsMoved() &&
+            m_pRedlineData->GetType() == RedlineType::Delete )
+    {
         nTextToken = XML_delText;
+    }
 
     sal_Unicode prevUnicode = *pBegin;
 
@@ -3503,17 +3550,18 @@ void DocxAttributeOutput::StartRedline( const 
SwRedlineData * pRedlineData )
             ? DateTime(Date( 1, 1, 1970 )) // Epoch time
             : pRedlineData->GetTimeStamp() ) );
 
+    bool bMoved = pRedlineData->IsMoved();
     switch ( pRedlineData->GetType() )
     {
         case RedlineType::Insert:
-            m_pSerializer->startElementNS( XML_w, XML_ins,
+            m_pSerializer->startElementNS( XML_w, bMoved ? XML_moveTo : 
XML_ins,
                     FSNS( XML_w, XML_id ), aId,
                     FSNS( XML_w, XML_author ), aAuthor,
                     FSNS( XML_w, XML_date ), aDate );
             break;
 
         case RedlineType::Delete:
-            m_pSerializer->startElementNS( XML_w, XML_del,
+            m_pSerializer->startElementNS( XML_w, bMoved ? XML_moveFrom : 
XML_del,
                     FSNS( XML_w, XML_id ), aId,
                     FSNS( XML_w, XML_author ), aAuthor,
                     FSNS( XML_w, XML_date ), aDate );
@@ -3532,14 +3580,15 @@ void DocxAttributeOutput::EndRedline( const 
SwRedlineData * pRedlineData )
     if ( !pRedlineData || m_bWritingField )
         return;
 
+    bool bMoved = pRedlineData->IsMoved();
     switch ( pRedlineData->GetType() )
     {
         case RedlineType::Insert:
-            m_pSerializer->endElementNS( XML_w, XML_ins );
+            m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveTo : XML_ins 
);
             break;
 
         case RedlineType::Delete:
-            m_pSerializer->endElementNS( XML_w, XML_del );
+            m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveFrom : 
XML_del );
             break;
 
         case RedlineType::Format:
@@ -8493,7 +8542,7 @@ void DocxAttributeOutput::WriteFormData_Impl( const 
::sw::mark::IFieldmark& rFie
         m_Fields.begin()->pFieldmark = &rFieldmark;
 }
 
-void DocxAttributeOutput::WriteBookmarks_Impl( std::vector< OUString >& 
rStarts, std::vector< OUString >& rEnds )
+void DocxAttributeOutput::WriteBookmarks_Impl( std::vector< OUString >& 
rStarts, std::vector< OUString >& rEnds, const SwRedlineData* pRedlineData )
 {
     for ( const OUString & name : rStarts )
     {
@@ -8505,6 +8554,7 @@ void DocxAttributeOutput::WriteBookmarks_Impl( 
std::vector< OUString >& rStarts,
         else
         {
             m_rBookmarksStart.push_back(name);
+            m_pMoveRedlineData = const_cast<SwRedlineData*>(pRedlineData);
         }
     }
     rStarts.clear();
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx 
b/sw/source/filter/ww8/docxattributeoutput.hxx
index 3897073ffe1f..f05332612e00 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -28,6 +28,7 @@
 #include <IMark.hxx>
 #include "docxexport.hxx"
 #include <wrtswtbl.hxx>
+#include <redline.hxx>
 
 #include <editeng/boxitem.hxx>
 #include <sax/fshelper.hxx>
@@ -393,7 +394,7 @@ public:
             OUString const* pBookmarkName = nullptr);
     void WriteFormData_Impl( const ::sw::mark::IFieldmark& rFieldmark );
 
-    void WriteBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< 
OUString >& rEnds );
+    void WriteBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< 
OUString >& rEnds, const SwRedlineData* pRedlineData = nullptr );
     void WriteFinalBookmarks_Impl( std::vector< OUString >& rStarts, 
std::vector< OUString >& rEnds );
     void WriteAnnotationMarks_Impl( std::vector< OUString >& rStarts, 
std::vector< OUString >& rEnds );
     void PushRelIdCache();
@@ -729,7 +730,10 @@ private:
 
     void DoWriteBookmarkTagStart(const OUString & bookmarkName);
     void DoWriteBookmarkTagEnd(sal_Int32 nId);
-    void DoWriteBookmarksStart(std::vector<OUString>& rStarts);
+    void DoWriteMoveRangeTagStart(const OString & bookmarkName,
+            bool bFrom, const SwRedlineData* pRedlineData);
+    void DoWriteMoveRangeTagEnd(sal_Int32 nId, bool bFrom);
+    void DoWriteBookmarksStart(std::vector<OUString>& rStarts, const 
SwRedlineData* pRedlineData = nullptr);
     void DoWriteBookmarksEnd(std::vector<OUString>& rEnds);
     void DoWriteBookmarkStartIfExist(sal_Int32 nRunPos);
     void DoWriteBookmarkEndIfExist(sal_Int32 nRunPos);
@@ -829,6 +833,7 @@ private:
     /// Bookmarks to output
     std::vector<OUString> m_rBookmarksStart;
     std::vector<OUString> m_rBookmarksEnd;
+    SwRedlineData* m_pMoveRedlineData;
 
     /// Bookmarks to output at the end
     std::vector<OUString> m_rFinalBookmarksStart;
diff --git a/sw/source/filter/ww8/docxexport.cxx 
b/sw/source/filter/ww8/docxexport.cxx
index 1afc002f6e75..0f1ca6ddb969 100644
--- a/sw/source/filter/ww8/docxexport.cxx
+++ b/sw/source/filter/ww8/docxexport.cxx
@@ -66,6 +66,7 @@
 #include <ftninfo.hxx>
 #include <pagedesc.hxx>
 #include <poolfmt.hxx>
+#include <redline.hxx>
 #include <swdbdata.hxx>
 
 #include <editeng/unoprnms.hxx>
@@ -145,7 +146,7 @@ bool DocxExport::CollapseScriptsforWordOk( sal_uInt16 
nScript, sal_uInt16 nWhich
     return true;
 }
 
-void DocxExport::AppendBookmarks( const SwTextNode& rNode, sal_Int32 
nCurrentPos, sal_Int32 nLen )
+void DocxExport::AppendBookmarks( const SwTextNode& rNode, sal_Int32 
nCurrentPos, sal_Int32 nLen, const SwRedlineData* pRedlineData )
 {
     std::vector< OUString > aStarts;
     std::vector< OUString > aEnds;
@@ -172,7 +173,7 @@ void DocxExport::AppendBookmarks( const SwTextNode& rNode, 
sal_Int32 nCurrentPos
     if ( nCurrentPos == nEnd )
         m_pAttrOutput->WriteFinalBookmarks_Impl( aStarts, aEnds );
     else
-        m_pAttrOutput->WriteBookmarks_Impl( aStarts, aEnds );
+        m_pAttrOutput->WriteBookmarks_Impl( aStarts, aEnds, pRedlineData );
 }
 
 void DocxExport::AppendBookmark( const OUString& rName )
diff --git a/sw/source/filter/ww8/docxexport.hxx 
b/sw/source/filter/ww8/docxexport.hxx
index 91ca7c82d154..cdd18c9e4510 100644
--- a/sw/source/filter/ww8/docxexport.hxx
+++ b/sw/source/filter/ww8/docxexport.hxx
@@ -145,7 +145,7 @@ public:
     /// Guess the script (asian/western).
     virtual bool CollapseScriptsforWordOk( sal_uInt16 nScript, sal_uInt16 
nWhich ) override;
 
-    virtual void AppendBookmarks( const SwTextNode& rNode, sal_Int32 
nCurrentPos, sal_Int32 nLen ) override;
+    virtual void AppendBookmarks( const SwTextNode& rNode, sal_Int32 
nCurrentPos, sal_Int32 nLen, const SwRedlineData* pRedlineData = nullptr ) 
override;
 
     virtual void AppendBookmark( const OUString& rName ) override;
 
diff --git a/sw/source/filter/ww8/rtfexport.cxx 
b/sw/source/filter/ww8/rtfexport.cxx
index 2be381a3dfb2..68fd898ab1ff 100644
--- a/sw/source/filter/ww8/rtfexport.cxx
+++ b/sw/source/filter/ww8/rtfexport.cxx
@@ -118,7 +118,8 @@ bool RtfExport::CollapseScriptsforWordOk(sal_uInt16 
nScript, sal_uInt16 nWhich)
     return true;
 }
 
-void RtfExport::AppendBookmarks(const SwTextNode& rNode, sal_Int32 
nCurrentPos, sal_Int32 nLen)
+void RtfExport::AppendBookmarks(const SwTextNode& rNode, sal_Int32 
nCurrentPos, sal_Int32 nLen,
+                                const SwRedlineData* /*pRedlineData*/)
 {
     std::vector<OUString> aStarts;
     std::vector<OUString> aEnds;
diff --git a/sw/source/filter/ww8/rtfexport.hxx 
b/sw/source/filter/ww8/rtfexport.hxx
index 48b3379d4aa3..36a735eb783a 100644
--- a/sw/source/filter/ww8/rtfexport.hxx
+++ b/sw/source/filter/ww8/rtfexport.hxx
@@ -67,7 +67,8 @@ public:
     /// Guess the script (asian/western).
     bool CollapseScriptsforWordOk(sal_uInt16 nScript, sal_uInt16 nWhich) 
override;
 
-    void AppendBookmarks(const SwTextNode& rNode, sal_Int32 nCurrentPos, 
sal_Int32 nLen) override;
+    void AppendBookmarks(const SwTextNode& rNode, sal_Int32 nCurrentPos, 
sal_Int32 nLen,
+                         const SwRedlineData* pSwRedlineData = nullptr) 
override;
 
     void AppendBookmark(const OUString& rName) override;
 
diff --git a/sw/source/filter/ww8/wrtw8nds.cxx 
b/sw/source/filter/ww8/wrtw8nds.cxx
index f33a52708a57..4f3ca7f94305 100644
--- a/sw/source/filter/ww8/wrtw8nds.cxx
+++ b/sw/source/filter/ww8/wrtw8nds.cxx
@@ -1208,10 +1208,28 @@ bool WW8AttributeOutput::EndURL(bool const)
     return true;
 }
 
-OUString BookmarkToWord(const OUString &rBookmark)
+OUString BookmarkToWord(const OUString &rBookmark, bool* pIsMove, bool* 
pIsFrom)
 {
+    sal_Int32 nTrim = 0; // position to remove "__RefMoveRange" from bookmark 
names
+    if ( pIsMove )
+    {
+        static constexpr OUStringLiteral MoveFrom_Bookmark_NamePrefix = 
u"__RefMoveFrom__";
+        static constexpr OUStringLiteral MoveTo_Bookmark_NamePrefix = 
u"__RefMoveTo__";
+        if ( rBookmark.startsWith(MoveFrom_Bookmark_NamePrefix) )
+        {
+            *pIsMove = true;
+            *pIsFrom = true;
+            nTrim = MoveFrom_Bookmark_NamePrefix.getLength();
+        }
+        else if ( rBookmark.startsWith(MoveTo_Bookmark_NamePrefix) )
+        {
+            *pIsMove = true;
+            *pIsFrom = false;
+            nTrim = MoveTo_Bookmark_NamePrefix.getLength();
+        }
+    }
     OUString sRet(INetURLObject::encode(
-        rBookmark.replace(' ', '_'), // Spaces are prohibited in bookmark name
+        rBookmark.copy(nTrim).replace(' ', '_'), // Spaces are prohibited in 
bookmark name
         INetURLObject::PART_REL_SEGMENT_EXTRA,
         INetURLObject::EncodeMechanism::All, RTL_TEXTENCODING_ASCII_US));
     // Unicode letters are allowed
@@ -2418,7 +2436,7 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode )
             AttrOutput().SetAnchorIsLinkedToNode( bPostponeWritingText && 
(FLY_POSTPONED != nStateOfFlyFrame) );
             // Append bookmarks in this range after flys, exclusive of final
             // position of this range
-            AppendBookmarks( rNode, nCurrentPos, nNextAttr - nCurrentPos );
+            AppendBookmarks( rNode, nCurrentPos, nNextAttr - nCurrentPos, 
pRedlineData );
             // Sadly only possible for main or glossary document parts: 
ECMA-376 Part 1 sect. 11.3.2
             if ( m_nTextTyp == TXT_MAINTEXT )
                 AppendAnnotationMarks(aAttrIter, nCurrentPos, nNextAttr - 
nCurrentPos);
diff --git a/sw/source/filter/ww8/wrtww8.cxx b/sw/source/filter/ww8/wrtww8.cxx
index 2c2f40db021e..d179858fd3f3 100644
--- a/sw/source/filter/ww8/wrtww8.cxx
+++ b/sw/source/filter/ww8/wrtww8.cxx
@@ -1415,7 +1415,7 @@ WW8_CP WW8_WrPct::Fc2Cp( sal_uLong nFc ) const
     return nFc + m_Pcts.back()->GetStartCp();
 }
 
-void WW8Export::AppendBookmarks( const SwTextNode& rNd, sal_Int32 nCurrentPos, 
sal_Int32 nLen )
+void WW8Export::AppendBookmarks( const SwTextNode& rNd, sal_Int32 nCurrentPos, 
sal_Int32 nLen, const SwRedlineData* /*pRedlineData*/ )
 {
     std::vector< const ::sw::mark::IMark* > aArr;
     sal_uInt16 nContent;
diff --git a/sw/source/filter/ww8/wrtww8.hxx b/sw/source/filter/ww8/wrtww8.hxx
index 1051c8bf5fa4..72e5a8d65e1e 100644
--- a/sw/source/filter/ww8/wrtww8.hxx
+++ b/sw/source/filter/ww8/wrtww8.hxx
@@ -704,7 +704,7 @@ public:
     /// has two
     virtual bool CollapseScriptsforWordOk( sal_uInt16 nScript, sal_uInt16 
nWhich ) = 0;
 
-    virtual void AppendBookmarks( const SwTextNode& rNd, sal_Int32 
nCurrentPos, sal_Int32 nLen ) = 0;
+    virtual void AppendBookmarks( const SwTextNode& rNd, sal_Int32 
nCurrentPos, sal_Int32 nLen, const SwRedlineData* pSwRedline = nullptr ) = 0;
 
     virtual void AppendBookmark( const OUString& rName ) = 0;
 
@@ -1074,7 +1074,7 @@ public:
                              const tools::SvRef<SotStorage>& xObjStg, OUString 
const& rStorageName,
                              SwOLENode* pOLENd);
 
-    virtual void AppendBookmarks( const SwTextNode& rNd, sal_Int32 
nCurrentPos, sal_Int32 nLen ) override;
+    virtual void AppendBookmarks( const SwTextNode& rNd, sal_Int32 
nCurrentPos, sal_Int32 nLen, const SwRedlineData* pRedlineData = nullptr ) 
override;
     virtual void AppendBookmark( const OUString& rName ) override;
     void AppendBookmarkEndWithCorrection( const OUString& rName );
 
@@ -1633,7 +1633,7 @@ public:
 sal_Int16 GetWordFirstLineOffset(const SwNumFormat &rFormat);
 // A bit of a bag on the side for now
 OUString FieldString(ww::eField eIndex);
-OUString BookmarkToWord(const OUString &rBookmark);
+OUString BookmarkToWord(const OUString &rBookmark, bool* pIsMove = nullptr, 
bool* pIsFrom = nullptr);
 
 class WW8SHDLong
 {
diff --git a/writerfilter/source/dmapper/DomainMapper.cxx 
b/writerfilter/source/dmapper/DomainMapper.cxx
index b247e0156b38..2958f004553d 100644
--- a/writerfilter/source/dmapper/DomainMapper.cxx
+++ b/writerfilter/source/dmapper/DomainMapper.cxx
@@ -2500,10 +2500,12 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
     }
     break;
     case NS_ooxml::LN_EG_RangeMarkupElements_moveFromRangeStart:
+        m_pImpl->SetMoveBookmark(/*bIsFrom=*/true);
         if (m_pImpl->hasTableManager())
             m_pImpl->getTableManager().setMoved( 
getPropertyName(PROP_TABLE_ROW_DELETE) );
     break;
     case NS_ooxml::LN_EG_RangeMarkupElements_moveToRangeStart:
+        m_pImpl->SetMoveBookmark(/*bIsFrom=*/false);
         if (m_pImpl->hasTableManager())
             m_pImpl->getTableManager().setMoved( 
getPropertyName(PROP_TABLE_ROW_INSERT) );
     break;
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 97cb3511506b..befa25aed954 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -7051,10 +7051,14 @@ void DomainMapper_Impl::SetBookmarkName( const 
OUString& rBookmarkName )
             }
         }
 
-        aBookmarkIter->second.m_sBookmarkName = rBookmarkName;
+        aBookmarkIter->second.m_sBookmarkName = m_sCurrentBkmkPrefix + 
rBookmarkName;
+        m_sCurrentBkmkPrefix.clear();
     }
     else
+    {
         m_sCurrentBkmkName = rBookmarkName;
+        m_sCurrentBkmkPrefix.clear();
+    }
 }
 
 // This method was used as-is for 
DomainMapper_Impl::startOrEndPermissionRange() implementation.
@@ -7101,7 +7105,10 @@ void DomainMapper_Impl::StartOrEndBookmark( const 
OUString& rId )
                 // then  move the bookmark-End to the earlier paragraph
                 if (IsOutsideAParagraph())
                 {
+                    // keep bookmark range
+                    uno::Reference< text::XTextRange > xStart = 
xCursor->getStart();
                     xCursor->goLeft( 1, false );
+                    xCursor->gotoRange(xStart, true );
                 }
                 uno::Reference< container::XNamed > xBkmNamed( xBookmark, 
uno::UNO_QUERY_THROW );
                 SAL_WARN_IF(aBookmarkIter->second.m_sBookmarkName.isEmpty(), 
"writerfilter.dmapper", "anonymous bookmark");
@@ -7143,6 +7150,16 @@ void DomainMapper_Impl::StartOrEndBookmark( const 
OUString& rId )
     }
 }
 
+void DomainMapper_Impl::SetMoveBookmark( bool bIsFrom )
+{
+    static constexpr OUStringLiteral MoveFrom_Bookmark_NamePrefix = 
u"__RefMoveFrom__";
+    static constexpr OUStringLiteral MoveTo_Bookmark_NamePrefix = 
u"__RefMoveTo__";
+    if ( bIsFrom )
+        m_sCurrentBkmkPrefix = MoveFrom_Bookmark_NamePrefix;
+    else
+        m_sCurrentBkmkPrefix = MoveTo_Bookmark_NamePrefix;
+}
+
 void DomainMapper_Impl::setPermissionRangeEd(const OUString& user)
 {
     PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId);
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
index 7d58fe653716..c41787e09363 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
@@ -488,6 +488,7 @@ private:
     BookmarkMap_t                                                              
     m_aBookmarkMap;
     OUString                                                                   
     m_sCurrentBkmkId;
     OUString                                                                   
     m_sCurrentBkmkName;
+    OUString                                                                   
     m_sCurrentBkmkPrefix;
 
     PermMap_t                                                                  
     m_aPermMap;
     sal_Int32                                                                  
     m_sCurrentPermId;
@@ -913,6 +914,9 @@ public:
     void SetBookmarkName( const OUString& rBookmarkName );
     void StartOrEndBookmark( const OUString& rId );
 
+    OUString m_sBookmarkPrefix;
+    void SetMoveBookmark( bool IsFrom );
+
     void setPermissionRangeEd(const OUString& user);
     void setPermissionRangeEdGrp(const OUString& group);
     void startOrEndPermissionRange(sal_Int32 permissinId);

Reply via email to