sw/inc/swtable.hxx                           |    7 ++
 sw/qa/extras/uiwriter/uiwriter2.cxx          |   53 ++++++++++++++++++++
 sw/source/core/table/swtable.cxx             |   71 +++++++++++++++++++++------
 sw/source/filter/ww8/docxattributeoutput.cxx |   69 +++++++-------------------
 4 files changed, 135 insertions(+), 65 deletions(-)

New commits:
commit 2fe0881906462cbcbe8baf81068010310fb1c97a
Author:     László Németh <nem...@numbertext.org>
AuthorDate: Tue Nov 9 16:11:03 2021 +0100
Commit:     László Németh <nem...@numbertext.org>
CommitDate: Tue Nov 9 20:14:57 2021 +0100

    tdf#145089 DOCX export: fix track changes of table row insertion
    
    Newly inserted table rows lost their change tracking during
    DOCX import, i.e. rejection didn't remove the rows.
    
    Note: start to clean-up DOCX export and SwTableLine::IsDeleted(),
    preparing the fix for tdf#145091.
    
    Follow-up to commit dbc82c02eb24ec1c97c6ee32069771d8deb394f9
    "tdf#143358 sw: track insertion of empty table rows".
    
    Change-Id: Ib90b745632ec4aeb30651fbff209ecef69657f4d
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/124922
    Tested-by: László Németh <nem...@numbertext.org>
    Reviewed-by: László Németh <nem...@numbertext.org>

diff --git a/sw/inc/swtable.hxx b/sw/inc/swtable.hxx
index f358d348ab5f..e4ba92aaa6df 100644
--- a/sw/inc/swtable.hxx
+++ b/sw/inc/swtable.hxx
@@ -397,8 +397,13 @@ public:
     // it doesn't contain box content
     bool IsEmpty() const;
 
+    // Update TextChangesOnly property based on the redlines of the table row.
+    // rRedlinePos: search from this redline to speed up SwTable::IsDeleted().
+    // Return with the redline, which associated to the row change (latest 
deletion
+    // in the case of deleted row, the first insertion in the case of row 
insertion
+    // or npos, if TextChangesOnly is true, i.e. the table row is not deleted 
or inserted).
+    SwRedlineTable::size_type UpdateTextChangesOnly(SwRedlineTable::size_type& 
rRedlinePos) const;
     // is it a tracked deleted row
-    // (search its first redline from rRedlinePos to speed up 
SwTable::IsDeleted())
     bool IsDeleted(SwRedlineTable::size_type& rRedlinePos) const;
 };
 
diff --git a/sw/qa/extras/uiwriter/uiwriter2.cxx 
b/sw/qa/extras/uiwriter/uiwriter2.cxx
index 8ab21d4b458a..7fd1ffb9a546 100644
--- a/sw/qa/extras/uiwriter/uiwriter2.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter2.cxx
@@ -4889,6 +4889,59 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, 
testRedlineTableRowInsertionWithReject)
     assertXPath(pXmlDoc, "//page[1]//body/tab/row", 1);
 }
 
+CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, 
testTdf145089_RedlineTableRowInsertionDOCX)
+{
+    // load a 1-row table, and insert a row with enabled change tracking
+    SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf118311.fodt");
+
+    // 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()));
+
+    // check table and its single row
+    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
+    assertXPath(pXmlDoc, "//page[1]//body/tab");
+    assertXPath(pXmlDoc, "//page[1]//body/tab/row", 1);
+
+    // insert rows before and after with enabled change tracking
+    // (HasTextChangesOnly property of the row will be false, and
+    // add dummy characters CH_TXT_TRACKED_DUMMY_CHAR)
+    dispatchCommand(mxComponent, ".uno:InsertRowsBefore", {});
+    dispatchCommand(mxComponent, ".uno:InsertRowsAfter", {});
+
+    // save it to DOCX
+    reload("Office Open XML Text", "tdf145089.docx");
+    SwXTextDocument* pTextDoc = 
dynamic_cast<SwXTextDocument*>(mxComponent.get());
+    SwViewShell* pViewShell
+        = 
pTextDoc->GetDocShell()->GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell();
+    pViewShell->Reformat();
+    discardDumpedLayout();
+    pXmlDoc = parseLayoutDump();
+
+    assertXPath(pXmlDoc, "//page[1]//body/tab");
+    assertXPath(pXmlDoc, "//page[1]//body/tab/row", 3);
+
+    // reject redlines
+    SwDoc* pDOCXDoc(pTextDoc->GetDocShell()->GetDoc());
+    SwEditShell* const pEditShell(pDOCXDoc->GetEditShell());
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(2), 
pEditShell->GetRedlineCount());
+    pEditShell->RejectRedline(0);
+    pEditShell->RejectRedline(0);
+
+    discardDumpedLayout();
+
+    pXmlDoc = parseLayoutDump();
+    assertXPath(pXmlDoc, "//page[1]//body/tab");
+
+    // This was 3 (not rejected row insertion)
+    assertXPath(pXmlDoc, "//page[1]//body/tab/row", 1);
+}
+
 CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf128603)
 {
     // Load the bugdoc, which has 3 textboxes.
diff --git a/sw/source/core/table/swtable.cxx b/sw/source/core/table/swtable.cxx
index 9f68ca5203ff..da6255a73770 100644
--- a/sw/source/core/table/swtable.cxx
+++ b/sw/source/core/table/swtable.cxx
@@ -1592,12 +1592,17 @@ bool SwTable::IsDeleted() const
     return true;
 }
 
