tdf#60382 sw xmloff: import/export tracked table/row deletion

to OpenDocument format using

<style:table-row-properties loext:text-changes-only="false"/>

Rename also com::sun::star::text::TextTableRow::IsNotTracked
to com::sun::star::text::TextTableRow::HasTextChangesOnly.

Follow-up to commit 05366b8e6683363688de8708a3d88cf144c7a2bf
"tdf#60382 sw offapi: add change tracking of table/row deletion".

Change-Id: Iefb0d4095af0983fdd15697d5b80073d18d21bd7
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116212
Tested-by: Jenkins
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index b75907d..68efdb4 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -1914,6 +1914,7 @@ namespace xmloff::token {
        XML_TEXT_BACKGROUND_COLOR,
        XML_TEXT_BLINKING,
        XML_TEXT_BOX,
        XML_TEXT_CHANGES_ONLY,
        XML_TEXT_COLOR,
        XML_TEXT_COMBINE,
        XML_TEXT_COMBINE_END_CHAR,
diff --git a/offapi/com/sun/star/text/TextTableRow.idl b/offapi/com/sun/star/text/TextTableRow.idl
index e418fbb..dcecda3 100644
--- a/offapi/com/sun/star/text/TextTableRow.idl
+++ b/offapi/com/sun/star/text/TextTableRow.idl
@@ -111,7 +111,7 @@ published service TextTableRow
        @since LibreOffice 7.2
     */

        [optional, property] boolean IsNotTracked;
        [optional, property] boolean HasTextChangesOnly;

};

diff --git a/schema/libreoffice/OpenDocument-schema-v1.3+libreoffice.rng b/schema/libreoffice/OpenDocument-schema-v1.3+libreoffice.rng
index a50316e..a4d359c 100644
--- a/schema/libreoffice/OpenDocument-schema-v1.3+libreoffice.rng
+++ b/schema/libreoffice/OpenDocument-schema-v1.3+libreoffice.rng
@@ -2651,4 +2651,13 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
    </rng:optional>
  </rng:define>

  <!-- TODO no proposal -->
  <rng:define name="style-table-row-properties-attlist" combine="interleave">
    <rng:optional>
      <rng:attribute name="loext:text-changes-only">
        <rng:ref name="boolean"/>
      </rng:attribute>
    </rng:optional>
  </rng:define>

</rng:grammar>
diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx
index 317ad79..a6fe2db 100644
--- a/sw/inc/unoprnms.hxx
+++ b/sw/inc/unoprnms.hxx
@@ -736,7 +736,7 @@
#define UNO_NAME_ITEMS "Items"
#define UNO_NAME_SELITEM "SelectedItem"
#define UNO_NAME_IS_SPLIT_ALLOWED "IsSplitAllowed"
#define UNO_NAME_IS_NOT_TRACKED "IsNotTracked"
#define UNO_NAME_HAS_TEXT_CHANGES_ONLY "HasTextChangesOnly"
#define UNO_NAME_CHAR_HIDDEN "CharHidden"
#define UNO_NAME_IS_FOLLOWING_TEXT_FLOW "IsFollowingTextFlow"
#define UNO_NAME_WIDTH_TYPE "WidthType"
diff --git a/sw/qa/extras/uiwriter/uiwriter2.cxx b/sw/qa/extras/uiwriter/uiwriter2.cxx
index 3da57c9..2453f3a 100644
--- a/sw/qa/extras/uiwriter/uiwriter2.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter2.cxx
@@ -3690,7 +3690,7 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testRedlineTableRowDeletion)
    assertXPath(pXmlDoc, "//page[1]//body/tab");

    // delete table row with enabled change tracking
    // (IsNotTracked property of the row will be false)
    // (HasTextChangesOnly property of the row will be false)
    dispatchCommand(mxComponent, ".uno:DeleteRows", {});

    // This was deleted without change tracking
@@ -3717,7 +3717,7 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testRedlineTableRowDeletion)
    assertXPath(pXmlDoc, "//page[1]//body/tab", 0);

    // Undo, and repeat the previous test, but only with deletion of the text content of the cells
    // (IsNotTracked property will be removed by Undo)
    // (HasTextChangesOnly property will be removed by Undo)

    dispatchCommand(mxComponent, ".uno:Undo", {});
    dispatchCommand(mxComponent, ".uno:Undo", {});
@@ -3750,7 +3750,7 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testRedlineTableRowDeletion)
    pEditShell->AcceptRedline(0);

    // table row (and the 1-row table) still exists
    // (IsNotTracked property wasn't set for table row deletion)
    // (HasTextChangesOnly property wasn't set for table row deletion)
    discardDumpedLayout();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "//page[1]//body/tab");