-bool SwTableLine::IsDeleted(SwRedlineTable::size_type& rRedlinePos) const
-{
-    bool bRet = false;
+// TODO Set HasTextChangesOnly=true, if needed based on the redlines in the 
cells.
+// At tracked row deletion, return with the newest deletion of the row or
+// at tracked row insertion, return with the oldest insertion in the row, which
+// contain the change data of the row change.
+// If the return value is SwRedlineTable::npos, there is no tracked row change.
+SwRedlineTable::size_type 
SwTableLine::UpdateTextChangesOnly(SwRedlineTable::size_type& rRedlinePos) const
+{
+    SwRedlineTable::size_type nRet = SwRedlineTable::npos;
     const SwRedlineTable& aRedlineTable = 
GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable();
     if ( aRedlineTable.empty() )
-        return false;
+        return nRet;
 
     // check table row property "HasTextChangesOnly", if it's defined and its
     // value is false, and all text content is in delete redlines, the row is 
deleted
@@ -1607,6 +1612,8 @@ bool SwTableLine::IsDeleted(SwRedlineTable::size_type& 
rRedlinePos) const
     {
         const SwTableBoxes & rBoxes = GetTabBoxes();
         size_t nBoxes = rBoxes.size();
+        bool bInsertion = false;
+
         for (size_t nBoxIndex = 0; nBoxIndex < nBoxes && rRedlinePos < 
aRedlineTable.size(); ++nBoxIndex)
         {
             auto pBox = rBoxes[nBoxIndex];
@@ -1616,10 +1623,11 @@ bool SwTableLine::IsDeleted(SwRedlineTable::size_type& 
rRedlinePos) const
                continue;
             }
 
+            bool bHasRedline = false;
             SwPosition aCellStart( SwNodeIndex( *pBox->GetSttNd(), 0 ) );
             SwPosition aCellEnd( SwNodeIndex( 
*pBox->GetSttNd()->EndOfSectionNode(), -1 ) );
             SwNodeIndex pEndNodeIndex(aCellEnd.nNode.GetNode());
-            for( bRet = false ; rRedlinePos < aRedlineTable.size(); 
++rRedlinePos )
+            for( ; rRedlinePos < aRedlineTable.size(); ++rRedlinePos )
             {
                 const SwRangeRedline* pRedline = aRedlineTable[ rRedlinePos ];
 
@@ -1630,26 +1638,61 @@ bool SwTableLine::IsDeleted(SwRedlineTable::size_type& 
rRedlinePos) const
                     break;
                 }
 
-                // redline in the cell, it must be a delete redline
+                // redline in the cell
                 if ( aCellStart <= *pRedline->Start() )
                 {
-                    bRet = RedlineType::Delete == pRedline->GetType();
-                    if ( !bRet )
-                        // other type of redline, e.g. tracked row insertion
-                        // contains an insert redline at the beginning of the 
first cell
-                        return false;
+                    bHasRedline = true;
+                    RedlineType nType = pRedline->GetType();
+
+                    // first insert redline
+                    if ( !bInsertion && RedlineType::Insert == nType )
+                    {
+                        bInsertion = true;
+                        nRet = rRedlinePos;
+                        continue;
+                        // TODO check older delete redlines to remove row 
change, if needed
+                    }
+
+                    // search newest deletion or oldest insertion
+                    if ( ( !bInsertion && RedlineType::Delete == nType &&
+                           ( nRet == SwRedlineTable::npos ||
+                             
aRedlineTable[nRet]->GetRedlineData().GetTimeStamp() <
+                                 pRedline->GetRedlineData().GetTimeStamp() ) ) 
||
+                         ( bInsertion && RedlineType::Insert == nType &&
+                           ( nRet == SwRedlineTable::npos ||
+                             
aRedlineTable[nRet]->GetRedlineData().GetTimeStamp() >
+                                 pRedline->GetRedlineData().GetTimeStamp() ) ) 
)
+                    {
+                        nRet = rRedlinePos;
+                    }
                 }
             }
 
-            if ( !bRet )
+            if ( !bHasRedline && !bInsertion )
             {
                 // not deleted cell content: the row is not empty
-                return false;
+                // maybe insertion of a row, try to search it
+                bInsertion = true;
+                // drop collected deletion
+                nRet = SwRedlineTable::npos;
             }
             // TODO: check also text outside of the redlines
         }
     }