@@ -3782,6 +3782,63 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testRedlineTableRowDeletion)
    assertXPath(pXmlDoc, "//page[1]//body/tab", 0);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testRedlineTableRowDeletionWithExport)
{
    // load a 1-row table, and delete the row with enabled change tracking:
    // now the row is not deleted silently, but keeps the deleted cell contents,
    // and only accepting all of them will result the deletion of the table row.
    SwDoc* pDoc = createDoc("tdf118311.fodt");

    SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
    CPPUNIT_ASSERT(pTextDoc);

    // 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
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "//page[1]//body/tab");

    // delete table row with enabled change tracking
    // (HasTextChangesOnly property of the row will be false)
    dispatchCommand(mxComponent, ".uno:DeleteRows", {});

    // Deleted text content with change tracking,
    // but not table deletion
    discardDumpedLayout();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "//page[1]//body/tab");

    // Save it and load it back.
    reload("writer8", "tdf60382_tracked_table_deletion.odt");
    pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
    pDoc = pTextDoc->GetDocShell()->GetWrtShell()->GetDoc();

    // accept the deletion of the content of the first cell
    SwEditShell* const pEditShell(pDoc->GetEditShell());
    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(2), pEditShell->GetRedlineCount());
    pEditShell->AcceptRedline(0);

    // table row was still not deleted
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "//page[1]//body/tab");

    // accept last redline
    pEditShell->AcceptRedline(0);

    // table row (and the 1-row table) was deleted finally
    // (working export/import of HasTextChangesOnly)
    discardDumpedLayout();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "//page[1]//body/tab", 0);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf128335)
{
    // Load the bugdoc, which has 3 textboxes.
diff --git a/sw/source/core/bastyp/init.cxx b/sw/source/core/bastyp/init.cxx
index f1b86f8..1acc4bf 100644
--- a/sw/source/core/bastyp/init.cxx
+++ b/sw/source/core/bastyp/init.cxx
@@ -206,7 +206,7 @@ sal_uInt16 const aTableSetRange[] = {

sal_uInt16 const aTableLineSetRange[] = {
    RES_FILL_ORDER,     RES_FRM_SIZE,
    // IsNotTracked
    // HasTextChangesOnly
    RES_PRINT,          RES_PRINT,
    RES_LR_SPACE,       RES_UL_SPACE,
    RES_BACKGROUND,     RES_SHADOW,
diff --git a/sw/source/core/doc/DocumentRedlineManager.cxx b/sw/source/core/doc/DocumentRedlineManager.cxx
index 0e92f16..6d23324 100644
--- a/sw/source/core/doc/DocumentRedlineManager.cxx
+++ b/sw/source/core/doc/DocumentRedlineManager.cxx
@@ -437,10 +437,10 @@ namespace
        if ( const SwTableBox* pBox = pPos->nNode.GetNode().GetTableBox() )
        {
            const SwTableLine* pLine = pBox->GetUpper();
            const SvxPrintItem *pIsNoTrackedProp =
            const SvxPrintItem *pHasTextChangesOnlyProp =
                    pLine->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT);
            // table row property "IsNotTracked" is set and its value is false
            if ( pIsNoTrackedProp && !pIsNoTrackedProp->GetValue() )
            // table row property "HasTextChangesOnly" is set and its value is false
            if ( pHasTextChangesOnlyProp && !pHasTextChangesOnlyProp->GetValue() )
            {
                bool bEmptyLine = true;
                const SwTableBoxes & rBoxes = pLine->GetTabBoxes();
diff --git a/sw/source/core/unocore/unomap.cxx b/sw/source/core/unocore/unomap.cxx
index 55b948a..0cb4393 100644
--- a/sw/source/core/unocore/unomap.cxx
+++ b/sw/source/core/unocore/unomap.cxx
@@ -542,7 +542,7 @@ const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetPropertyMapEntries(s
                    { u"" UNO_NAME_SIZE_TYPE, RES_FRM_SIZE,           cppu::UnoType<sal_Int16>::get()  ,         PROPERTY_NONE,   MID_FRMSIZE_SIZE_TYPE  },
                    { u"" UNO_NAME_WIDTH_TYPE, RES_FRM_SIZE,          cppu::UnoType<sal_Int16>::get()  ,         PROPERTY_NONE,   MID_FRMSIZE_WIDTH_TYPE },
                    { u"" UNO_NAME_IS_SPLIT_ALLOWED, RES_ROW_SPLIT,       cppu::UnoType<bool>::get()  , PropertyAttribute::MAYBEVOID, 0},
                    { u"" UNO_NAME_IS_NOT_TRACKED, RES_PRINT, cppu::UnoType<bool>::get()  , PropertyAttribute::MAYBEVOID, 0},
                    { u"" UNO_NAME_HAS_TEXT_CHANGES_ONLY, RES_PRINT, cppu::UnoType<bool>::get()  , PropertyAttribute::MAYBEVOID, 0},
                    { u"" UNO_NAME_ROW_INTEROP_GRAB_BAG, RES_FRMATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0 },
                    { u"", 0, css::uno::Type(), 0, 0 }
                };
diff --git a/sw/source/filter/xml/xmlexpit.cxx b/sw/source/filter/xml/xmlexpit.cxx
index 4ab794c..b0fdfe5 100644
--- a/sw/source/filter/xml/xmlexpit.cxx
+++ b/sw/source/filter/xml/xmlexpit.cxx
@@ -46,6 +46,7 @@
#include <editeng/keepitem.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/frmdiritem.hxx>
#include <editeng/prntitem.hxx>
#include <fmtpdsc.hxx>
#include <fmtornt.hxx>
#include <fmtfsize.hxx>
@@ -871,6 +872,17 @@ bool SvXMLExportItemMapper::QueryXMLValue(
        }
        break;

        case RES_PRINT:
        {
            const SvxPrintItem* pHasTextChangesOnly = dynamic_cast<const SvxPrintItem*>( &rItem );
            if (pHasTextChangesOnly && !pHasTextChangesOnly->GetValue())
            {
                aOut.append( "false" );
                bOk = true;
            }
        }
        break;

        case RES_BACKGROUND:
        {
            const SvxBrushItem& rBrush = dynamic_cast<const SvxBrushItem&>(rItem);
diff --git a/sw/source/filter/xml/xmlimpit.cxx b/sw/source/filter/xml/xmlimpit.cxx
index 4d84981..d09221b 100644
--- a/sw/source/filter/xml/xmlimpit.cxx
+++ b/sw/source/filter/xml/xmlimpit.cxx
@@ -856,6 +856,23 @@ bool SvXMLImportItemMapper::PutXMLValue(
        }
        break;

        case RES_PRINT:
        {
            SfxBoolItem& rHasTextChangesOnly = dynamic_cast<SfxBoolItem&>(rItem);

            if( IsXMLToken( rValue, XML_TRUE ) )
            {
                rHasTextChangesOnly.SetValue( true );
                bOk = true;
            }
            else if( IsXMLToken( rValue, XML_FALSE ) )
            {
                rHasTextChangesOnly.SetValue( false );
                bOk = true;
            }
        }
        break;

        case RES_HORI_ORIENT:
        {
            SwFormatHoriOrient& rHoriOrient = dynamic_cast<SwFormatHoriOrient&>(rItem);
diff --git a/sw/source/filter/xml/xmliteme.cxx b/sw/source/filter/xml/xmliteme.cxx
index 8594c75..61a82050 100644
--- a/sw/source/filter/xml/xmliteme.cxx
+++ b/sw/source/filter/xml/xmliteme.cxx
@@ -33,6 +33,8 @@
#include <frmfmt.hxx>
#include "xmlexp.hxx"
#include <editeng/memberids.h>
#include <editeng/prntitem.hxx>
#include <xmloff/xmlnamespace.hxx>

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
@@ -105,6 +107,33 @@ void SwXMLTableItemMapper_Impl::handleSpecialItem(
{
    switch( rEntry.nWhichId )
    {

    case RES_PRINT:
        {
            const SfxPoolItem *pItem;
            if( pSet &&
                SfxItemState::SET == pSet->GetItemState( RES_PRINT, true,
                                                    &pItem ) )
            {
                bool bHasTextChangesOnly =
                    static_cast<const SvxPrintItem *>(pItem)->GetValue();
                if ( !bHasTextChangesOnly )
                {
                    OUString sValue;
                    sal_uInt16 nMemberId =
                        static_cast<sal_uInt16>( rEntry.nMemberId & MID_SW_FLAG_MASK );

                    if( SvXMLExportItemMapper::QueryXMLValue(
                        rItem, sValue, nMemberId, rUnitConverter ) )
                    {
                        AddAttribute( rEntry.nNameSpace, rEntry.eLocalName,
                                      sValue, rNamespaceMap, rAttrList );
                    }
               }
           }
        }
        break;

    case RES_LR_SPACE:
        {
            const SfxPoolItem *pItem;
diff --git a/sw/source/filter/xml/xmlitemm.cxx b/sw/source/filter/xml/xmlitemm.cxx
index 66001b1..6464149 100644
--- a/sw/source/filter/xml/xmlitemm.cxx
+++ b/sw/source/filter/xml/xmlitemm.cxx
@@ -151,7 +151,8 @@ SvXMLItemMapEntry const aXMLTableRowItemMap[] =
    // RES_FOOTER
    // not required
    // RES_PRINT
    // not required
    // M_E_SE( STYLE, TEXT_CHANGES_ONLY, RES_PRINT, 0 ),
    M_E_SE( LO_EXT, TEXT_CHANGES_ONLY, RES_PRINT, 0 ),
    // RES_OPAQUE
    // not required
    // RES_PROTECT
diff --git a/sw/source/filter/xml/xmltble.cxx b/sw/source/filter/xml/xmltble.cxx
index b4ba4c9..492eb5f 100644
--- a/sw/source/filter/xml/xmltble.cxx
+++ b/sw/source/filter/xml/xmltble.cxx
@@ -32,6 +32,7 @@
#include <xmloff/numehelp.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/prntitem.hxx>
#include <editeng/xmlcnitm.hxx>
#include <fmtrowsplt.hxx>
#include <editeng/frmdiritem.hxx>
@@ -197,6 +198,7 @@ bool SwXMLTableFrameFormatsSort_Impl::AddRow( SwFrameFormat& rFrameFormat,
    const SwFormatFrameSize *pFrameSize = nullptr;
    const SwFormatRowSplit* pRowSplit = nullptr;
    const SvxBrushItem *pBrush = nullptr;
    const SvxPrintItem *pHasTextChangesOnly = nullptr;

    const SfxItemSet& rItemSet = rFrameFormat.GetAttrSet();
    const SfxPoolItem *pItem;
@@ -209,8 +211,11 @@ bool SwXMLTableFrameFormatsSort_Impl::AddRow( SwFrameFormat& rFrameFormat,
    if( SfxItemState::SET == rItemSet.GetItemState( RES_BACKGROUND, false, &pItem ) )
        pBrush = static_cast<const SvxBrushItem *>(pItem);

    if( SfxItemState::SET == rItemSet.GetItemState( RES_PRINT, false, &pItem ) )
        pHasTextChangesOnly = static_cast<const SvxPrintItem *>(pItem);

    // empty styles have not to be exported
    if( !pFrameSize && !pBrush && !pRowSplit )
    if( !pFrameSize && !pBrush && !pRowSplit && !pHasTextChangesOnly )
        return false;

    // order is: -/brush, size/-, size/brush
@@ -221,6 +226,7 @@ bool SwXMLTableFrameFormatsSort_Impl::AddRow( SwFrameFormat& rFrameFormat,
        const SwFormatFrameSize *pTestFrameSize = nullptr;
        const SwFormatRowSplit* pTestRowSplit = nullptr;
        const SvxBrushItem *pTestBrush = nullptr;
        const SvxPrintItem *pTestHasTextChangesOnly = nullptr;
        const SwFrameFormat *pTestFormat = *i;
        const SfxItemSet& rTestSet = pTestFormat->GetAttrSet();
        if( SfxItemState::SET == rTestSet.GetItemState( RES_FRM_SIZE, false,
@@ -265,6 +271,20 @@ bool SwXMLTableFrameFormatsSort_Impl::AddRow( SwFrameFormat& rFrameFormat,
                continue;
        }

        if( SfxItemState::SET == rTestSet.GetItemState( RES_PRINT, false,
                                                  &pItem ) )
        {
            if( !pHasTextChangesOnly )
                break;

            pTestHasTextChangesOnly = static_cast<const SvxPrintItem *>(pItem);
        }
        else
        {
            if( pHasTextChangesOnly )
                continue;
        }

        if( pFrameSize &&
            ( pFrameSize->GetHeightSizeType() != pTestFrameSize->GetHeightSizeType() ||
              pFrameSize->GetHeight() != pTestFrameSize->GetHeight() ) )
@@ -276,6 +296,9 @@ bool SwXMLTableFrameFormatsSort_Impl::AddRow( SwFrameFormat& rFrameFormat,
        if( pRowSplit && (!pRowSplit->GetValue() != !pTestRowSplit->GetValue()) )
            continue;

        if( pHasTextChangesOnly && (!pHasTextChangesOnly->GetValue() != !pTestHasTextChangesOnly->GetValue()) )
            continue;

        // found!
        rFrameFormat.SetName( pTestFormat->GetName() );
        bInsert = false;
diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index f557d0e..4334f52 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -1920,6 +1920,7 @@ namespace xmloff::token {
        TOKEN( "text-background-color",           XML_TEXT_BACKGROUND_COLOR ),
        TOKEN( "text-blinking",                   XML_TEXT_BLINKING ),
        TOKEN( "text-box",                        XML_TEXT_BOX ),
        TOKEN( "text-changes-only",               XML_TEXT_CHANGES_ONLY ),
        TOKEN( "text-color",                      XML_TEXT_COLOR ),
        TOKEN( "text-combine",                    XML_TEXT_COMBINE ),
        TOKEN( "text-combine-end-char",           XML_TEXT_COMBINE_END_CHAR ),
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index 53b2f76..de859d5 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -1827,6 +1827,7 @@ text-autospace
text-background-color
text-blinking
text-box
text-changes-only
text-color
text-combine
text-combine-end-char