-    return bRet;
+    return nRet;
+}
+
+bool SwTableLine::IsDeleted(SwRedlineTable::size_type& rRedlinePos) const
+{
+   SwRedlineTable::size_type nPos = UpdateTextChangesOnly(rRedlinePos);
+   if ( nPos != SwRedlineTable::npos )
+   {
+       const SwRedlineTable& aRedlineTable =
+           
GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable();
+       if ( RedlineType::Delete == aRedlineTable[nPos]->GetType() )
+           return true;
+   }
+   return false;
 }
 
 SwTableBox::SwTableBox( SwTableBoxFormat* pFormat, sal_uInt16 nLines, 
SwTableLine *pUp )
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index 035e1e8e4d62..b2b7a047b0f2 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -4379,68 +4379,37 @@ void DocxAttributeOutput::TableRowRedline( 
ww8::WW8TableNodeInfoInner::Pointer_t
     const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
     const SwTableLine * pTabLine = pTabBox->GetUpper();
 
-    // check table row property "HasTextChangesOnly" (used only for tracked 
deletion, yet)
-    const SwRedlineTable& aRedlineTable = 
m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable();
-    const SvxPrintItem *pHasTextChangesOnlyProp =
-            
pTabLine->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT);
-
     bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
         SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
 
-    if ( !aRedlineTable.empty() && pHasTextChangesOnlyProp && 
!pHasTextChangesOnlyProp->GetValue() )
-    {
-        // Tracked row deletion is associated to the newest redline range in 
the row.
-        // Search it to get the date and the mandatory author.
-        const SwTableBoxes & rBoxes = pTabLine->GetTabBoxes();
-        SwPosition aRowStart( SwNodeIndex( *rBoxes[0]->GetSttNd(), 0 ) );
-        SwPosition aRowEnd( SwNodeIndex( *rBoxes[rBoxes.size() - 
1]->GetSttNd()->EndOfSectionNode(), -1 ) );
-        SwNodeIndex pEndNodeIndex(aRowEnd.nNode.GetNode());
-
-        SwRedlineTable::size_type nLastDeletion = SwRedlineTable::npos;
-        for( SwRedlineTable::size_type n = 0; n < aRedlineTable.size(); ++n )
-        {
-            const SwRangeRedline* pRedline = aRedlineTable[ n ];
-
-            if ( pRedline->Start()->nNode > pEndNodeIndex )
-                break;
-
-            if( RedlineType::Delete != pRedline->GetType() )
-                continue;
-
-            // redline is in the table row, and newer, than the previous
-            if ( aRowStart <= *pRedline->Start() )
-            {
-                if ( nLastDeletion == SwRedlineTable::npos ||
-                         aRedlineTable [ nLastDeletion 
]->GetRedlineData().GetTimeStamp() <
-                             pRedline->GetRedlineData().GetTimeStamp() )
-                {
-                    nLastDeletion = n;
-                }
-            }
-        }
-
-        if ( nLastDeletion != SwRedlineTable::npos )
-        {
-            const SwRedlineData& aRedlineData = aRedlineTable[ nLastDeletion 
]->GetRedlineData();
-            // Note: all redline ranges and table row redline (with the same 
author and timestamp)
-            // use the same redline id in OOXML exported by MSO, but it seems, 
the recent solution
-            // (different IDs for different ranges, also row changes) is also 
portable.
-            OString aId( OString::number( m_nRedlineId++ ) );
-            const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( 
aRedlineData.GetAuthor() ) );
-            OString aAuthor( OUStringToOString( bRemovePersonalInfo
+    // check table row property "HasTextChangesOnly"
+    SwRedlineTable::size_type nPos(0);
+    SwRedlineTable::size_type nChange = pTabLine->UpdateTextChangesOnly(nPos);
+    if ( nChange != SwRedlineTable::npos )
+    {
+        const SwRedlineTable& aRedlineTable = 
m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable();
+        const SwRangeRedline* pRedline = aRedlineTable[ nChange ];
+        const SwRedlineData& aRedlineData = pRedline->GetRedlineData();
+
+        // Note: all redline ranges and table row redline (with the same 
author and timestamp)
+        // use the same redline id in OOXML exported by MSO, but it seems, the 
recent solution
+        // (different IDs for different ranges, also row changes) is also 
portable.
+        OString aId( OString::number( m_nRedlineId++ ) );
+        const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( 
aRedlineData.GetAuthor() ) );
+        OString aAuthor( OUStringToOString( bRemovePersonalInfo
                         ? "Author" + OUString::number( 
GetExport().GetInfoID(rAuthor) )
                         : rAuthor, RTL_TEXTENCODING_UTF8 ) );
 
-            OString aDate( DateTimeToOString( bRemovePersonalInfo
+        OString aDate( DateTimeToOString( bRemovePersonalInfo
                     ? DateTime(Date( 1, 1, 1970 )) // Epoch time
                     : aRedlineData.GetTimeStamp() ) );
 
-            m_pSerializer->singleElementNS( XML_w, XML_del,
+        m_pSerializer->singleElementNS( XML_w,
+                            RedlineType::Delete == pRedline->GetType() ? 
XML_del : XML_ins,
                             FSNS( XML_w, XML_id ), aId,
                             FSNS( XML_w, XML_author ), aAuthor,
                             FSNS( XML_w, XML_date ), aDate );
-            return;
-        }
+        return;
     }
 
     // search next Redline (only deletion of empty rows and all insertions 
imported from a DOCX)

Reply via email to