/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <libxml/xmlwriter.h>

#include <UndoTable.hxx>
#include <UndoRedline.hxx>
#include <UndoDelete.hxx>
#include <UndoSplitMove.hxx>
#include <UndoCore.hxx>
#include <fesh.hxx>
#include <fmtpdsc.hxx>
#include <hintids.hxx>
#include <hints.hxx>
#include <doc.hxx>
#include <docredln.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentChartDataProviderAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <rootfrm.hxx>
#include <editsh.hxx>
#include <docary.hxx>
#include <ndtxt.hxx>
#include <swtable.hxx>
#include <pam.hxx>
#include <tblsel.hxx>
#include <swundo.hxx>
#include <rolbck.hxx>
#include <ddefld.hxx>
#include <tabfrm.hxx>
#include <tblafmt.hxx>
#include <poolfmt.hxx>
#include <mvsave.hxx>
#include <cellatr.hxx>
#include <swtblfmt.hxx>
#include <swddetbl.hxx>
#include <redline.hxx>
#include <node2lay.hxx>
#include <tblrwcl.hxx>
#include <fmtanchr.hxx>
#include <strings.hrc>
#include <unochart.hxx>
#include <calbck.hxx>
#include <frameformats.hxx>
#include <editeng/formatbreakitem.hxx>
#include <officecfg/Office/Writer.hxx>
#include <osl/diagnose.h>
#include <docsh.hxx>

#include <memory>
#include <utility>
#include <vector>

#ifdef DBG_UTIL
#define CHECK_TABLE(t) (t).CheckConsistency();
#else
#define CHECK_TABLE(t)
#endif

#ifdef DBG_UTIL
    #define DEBUG_REDLINE( rDoc ) sw_DebugRedline( rDoc );
#else
    #define DEBUG_REDLINE( rDoc )
#endif

typedef std::vector<std::shared_ptr<SfxItemSet> > SfxItemSets;

struct UndoTableCpyTable_Entry
{
    SwNodeOffset nBoxIdx, nOffset;
    std::unique_ptr<SfxItemSet> pBoxNumAttr;
    std::unique_ptr<SwUndo> pUndo;

    // Was the last paragraph of the new and the first paragraph of the old content joined?
    bool bJoin; // For redlining only

    explicit UndoTableCpyTable_Entry( const SwTableBox& rBox );

    void dumpAsXml(xmlTextWriterPtr pWriter) const;
};

namespace {

class SaveBox;
class SaveLine;

void KillEmptyFrameFormat(SwFrameFormat& rFormat)
{
    if(!rFormat.HasWriterListeners())
        delete &rFormat;
};

}

class SaveTable
{
    friend SaveBox;
    friend SaveLine;
    SfxItemSet m_aTableSet;
    std::unique_ptr<SaveLine> m_pLine;
    const SwTable* m_pSwTable;
    SfxItemSets m_aSets;
    SwFrameFormatsV m_aFrameFormats;
    sal_uInt16 m_nLineCount;
    bool m_bModifyBox : 1;
    bool m_bSaveFormula : 1;
    bool m_bNewModel : 1;

    SaveTable(const SaveTable&) = delete;
    SaveTable& operator=(const SaveTable&) = delete;
    SwFrameFormat& CreateNewFormat(SwFrameFormat& rFormat, sal_uInt16 nFormatPos);

public:
    SaveTable( const SwTable& rTable, sal_uInt16 nLnCnt = USHRT_MAX,
                bool bSaveFormula = true );

    sal_uInt16 AddFormat( SwFrameFormat* pFormat, bool bIsLine );
    void NewFrameFormatForLine(const SwTableLine&, sal_uInt16 nFormatPos, SwFrameFormat* pOldFormat);
    void NewFrameFormatForBox(const SwTableBox&, sal_uInt16 nFormatPos, SwFrameFormat* pOldFormat);

    void RestoreAttr( SwTable& rTable, bool bModifyBox = false );
    void SaveContentAttrs( SwDoc& rDoc );
    void CreateNew( SwTable& rTable, bool bCreateFrames = true,
                    bool bRestoreChart = true );
    bool IsNewModel() const { return m_bNewModel; }
};

namespace {

class SaveLine
{
    friend SaveTable;
    friend class SaveBox;

    SaveLine* m_pNext;
    SaveBox* m_pBox;
    sal_uInt16 m_nItemSet;

    SaveLine(const SaveLine&) = delete;
    SaveLine& operator=(const SaveLine&) = delete;

public:
    SaveLine( SaveLine* pPrev, const SwTableLine& rLine, SaveTable& rSTable );
    ~SaveLine();

    void RestoreAttr( SwTableLine& rLine, SaveTable& rSTable );
    void SaveContentAttrs( SwDoc& rDoc );

    void CreateNew( SwTable& rTable, SwTableBox& rParent, SaveTable& rSTable  );
};

class SaveBox
{
    friend class SaveLine;

    SaveBox* m_pNext;
    SwNodeOffset m_nStartNode;
    sal_Int32 m_nRowSpan;
    sal_uInt16 m_nItemSet;
    bool m_bHasDirectFormatting : 1;
    union
    {
        SfxItemSets* pContentAttrs;
        SaveLine* pLine;
    } m_Ptrs;

public:
    SaveBox( SaveBox* pPrev, const SwTableBox& rBox, SaveTable& rSTable );
    ~SaveBox();

    void RestoreAttr( SwTableBox& rBox, SaveTable& rSTable );
    void SaveContentAttrs( SwDoc& rDoc );

    void CreateNew( SwTable& rTable, SwTableLine& rParent, SaveTable& rSTable );
};

}

#if OSL_DEBUG_LEVEL > 0
static void CheckTable( const SwTable& );
#define CHECKTABLE(t) CheckTable( t );
#else
#define CHECKTABLE(t)
#endif

/* #130880: Crash in undo of table to text when the table has (freshly) merged cells
The order of cell content nodes in the nodes array is not given by the recursive table structure.
The algorithm must not rely on this even it holds for a fresh loaded table in odt file format.
So we need to remember not only the start node position but the end node position as well.
*/

struct SwTableToTextSave
{
    SwNodeOffset m_nSttNd;
    SwNodeOffset m_nEndNd;
    sal_Int32 m_nContent;
    std::unique_ptr<SwHistory> m_pHstry;
    // metadata references for first and last paragraph in cell
    std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoStart;
    std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoEnd;

    SwTableToTextSave( SwDoc& rDoc, SwNodeOffset nNd, SwNodeOffset nEndIdx, sal_Int32 nContent );

private:
    SwTableToTextSave(const SwTableToTextSave&) = delete;
    SwTableToTextSave& operator=(const SwTableToTextSave&) = delete;

};

WhichRangesContainer const aSave_BoxContentSet(svl::Items<
    RES_CHRATR_COLOR, RES_CHRATR_CROSSEDOUT,
    RES_CHRATR_FONT, RES_CHRATR_FONTSIZE,
    RES_CHRATR_POSTURE, RES_CHRATR_POSTURE,
    RES_CHRATR_SHADOWED, RES_CHRATR_WEIGHT,
    RES_PARATR_ADJUST, RES_PARATR_ADJUST>);

SwUndoInsTable::SwUndoInsTable( const SwPosition& rPos, sal_uInt16 nCl, sal_uInt16 nRw,
                            sal_uInt16 nAdj, const SwInsertTableOptions& rInsTableOpts,
                            const SwTableAutoFormat* pTAFormat,
                            const std::vector<sal_uInt16> *pColArr,
                            const UIName & rName)
    : SwUndo( SwUndoId::INSTABLE, rPos.GetDoc() ),
    m_aInsTableOptions( rInsTableOpts ),
    m_nStartNode( rPos.GetNodeIndex() ), m_nRows( nRw ), m_nColumns( nCl ), m_nAdjust( nAdj )
{
    if( pColArr )
    {
        m_oColumnWidth.emplace( *pColArr );
    }
    if( pTAFormat )
        m_pAutoFormat.reset( new SwTableAutoFormat( *pTAFormat ) );

    // consider redline
    SwDoc& rDoc = rPos.GetNode().GetDoc();
    if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
    {
        m_pRedlineData.reset( new SwRedlineData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) );
        SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() );
    }

    m_sTableName = rName;
}

SwUndoInsTable::~SwUndoInsTable()
{
    m_pDDEFieldType.reset();
    m_oColumnWidth.reset();
    m_pRedlineData.reset();
    m_pAutoFormat.reset();
}

void SwUndoInsTable::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwNodeIndex aIdx( rDoc.GetNodes(), m_nStartNode );

    SwTableNode* pTableNd = aIdx.GetNode().GetTableNode();
    // tdf#159025 skip undo if SwTableNode is a nullptr
    // I don't know what causes the SwTableNode to be a nullptr in the
    // case of tdf#159025, but at least stop the crashing by skipping
    // this undo request.
    SAL_WARN_IF( !pTableNd, "sw.core", "no TableNode" );
    if( !pTableNd )
        return;

    pTableNd->DelFrames();

    if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
        rDoc.getIDocumentRedlineAccess().DeleteRedline( *pTableNd, true, RedlineType::Any );
    RemoveIdxFromSection( rDoc, m_nStartNode );

    // move hard page breaks into next node
    SwContentNode* pNextNd = rDoc.GetNodes()[ pTableNd->EndOfSectionIndex()+1 ]->GetContentNode();
    if( pNextNd )
    {
        SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat();

        if( const SwFormatPageDesc* pItem = pTableFormat->GetItemIfSet( RES_PAGEDESC,
            false ) )
            pNextNd->SetAttr( *pItem );

        if( const SvxFormatBreakItem* pItem = pTableFormat->GetItemIfSet( RES_BREAK,
            false ) )
            pNextNd->SetAttr( *pItem );

        ::sw::NotifyTableCollapsedParagraph(pNextNd, nullptr);
    }

    m_sTableName = pTableNd->GetTable().GetFrameFormat()->GetName();
    if( auto pDDETable = dynamic_cast<const SwDDETable *>(&pTableNd->GetTable()) )
        m_pDDEFieldType.reset(static_cast<SwDDEFieldType*>(pDDETable->GetDDEFieldType()->Copy().release()));

    rDoc.GetNodes().Delete( aIdx, pTableNd->EndOfSectionIndex() -
                                aIdx.GetIndex() + 1 );

    SwPaM & rPam( rContext.GetCursorSupplier().CreateNewShellCursor() );
    rPam.DeleteMark();
    rPam.GetPoint()->Assign(aIdx);
}

void SwUndoInsTable::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();

    SwEditShell *const pEditShell(rDoc.GetEditShell());
    OSL_ENSURE(pEditShell, "SwUndoInsTable::RedoImpl needs a SwEditShell!");
    if (!pEditShell)
    {
        throw uno::RuntimeException();
    }

    SwPosition const aPos(rDoc.GetNodes(), m_nStartNode);
    const SwTable* pTable = rDoc.InsertTable( m_aInsTableOptions, aPos, m_nRows, m_nColumns,
                                            m_nAdjust,
                                            m_pAutoFormat.get(),
                                            m_oColumnWidth ? &*m_oColumnWidth : nullptr );
    pEditShell->MoveTable( GotoPrevTable, fnTableStart );
    static_cast<SwFrameFormat*>(pTable->GetFrameFormat())->SetFormatName( m_sTableName );
    SwTableNode* pTableNode = rDoc.GetNodes()[m_nStartNode]->GetTableNode();

    if( m_pDDEFieldType )
    {
        SwDDEFieldType* pNewType = static_cast<SwDDEFieldType*>(rDoc.getIDocumentFieldsAccess().InsertFieldType(
                                                            *m_pDDEFieldType));
        std::unique_ptr<SwDDETable> pDDETable(new SwDDETable( pTableNode->GetTable(), pNewType ));
        pTableNode->SetNewTable( std::move(pDDETable) );
        m_pDDEFieldType.reset();
    }

    if( !((m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) ||
        ( !( RedlineFlags::Ignore & GetRedlineFlags() ) &&
            !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )))
        return;

    SwPaM aPam( *pTableNode->EndOfSectionNode(), *pTableNode, SwNodeOffset(1) );

    if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) )
    {
        RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
        rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore);

        rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, aPam ), true);
        rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
    }
    else
        rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
}

void SwUndoInsTable::RepeatImpl(::sw::RepeatContext & rContext)
{
    rContext.GetDoc().InsertTable(
            m_aInsTableOptions, *rContext.GetRepeatPaM().GetPoint(),
            m_nRows, m_nColumns, m_nAdjust, m_pAutoFormat.get(),
            m_oColumnWidth ? &*m_oColumnWidth : nullptr );
}

SwRewriter SwUndoInsTable::GetRewriter() const
{
    SwRewriter aRewriter;

    aRewriter.AddRule(UndoArg1, SwResId(STR_START_QUOTE));
    aRewriter.AddRule(UndoArg2, m_sTableName);
    aRewriter.AddRule(UndoArg3, SwResId(STR_END_QUOTE));

    return aRewriter;
}

SwTableToTextSave::SwTableToTextSave( SwDoc& rDoc, SwNodeOffset nNd, SwNodeOffset nEndIdx, sal_Int32 nCnt )
    : m_nSttNd( nNd ), m_nEndNd( nEndIdx), m_nContent( nCnt )
{
    // keep attributes of the joined node
    SwTextNode* pNd = rDoc.GetNodes()[ nNd ]->GetTextNode();
    if( pNd )
    {
        m_pHstry.reset( new SwHistory );

        m_pHstry->AddColl(pNd->GetTextColl(), nNd, SwNodeType::Text);
        if ( pNd->GetpSwpHints() )
        {
            m_pHstry->CopyAttr( pNd->GetpSwpHints(), nNd, 0,
                        pNd->GetText().getLength(), false );
        }
        if( pNd->HasSwAttrSet() )
            m_pHstry->CopyFormatAttr( *pNd->GetpSwAttrSet(), nNd );

        if( !m_pHstry->Count() )
        {
            m_pHstry.reset();
        }

        // METADATA: store
        m_pMetadataUndoStart = pNd->CreateUndo();
    }

    // we also need to store the metadata reference of the _last_ paragraph
    // we subtract 1 to account for the removed cell start/end node pair
    // (after SectionUp, the end of the range points to the node after the cell)
    if ( nEndIdx - 1 > nNd )
    {
        SwTextNode* pLastNode( rDoc.GetNodes()[ nEndIdx - 1 ]->GetTextNode() );
        if( pLastNode )
        {
            // METADATA: store
            m_pMetadataUndoEnd = pLastNode->CreateUndo();
        }
    }
}

SwUndoTableToText::SwUndoTableToText( const SwTable& rTable, sal_Unicode cCh )
    : SwUndo( SwUndoId::TABLETOTEXT, rTable.GetFrameFormat()->GetDoc() ),
    m_sTableName( rTable.GetFrameFormat()->GetName() ),
    m_nStartNode( 0 ), m_nEndNode( 0 ),
    m_cSeparator( cCh ), m_nHeadlineRepeat( rTable.GetRowsToRepeat() )
{
    m_pTableSave.reset( new SaveTable( rTable ) );
    m_vBoxSaves.reserve(rTable.GetTabSortBoxes().size());

    if( auto pDDETable = dynamic_cast<const SwDDETable *>(&rTable) )
        m_pDDEFieldType.reset(static_cast<SwDDEFieldType*>(pDDETable->GetDDEFieldType()->Copy().release()));

    m_bCheckNumFormat = rTable.GetFrameFormat()->GetDoc().IsInsTableFormatNum();

    m_pHistory.reset(new SwHistory);
    const SwTableNode* pTableNd = rTable.GetTableNode();
    SwNodeOffset nTableStt = pTableNd->GetIndex(), nTableEnd = pTableNd->EndOfSectionIndex();

    for(sw::SpzFrameFormat* pFormat: *pTableNd->GetDoc().GetSpzFrameFormats())
    {
        SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
        SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
        if (pAnchorNode &&
            ((RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) ||
             (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId())) &&
            nTableStt <= pAnchorNode->GetIndex() &&
            pAnchorNode->GetIndex() < nTableEnd )
        {
            m_pHistory->AddChangeFlyAnchor(*pFormat);
        }
    }

    if( !m_pHistory->Count() )
    {
        m_pHistory.reset();
    }
}

SwUndoTableToText::~SwUndoTableToText()
{
    m_pDDEFieldType.reset();
    m_pTableSave.reset();
    m_vBoxSaves.clear();
    m_pHistory.reset();
}

void SwUndoTableToText::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor());

    SwNodeIndex aFrameIdx( rDoc.GetNodes(), m_nStartNode );
    SwNodeIndex aEndIdx( rDoc.GetNodes(), m_nEndNode );

    pPam->GetPoint()->Assign( aFrameIdx );
    pPam->SetMark();
    pPam->GetPoint()->Assign( aEndIdx );
    rDoc.DelNumRules( *pPam );
    pPam->DeleteMark();

    // now collect all Uppers
    SwNode2LayoutSaveUpperFrames aNode2Layout(aFrameIdx.GetNode());

    // create TableNode structure
    SwTableNode* pTableNd = rDoc.GetNodes().UndoTableToText( m_nStartNode, m_nEndNode, m_vBoxSaves );
    pTableNd->GetTable().SetTableModel( m_pTableSave->IsNewModel() );
    SwTableFormat* pTableFormat = rDoc.MakeTableFrameFormat( m_sTableName, rDoc.GetDfltFrameFormat() );
    pTableNd->GetTable().RegisterToFormat( *pTableFormat );
    pTableNd->GetTable().SetRowsToRepeat( m_nHeadlineRepeat );

    // create old table structure
    m_pTableSave->CreateNew( pTableNd->GetTable() );

    if( m_pDDEFieldType )
    {
        SwDDEFieldType* pNewType = static_cast<SwDDEFieldType*>(rDoc.getIDocumentFieldsAccess().InsertFieldType(
                                                            *m_pDDEFieldType));
        std::unique_ptr<SwDDETable> pDDETable( new SwDDETable( pTableNd->GetTable(), pNewType ) );
        pTableNd->SetNewTable( std::move(pDDETable), false );
        m_pDDEFieldType.reset();
    }

    if( m_bCheckNumFormat )
    {
        SwTableSortBoxes& rBxs = pTableNd->GetTable().GetTabSortBoxes();
        for (size_t nBoxes = rBxs.size(); nBoxes; )
        {
            rDoc.ChkBoxNumFormat( *rBxs[ --nBoxes ], false );
        }
    }

    if( m_pHistory )
    {
        sal_uInt16 nTmpEnd = m_pHistory->GetTmpEnd();
        m_pHistory->TmpRollback( rDoc, 0 );
        m_pHistory->SetTmpEnd( nTmpEnd );
    }

    aNode2Layout.RestoreUpperFrames( rDoc.GetNodes(),
                                   pTableNd->GetIndex(), pTableNd->GetIndex()+1 );

    // Is a table selection requested?
    pPam->DeleteMark();
    pPam->GetPoint()->Assign( *pTableNd->EndOfSectionNode() );
    pPam->SetMark();
    pPam->GetPoint()->Assign( *pPam->GetPointNode().StartOfSectionNode() );
    pPam->Move( fnMoveForward, GoInContent );
    pPam->Exchange();
    pPam->Move( fnMoveBackward, GoInContent );

    ClearFEShellTabCols(rDoc, nullptr);
}

// located in untbl.cxx and only an Undo object is allowed to call it
SwTableNode* SwNodes::UndoTableToText( SwNodeOffset nSttNd, SwNodeOffset nEndNd,
                                const SwTableToTextSaves& rSavedData )
{
    SwNodeIndex aSttIdx( *this, nSttNd );
    SwNodeIndex aEndIdx( *this, nEndNd+1 );

    SwTableNode * pTableNd = new SwTableNode( aSttIdx.GetNode() );
    SwEndNode* pEndNd = new SwEndNode( aEndIdx.GetNode(), *pTableNd  );

    aEndIdx = *pEndNd;

    /* Set pTableNd as start of section for all nodes in [nSttNd, nEndNd].
       Delete all Frames attached to the nodes in that range. */
    SwNode* pNd;
    {
        SwNodeOffset n, nTmpEnd = aEndIdx.GetIndex();
        for( n = pTableNd->GetIndex() + 1; n < nTmpEnd; ++n )
        {
            pNd = (*this)[n];
            if (pNd->IsContentNode())
            {
                static_cast<SwContentNode*>(pNd)->DelFrames(nullptr);
            }
            // tdf#147938 reset merge flag in nodes
            pNd->SetRedlineMergeFlag(SwNode::Merge::None);
            pNd->m_pStartOfSection = pTableNd;
        }
    }

    // than create table structure partially. First a single line that contains
    // all boxes. The correct structure is then taken from SaveStruct.
    SwTableBoxFormat* pBoxFormat = GetDoc().MakeTableBoxFormat();
    SwTableLineFormat* pLineFormat = GetDoc().MakeTableLineFormat();
    SwTableLine* pLine = new SwTableLine( pLineFormat, rSavedData.size(), nullptr );
    pTableNd->GetTable().GetTabLines().insert( pTableNd->GetTable().GetTabLines().begin(), pLine );

    for( size_t n = rSavedData.size(); n; )
    {
        const SwTableToTextSave *const pSave = rSavedData[ --n ].get();
        // if the start node was merged with last from prev. cell,
        // subtract 1 from index to get the merged paragraph, and split that
        aSttIdx = pSave->m_nSttNd - ( ( SAL_MAX_INT32 != pSave->m_nContent ) ? 1 : 0);
        SwTextNode* pTextNd = aSttIdx.GetNode().GetTextNode();

        if( SAL_MAX_INT32 != pSave->m_nContent )
        {
            // split at ContentPosition, delete previous char (= separator)
            OSL_ENSURE( pTextNd, "Where is my TextNode?" );
            SwContentIndex aCntPos( pTextNd, pSave->m_nContent - 1 );

            const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
            pContentStore->Save(GetDoc(), aSttIdx.GetIndex(), aCntPos.GetIndex());

            pTextNd->EraseText( aCntPos, 1 );

            std::function<void (SwTextNode *, sw::mark::RestoreMode, bool)> restoreFunc(
                [&](SwTextNode *const pNewNode, sw::mark::RestoreMode const eMode, bool)
                {
                    if (!pContentStore->Empty())
                    {
                        pContentStore->Restore(*pNewNode, pSave->m_nContent, pSave->m_nContent + 1, eMode);
                    }
                });
            pTextNd->SplitContentNode(
                        SwPosition(aSttIdx, aCntPos), &restoreFunc);
        }

        if( pTextNd )
        {
            // METADATA: restore
            pTextNd->GetTextNode()->RestoreMetadata(pSave->m_pMetadataUndoStart);
            if( pTextNd->HasSwAttrSet() )
                pTextNd->ResetAllAttr();

            if( pTextNd->GetpSwpHints() )
                pTextNd->ClearSwpHintsArr( false );
        }

        if( pSave->m_pHstry )
        {
            sal_uInt16 nTmpEnd = pSave->m_pHstry->GetTmpEnd();
            pSave->m_pHstry->TmpRollback( GetDoc(), 0 );
            pSave->m_pHstry->SetTmpEnd( nTmpEnd );
        }

        // METADATA: restore
        // end points to node after cell
        if ( pSave->m_nEndNd - 1 > pSave->m_nSttNd )
        {
            SwTextNode* pLastNode = (*this)[ pSave->m_nEndNd - 1 ]->GetTextNode();
            if (pLastNode)
            {
                pLastNode->RestoreMetadata(pSave->m_pMetadataUndoEnd);
            }
        }

        aEndIdx = pSave->m_nEndNd;
        SwStartNode* pSttNd = new SwStartNode( aSttIdx.GetNode(), SwNodeType::Start,
                                                SwTableBoxStartNode );
        pSttNd->m_pStartOfSection = pTableNd;
        new SwEndNode( aEndIdx.GetNode(), *pSttNd );

        for( SwNodeOffset i = aSttIdx.GetIndex(); i < aEndIdx.GetIndex()-1; ++i )
        {
            pNd = (*this)[ i ];
            pNd->m_pStartOfSection = pSttNd;
            if( pNd->IsStartNode() )
                i = pNd->EndOfSectionIndex();
        }

        SwTableBox* pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine );
        pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin(), pBox );
    }
    return pTableNd;
}

void SwUndoTableToText::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor());

    pPam->GetPoint()->Assign( m_nStartNode );
    SwNodeIndex aSaveIdx( pPam->GetPoint()->GetNode(), -1 );

    pPam->SetMark();            // log off all indices
    pPam->DeleteMark();

    SwTableNode* pTableNd = pPam->GetPointNode().GetTableNode();
    OSL_ENSURE( pTableNd, "Could not find any TableNode" );

    if( auto pDDETable = dynamic_cast<const SwDDETable *>(&pTableNd->GetTable()) )
        m_pDDEFieldType.reset(static_cast<SwDDEFieldType*>(pDDETable->GetDDEFieldType()->Copy().release()));

    rDoc.TableToText( pTableNd, m_cSeparator );

    ++aSaveIdx;
    SwContentNode* pCNd = aSaveIdx.GetNode().GetContentNode();
    if( !pCNd && nullptr == ( pCNd = SwNodes::GoNext( &aSaveIdx ) ) &&
        nullptr == ( pCNd = SwNodes::GoPrevious( &aSaveIdx )) )
    {
        OSL_FAIL( "Where is the TextNode now?" );
    }

    pPam->GetPoint()->Assign( aSaveIdx );

    pPam->SetMark();            // log off all indices
    pPam->DeleteMark();
}

void SwUndoTableToText::RepeatImpl(::sw::RepeatContext & rContext)
{
    SwPaM *const pPam = & rContext.GetRepeatPaM();
    SwTableNode *const pTableNd = pPam->GetPointNode().FindTableNode();
    if( pTableNd )
    {
        // move cursor out of table
        pPam->GetPoint()->Assign( *pTableNd->EndOfSectionNode() );
        pPam->Move( fnMoveForward, GoInContent );
        pPam->SetMark();
        pPam->DeleteMark();

        rContext.GetDoc().TableToText( pTableNd, m_cSeparator );
    }
}

void SwUndoTableToText::SetRange( const SwNodeRange& rRg )
{
    m_nStartNode = rRg.aStart.GetIndex();
    m_nEndNode = rRg.aEnd.GetIndex();
}

void SwUndoTableToText::AddBoxPos( SwDoc& rDoc, SwNodeOffset nNdIdx, SwNodeOffset nEndIdx, sal_Int32 nContentIdx )
{
    m_vBoxSaves.push_back(std::make_unique<SwTableToTextSave>(rDoc, nNdIdx, nEndIdx, nContentIdx));
}

SwUndoTextToTable::SwUndoTextToTable( const SwPaM& rRg,
                                const SwInsertTableOptions& rInsTableOpts,
                                sal_Unicode cCh, sal_uInt16 nAdj,
                                const SwTableAutoFormat* pAFormat )
    : SwUndo( SwUndoId::TEXTTOTABLE, rRg.GetDoc() ), SwUndRng( rRg ), m_aInsertTableOpts( rInsTableOpts ),
      m_pHistory( nullptr ), m_cSeparator( cCh ), m_nAdjust( nAdj )
{
    if( pAFormat )
        m_pAutoFormat.reset( new SwTableAutoFormat( *pAFormat ) );

    const SwPosition* pEnd = rRg.End();
    SwNodes& rNds = rRg.GetDoc().GetNodes();
    m_bSplitEnd = pEnd->GetContentIndex() && ( pEnd->GetContentIndex()
                        != pEnd->GetNode().GetContentNode()->Len() ||
                pEnd->GetNodeIndex() >= rNds.GetEndOfContent().GetIndex()-1 );
}

SwUndoTextToTable::~SwUndoTextToTable()
{
    m_pAutoFormat.reset();
}

void SwUndoTextToTable::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();

    SwNodeOffset nTableNd = m_nSttNode;
    if( m_nSttContent )
        ++nTableNd;       // Node was split previously
    SwNodeIndex aIdx( rDoc.GetNodes(), nTableNd );
    SwTableNode *const pTNd = aIdx.GetNode().GetTableNode();
    OSL_ENSURE( pTNd, "Could not find a TableNode" );

    RemoveIdxFromSection( rDoc, nTableNd );

    m_sTableName = pTNd->GetTable().GetFrameFormat()->GetName();

    if( m_pHistory )
    {
        m_pHistory->TmpRollback( rDoc, 0 );
        m_pHistory->SetTmpEnd( m_pHistory->Count() );
    }

    if( !mvDelBoxes.empty() )
    {
        pTNd->DelFrames();
        SwTable& rTable = pTNd->GetTable();
        for( size_t n = mvDelBoxes.size(); n; )
        {
            SwTableBox* pBox = rTable.GetTableBox( mvDelBoxes[ --n ] );
            if( pBox )
                ::DeleteBox_( rTable, pBox, nullptr, false, false );
            else {
                OSL_ENSURE( false, "Where is my box?" );
            }
        }
    }

    rDoc.TableToText( pTNd, 0x0b == m_cSeparator ? 0x09 : m_cSeparator );

    // join again at start?
    if( m_nSttContent )
    {
        SwPaM aPam(rDoc.GetNodes(), nTableNd);
        if (aPam.Move(fnMoveBackward, GoInContent))
        {
            SwNode & rIdx = aPam.GetPoint()->GetNode();

            // than move, relatively, the Cursor/etc. again
            RemoveIdxRel( rIdx.GetIndex()+1, *aPam.GetPoint() );

            rIdx.GetContentNode()->JoinNext();
        }
    }

    // join again at end?
    if( m_bSplitEnd )
    {
        SwPosition aEndPos( rDoc.GetNodes(), m_nEndNode );
        SwTextNode* pTextNd = aEndPos.GetNode().GetTextNode();
        if( pTextNd && pTextNd->CanJoinNext() )
        {
            aEndPos.nContent.Assign( nullptr, 0 );

            // than move, relatively, the Cursor/etc. again
            aEndPos.SetContent(pTextNd->GetText().getLength());
            RemoveIdxRel( m_nEndNode + 1, aEndPos );

            pTextNd->JoinNext();
        }
    }

    AddUndoRedoPaM(rContext);
}

void SwUndoTextToTable::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwPaM & rPam( AddUndoRedoPaM(rContext) );
    RemoveIdxFromRange(rPam, false);
    SetPaM(rPam);

    SwTable const*const pTable = rContext.GetDoc().TextToTable(
                m_aInsertTableOpts, rPam, m_cSeparator, m_nAdjust, m_pAutoFormat.get() );
    static_cast<SwFrameFormat*>(pTable->GetFrameFormat())->SetFormatName( m_sTableName );
}

void SwUndoTextToTable::RepeatImpl(::sw::RepeatContext & rContext)
{
    // no Table In Table
    if (!rContext.GetRepeatPaM().GetPointNode().FindTableNode())
    {
        rContext.GetDoc().TextToTable( m_aInsertTableOpts, rContext.GetRepeatPaM(),
                                        m_cSeparator, m_nAdjust,
                                        m_pAutoFormat.get() );
    }
}

void SwUndoTextToTable::AddFillBox( const SwTableBox& rBox )
{
    mvDelBoxes.push_back( rBox.GetSttIdx() );
}

SwHistory& SwUndoTextToTable::GetHistory()
{
    if( !m_pHistory )
        m_pHistory = new SwHistory;
    return *m_pHistory;
}

SwUndoTableHeadline::SwUndoTableHeadline( const SwTable& rTable, sal_uInt16 nOldHdl,
                                      sal_uInt16 nNewHdl )
    : SwUndo( SwUndoId::TABLEHEADLINE, rTable.GetFrameFormat()->GetDoc() ),
    m_nOldHeadline( nOldHdl ),
    m_nNewHeadline( nNewHdl )
{
    OSL_ENSURE( !rTable.GetTabSortBoxes().empty(), "Table without content" );
    const SwStartNode *pSttNd = rTable.GetTabSortBoxes()[ 0 ]->GetSttNd();
    assert(pSttNd && "Box without content");

    m_nTableNode = pSttNd->StartOfSectionIndex();
}

void SwUndoTableHeadline::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwTableNode* pTNd = rDoc.GetNodes()[ m_nTableNode ]->GetTableNode();
    OSL_ENSURE( pTNd, "could not find any TableNode" );

    rDoc.SetRowsToRepeat( pTNd->GetTable(), m_nOldHeadline );
}

void SwUndoTableHeadline::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();

    SwTableNode* pTNd = rDoc.GetNodes()[ m_nTableNode ]->GetTableNode();
    OSL_ENSURE( pTNd, "could not find any TableNode" );

    rDoc.SetRowsToRepeat( pTNd->GetTable(), m_nNewHeadline );
}

void SwUndoTableHeadline::RepeatImpl(::sw::RepeatContext & rContext)
{
    SwTableNode *const pTableNd =
        rContext.GetRepeatPaM().GetPointNode().FindTableNode();
    if( pTableNd )
    {
        rContext.GetDoc().SetRowsToRepeat( pTableNd->GetTable(), m_nNewHeadline );
    }
}

SaveTable::SaveTable( const SwTable& rTable, sal_uInt16 nLnCnt, bool bSaveFormula )
    : m_aTableSet(*rTable.GetFrameFormat()->GetAttrSet().GetPool(), aTableSetRange),
      m_pSwTable(&rTable), m_nLineCount(nLnCnt), m_bSaveFormula(bSaveFormula)
{
    m_bModifyBox = false;
    m_bNewModel = rTable.IsNewModel();
    m_aTableSet.Put(rTable.GetFrameFormat()->GetAttrSet());
    m_pLine.reset( new SaveLine( nullptr, *rTable.GetTabLines()[ 0 ], *this ) );

    SaveLine* pLn = m_pLine.get();
    if( USHRT_MAX == nLnCnt )
        nLnCnt = rTable.GetTabLines().size();
    for( sal_uInt16 n = 1; n < nLnCnt; ++n )
        pLn = new SaveLine( pLn, *rTable.GetTabLines()[ n ], *this );

    m_aFrameFormats.clear();
    m_pSwTable = nullptr;
}

sal_uInt16 SaveTable::AddFormat( SwFrameFormat* pFormat, bool bIsLine )
{
    size_t nRet = m_aFrameFormats.GetPos(pFormat);
    if( SIZE_MAX == nRet )
    {
        // Create copy of ItemSet
        auto pSet = std::make_shared<SfxItemSet>( *pFormat->GetAttrSet().GetPool(),
            bIsLine ? aTableLineSetRange : aTableBoxSetRange );
        pSet->Put( pFormat->GetAttrSet() );
        // When a formula is set, never save the value. It possibly must be
        // recalculated.
        // Save formulas always in plain text.
        if( const SwTableBoxFormula* pItem = pSet->GetItemIfSet( RES_BOXATR_FORMULA ))
        {
            pSet->ClearItem( RES_BOXATR_VALUE );
            if (m_pSwTable && m_bSaveFormula)
            {
                const_cast<SwTable*>(m_pSwTable)->SwitchFormulasToExternalRepresentation();
                SwTableBoxFormula* pFormulaItem = const_cast<SwTableBoxFormula*>(pItem);
                pFormulaItem->ChgDefinedIn(pFormat);
                pFormulaItem->ToRelBoxNm(m_pSwTable);
                pFormulaItem->ChgDefinedIn(nullptr);
            }
        }
        nRet = m_aSets.size();
        m_aSets.push_back(std::move(pSet));
        m_aFrameFormats.insert(m_aFrameFormats.begin() + nRet, pFormat);
    }
    return o3tl::narrowing<sal_uInt16>(nRet);
}

void SaveTable::RestoreAttr( SwTable& rTable, bool bMdfyBox )
{
    m_bModifyBox = bMdfyBox;

    FndBox_ aTmpBox( nullptr, nullptr );
    bool bHideChanges = rTable.GetFrameFormat()->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout()->IsHideRedlines();
    // TODO delete/make frames only at changing line attribute TextChangesOnly (RES_PRINT) to true again
    if ( bHideChanges )
        aTmpBox.DelFrames( rTable );

    // first, get back attributes of TableFrameFormat
    SwFrameFormat* pFormat = rTable.GetFrameFormat();
    SfxItemSet& rFormatSet  = const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pFormat->GetAttrSet()));
    rFormatSet.ClearItem();
    rFormatSet.Put(m_aTableSet);

    pFormat->InvalidateInSwCache();

    // table without table frame
    bool bHiddenTable = true;

    // for safety, invalidate all TableFrames
    SwIterator<SwTabFrame,SwFormat> aIter( *pFormat );
    for( SwTabFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() )
    {
        if( pLast->GetTable() == &rTable )
        {
            pLast->InvalidateAll();
            pLast->SetCompletePaint();
            bHiddenTable = false;
        }
    }

    // fill FrameFormats with defaults (0)
    pFormat = nullptr;
    for (size_t n = m_aSets.size(); n; --n)
        m_aFrameFormats.push_back(pFormat);

    const size_t nLnCnt = (USHRT_MAX == m_nLineCount)
        ? rTable.GetTabLines().size()
        : m_nLineCount;

    SaveLine* pLn = m_pLine.get();
    for (size_t n = 0; n < nLnCnt; ++n, pLn = pLn->m_pNext)
    {
        if( !pLn )
        {
            OSL_ENSURE( false, "Number of lines changed" );
            break;
        }

        pLn->RestoreAttr( *rTable.GetTabLines()[ n ], *this );
    }

    m_aFrameFormats.clear();
    m_bModifyBox = false;

    if ( bHideChanges )
    {
        if ( bHiddenTable )
        {
            SwTableNode* pTableNode = rTable.GetTableNode();
            pTableNode->DelFrames();
            pTableNode->MakeOwnFrames();
        }
        else
        {
            aTmpBox.MakeFrames( rTable );
        }
    }
}

void SaveTable::SaveContentAttrs( SwDoc& rDoc )
{
    m_pLine->SaveContentAttrs(rDoc);
}

void SaveTable::CreateNew( SwTable& rTable, bool bCreateFrames,
                            bool bRestoreChart )
{
    FndBox_ aTmpBox( nullptr, nullptr );
    aTmpBox.DelFrames( rTable );

    // first, get back attributes of TableFrameFormat
    SwFrameFormat* pFormat = rTable.GetFrameFormat();
    SfxItemSet& rFormatSet  = const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pFormat->GetAttrSet()));
    rFormatSet.ClearItem();
    rFormatSet.Put(m_aTableSet);

    pFormat->InvalidateInSwCache();

    // SwTableBox must have a format - the SwTableBox takes ownership of it
    SwTableBoxFormat *const pNewFormat(pFormat->GetDoc().MakeTableBoxFormat());
    SwTableBox aParent(pNewFormat, rTable.GetTabLines().size(), nullptr);

    // fill FrameFormats with defaults (0)
    pFormat = nullptr;
    for( size_t n = m_aSets.size(); n; --n )
        m_aFrameFormats.push_back(pFormat);

    m_pLine->CreateNew(rTable, aParent, *this);
    m_aFrameFormats.clear();

    // add new lines, delete old ones
    const size_t nOldLines = (USHRT_MAX == m_nLineCount)
        ? rTable.GetTabLines().size()
        : m_nLineCount;

    SwDoc& rDoc = rTable.GetFrameFormat()->GetDoc();
    SwChartDataProvider *pPCD = rDoc.getIDocumentChartDataProviderAccess().GetChartDataProvider();
    size_t n = 0;
    for( ; n < aParent.GetTabLines().size(); ++n )
    {
        SwTableLine* pLn = aParent.GetTabLines()[ n ];
        pLn->SetUpper( nullptr );
        if( n < nOldLines )
        {
            SwTableLine* pOld = rTable.GetTabLines()[ n ];

            // TL_CHART2: notify chart about boxes to be removed
            const SwTableBoxes &rBoxes = pOld->GetTabBoxes();
            const size_t nBoxes = rBoxes.size();
            for (size_t k = 0; k < nBoxes;  ++k)
            {
                SwTableBox *pBox = rBoxes[k];
                if (pPCD)
                    pPCD->DeleteBox( &rTable, *pBox );
            }

            rTable.GetTabLines()[n] = pLn;
            delete pOld;
        }
        else
            rTable.GetTabLines().insert( rTable.GetTabLines().begin() + n, pLn );
    }

    if( n < nOldLines )
    {
        // remove remaining lines...
        for (size_t k1 = 0; k1 < nOldLines - n; ++k1)
        {
            const SwTableBoxes &rBoxes = rTable.GetTabLines()[n + k1]->GetTabBoxes();
            const size_t nBoxes = rBoxes.size();
            for (size_t k2 = 0; k2 < nBoxes; ++k2)
            {
                SwTableBox *pBox = rBoxes[k2];
                // TL_CHART2: notify chart about boxes to be removed
                if (pPCD)
                    pPCD->DeleteBox( &rTable, *pBox );
            }
        }

        for( SwTableLines::const_iterator it = rTable.GetTabLines().begin() + n;
             it != rTable.GetTabLines().begin() + nOldLines; ++it )
            delete *it;
        rTable.GetTabLines().erase( rTable.GetTabLines().begin() + n, rTable.GetTabLines().begin() + nOldLines );
    }

    aParent.GetTabLines().erase( aParent.GetTabLines().begin(), aParent.GetTabLines().begin() + n );
    assert(aParent.GetTabLines().empty());

    if( bCreateFrames )
        aTmpBox.MakeFrames( rTable );
    if( bRestoreChart )
    {
        // TL_CHART2: need to inform chart of probably changed cell names
        rDoc.UpdateCharts( rTable.GetFrameFormat()->GetName() );
    }
}

SwFrameFormat& SaveTable::CreateNewFormat(SwFrameFormat& rFormat, sal_uInt16 nFormatPos)
{
    rFormat.SetFormatAttr(*m_aSets[nFormatPos]);
    m_aFrameFormats[nFormatPos] = &rFormat;
    return rFormat;
}

void SaveTable::NewFrameFormatForLine(const SwTableLine& rTableLn, sal_uInt16 nFormatPos, SwFrameFormat* pOldFormat)
{
    SwFrameFormat* pFormat = m_aFrameFormats[nFormatPos];
    if(!pFormat)
        pFormat = &CreateNewFormat(*pOldFormat->GetDoc().MakeTableLineFormat(), nFormatPos);
    pOldFormat->CallSwClientNotify(sw::MoveTableLineHint(*pFormat, rTableLn));
    pFormat->Add(const_cast<SwTableLine&>(rTableLn));
    KillEmptyFrameFormat(*pOldFormat);
}

void SaveTable::NewFrameFormatForBox(const SwTableBox& rTableBx, sal_uInt16 nFormatPos, SwFrameFormat* pOldFormat)
{
    SwFrameFormat* pFormat = m_aFrameFormats[nFormatPos];
    if(!pFormat)
        pFormat = &CreateNewFormat(*pOldFormat->GetDoc().MakeTableBoxFormat(), nFormatPos);
    pOldFormat->CallSwClientNotify(sw::MoveTableBoxHint(*pFormat, rTableBx));
    pFormat->MoveTableBox(*const_cast<SwTableBox*>(&rTableBx), m_bModifyBox ? pOldFormat : nullptr);
    KillEmptyFrameFormat(*pOldFormat);
}

SaveLine::SaveLine(SaveLine* pPrev, const SwTableLine& rLine, SaveTable& rSTable)
    : m_pNext(nullptr)
{
    if( pPrev )
        pPrev->m_pNext = this;

    m_nItemSet = rSTable.AddFormat(rLine.GetFrameFormat(), true);

    m_pBox = new SaveBox(nullptr, *rLine.GetTabBoxes()[0], rSTable);
    SaveBox* pBx = m_pBox;
    for( size_t n = 1; n < rLine.GetTabBoxes().size(); ++n )
        pBx = new SaveBox( pBx, *rLine.GetTabBoxes()[ n ], rSTable );
}

SaveLine::~SaveLine()
{
    delete m_pBox;
    delete m_pNext;
}

void SaveLine::RestoreAttr( SwTableLine& rLine, SaveTable& rSTable )
{
    rSTable.NewFrameFormatForLine(rLine, m_nItemSet, rLine.GetFrameFormat());

    SaveBox* pBx = m_pBox;
    for (size_t n = 0; n < rLine.GetTabBoxes().size(); ++n, pBx = pBx->m_pNext)
    {
        if( !pBx )
        {
            OSL_ENSURE( false, "Number of boxes changed" );
            break;
        }
        pBx->RestoreAttr( *rLine.GetTabBoxes()[ n ], rSTable );
    }
}

void SaveLine::SaveContentAttrs( SwDoc& rDoc )
{
    m_pBox->SaveContentAttrs(rDoc);
    if (m_pNext)
        m_pNext->SaveContentAttrs(rDoc);
}

void SaveLine::CreateNew( SwTable& rTable, SwTableBox& rParent, SaveTable& rSTable )
{
    SwTableLineFormat* pFormat
        = static_cast<SwTableLineFormat*>(rSTable.m_aFrameFormats[m_nItemSet]);
    if( !pFormat )
    {
        SwDoc& rDoc = rTable.GetFrameFormat()->GetDoc();
        pFormat = rDoc.MakeTableLineFormat();
        pFormat->SetFormatAttr(*rSTable.m_aSets[m_nItemSet]);
        rSTable.m_aFrameFormats[m_nItemSet] = pFormat;
    }
    SwTableLine* pNew = new SwTableLine( pFormat, 1, &rParent );

    rParent.GetTabLines().push_back( pNew );

    m_pBox->CreateNew(rTable, *pNew, rSTable);

    if (m_pNext)
        m_pNext->CreateNew(rTable, rParent, rSTable);
}

SaveBox::SaveBox(SaveBox* pPrev, const SwTableBox& rBox, SaveTable& rSTable)
    : m_pNext(nullptr)
    , m_nStartNode(NODE_OFFSET_MAX)
    , m_nRowSpan(0)
{
    m_Ptrs.pLine = nullptr;

    if( pPrev )
        pPrev->m_pNext = this;

    m_nItemSet = rSTable.AddFormat(rBox.GetFrameFormat(), false);

    if( rBox.GetSttNd() )
    {
        m_nStartNode = rBox.GetSttIdx();
        m_nRowSpan = rBox.getRowSpan();
    }
    else
    {
        m_Ptrs.pLine = new SaveLine(nullptr, *rBox.GetTabLines()[0], rSTable);

        SaveLine* pLn = m_Ptrs.pLine;
        for( size_t n = 1; n < rBox.GetTabLines().size(); ++n )
            pLn = new SaveLine( pLn, *rBox.GetTabLines()[ n ], rSTable );
    }
    m_bHasDirectFormatting = rBox.HasDirectFormatting();

}

SaveBox::~SaveBox()
{
    if (NODE_OFFSET_MAX == m_nStartNode) // no EndBox
        delete m_Ptrs.pLine;
    else
        delete m_Ptrs.pContentAttrs;
    delete m_pNext;
}

void SaveBox::RestoreAttr( SwTableBox& rBox, SaveTable& rSTable )
{
    rSTable.NewFrameFormatForBox(rBox, m_nItemSet, rBox.GetFrameFormat());

    if (NODE_OFFSET_MAX == m_nStartNode) // no EndBox
    {
        if( rBox.GetTabLines().empty() )
        {
            OSL_ENSURE( false, "Number of lines changed" );
        }
        else
        {
            SaveLine* pLn = m_Ptrs.pLine;
            for (size_t n = 0; n < rBox.GetTabLines().size(); ++n, pLn = pLn->m_pNext)
            {
                if( !pLn )
                {
                    OSL_ENSURE( false, "Number of lines changed" );
                    break;
                }

                pLn->RestoreAttr( *rBox.GetTabLines()[ n ], rSTable );
            }
        }
    }
    else if (rBox.GetSttNd() && rBox.GetSttIdx() == m_nStartNode)
    {
        if (m_Ptrs.pContentAttrs)
        {
            SwNodes& rNds = rBox.GetFrameFormat()->GetDoc().GetNodes();
            sal_uInt16 nSet = 0;
            SwNodeOffset nEnd = rBox.GetSttNd()->EndOfSectionIndex();
            for (SwNodeOffset n = m_nStartNode + 1; n < nEnd; ++n)
            {
                SwContentNode* pCNd = rNds[ n ]->GetContentNode();
                if( pCNd )
                {
                    std::shared_ptr<SfxItemSet> pSet((*m_Ptrs.pContentAttrs)[nSet++]);
                    if( pSet )
                    {
                        for( const WhichPair& rPair : aSave_BoxContentSet )
                            pCNd->ResetAttr( rPair.first, rPair.second );
                        pCNd->SetAttr( *pSet );
                    }
                    else
                        pCNd->ResetAllAttr();
                }
            }
        }
    }
    else
    {
        OSL_ENSURE( false, "Box not anymore at the same node" );
    }
    rBox.SetDirectFormatting(m_bHasDirectFormatting);
}

void SaveBox::SaveContentAttrs( SwDoc& rDoc )
{
    if (NODE_OFFSET_MAX == m_nStartNode) // no EndBox
    {
        // continue in current line
        m_Ptrs.pLine->SaveContentAttrs(rDoc);
    }
    else
    {
        SwNodeOffset nEnd = rDoc.GetNodes()[m_nStartNode]->EndOfSectionIndex();
        m_Ptrs.pContentAttrs = new SfxItemSets;
        for (SwNodeOffset n = m_nStartNode + 1; n < nEnd; ++n)
        {
            SwContentNode* pCNd = rDoc.GetNodes()[ n ]->GetContentNode();
            if( pCNd )
            {
                std::shared_ptr<SfxItemSet> pSet;
                if( pCNd->HasSwAttrSet() )
                {
                    pSet = std::make_shared<SfxItemSet>( rDoc.GetAttrPool(),
                                            aSave_BoxContentSet );
                    pSet->Put( *pCNd->GetpSwAttrSet() );
                }

                m_Ptrs.pContentAttrs->push_back(std::move(pSet));
            }
        }
    }
    if (m_pNext)
        m_pNext->SaveContentAttrs(rDoc);
}

void SaveBox::CreateNew( SwTable& rTable, SwTableLine& rParent, SaveTable& rSTable )
{
    SwTableBoxFormat* pFormat = static_cast<SwTableBoxFormat*>(rSTable.m_aFrameFormats[m_nItemSet]);
    if( !pFormat )
    {
        SwDoc& rDoc = rTable.GetFrameFormat()->GetDoc();
        pFormat = rDoc.MakeTableBoxFormat();
        pFormat->SetFormatAttr(*rSTable.m_aSets[m_nItemSet]);
        rSTable.m_aFrameFormats[m_nItemSet] = pFormat;
    }

    if (NODE_OFFSET_MAX == m_nStartNode) // no EndBox
    {
        SwTableBox* pNew = new SwTableBox( pFormat, 1, &rParent );
        rParent.GetTabBoxes().push_back( pNew );

        m_Ptrs.pLine->CreateNew(rTable, *pNew, rSTable);
    }
    else
    {
        // search box for StartNode in old table
        SwTableBox* pBox = rTable.GetTableBox(m_nStartNode);
        if (pBox)
        {
            SwFrameFormat* pOld = pBox->GetFrameFormat();
            pBox->RegisterToFormat( *pFormat );
            if( !pOld->HasWriterListeners() )
                delete pOld;

            pBox->setRowSpan(m_nRowSpan);

            SwTableBoxes* pTBoxes = &pBox->GetUpper()->GetTabBoxes();
            pTBoxes->erase( std::find( pTBoxes->begin(), pTBoxes->end(), pBox ) );

            pBox->SetUpper( &rParent );
            pTBoxes = &rParent.GetTabBoxes();
            pTBoxes->push_back( pBox );
        }
    }

    if (m_pNext)
        m_pNext->CreateNew(rTable, rParent, rSTable);
}

// UndoObject for attribute changes on table
SwUndoAttrTable::SwUndoAttrTable( const SwTableNode& rTableNd, bool bClearTabCols )
    : SwUndo( SwUndoId::TABLE_ATTR, rTableNd.GetDoc() ),
    m_nStartNode( rTableNd.GetIndex() )
{
    m_bClearTableCol = bClearTabCols;
    m_pSaveTable.reset( new SaveTable( rTableNd.GetTable() ) );
}

SwUndoAttrTable::~SwUndoAttrTable()
{
}

void SwUndoAttrTable::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwTableNode* pTableNd = rDoc.GetNodes()[ m_nStartNode ]->GetTableNode();
    OSL_ENSURE( pTableNd, "no TableNode" );

    if (pTableNd)
    {
        SaveTable* pOrig = new SaveTable( pTableNd->GetTable() );
        m_pSaveTable->RestoreAttr( pTableNd->GetTable() );
        m_pSaveTable.reset( pOrig );
    }

    if( m_bClearTableCol )
    {
        ClearFEShellTabCols(rDoc, nullptr);
    }
}

void SwUndoAttrTable::RedoImpl(::sw::UndoRedoContext & rContext)
{
    UndoImpl(rContext);
}

// UndoObject for AutoFormat on Table
SwUndoTableAutoFormat::SwUndoTableAutoFormat( const SwTableNode& rTableNd,
                                    const SwTableAutoFormat& rAFormat )
    : SwUndo( SwUndoId::TABLE_AUTOFMT, rTableNd.GetDoc() )
    , m_TableStyleName(rTableNd.GetTable().GetTableStyleName())
    , m_nStartNode( rTableNd.GetIndex() )
    , m_bSaveContentAttr( false )
    , m_nRepeatHeading(rTableNd.GetTable().GetRowsToRepeat())
{
    m_pSaveTable.reset( new SaveTable( rTableNd.GetTable() ) );

    if( rAFormat.IsFont() || rAFormat.IsJustify() )
    {
        // then also go over the ContentNodes of the EndBoxes and collect
        // all paragraph attributes
        m_pSaveTable->SaveContentAttrs( const_cast<SwDoc&>(rTableNd.GetDoc()) );
        m_bSaveContentAttr = true;
    }
}

SwUndoTableAutoFormat::~SwUndoTableAutoFormat()
{
}

void SwUndoTableAutoFormat::SaveBoxContent( const SwTableBox& rBox )
{
    m_Undos.push_back(std::make_shared<SwUndoTableNumFormat>(rBox));
}

void
SwUndoTableAutoFormat::UndoRedo(bool const bUndo, ::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwTableNode* pTableNd = rDoc.GetNodes()[ m_nStartNode ]->GetTableNode();
    OSL_ENSURE( pTableNd, "no TableNode" );

    SwTable& table = pTableNd->GetTable();
    if (table.GetTableStyleName() != m_TableStyleName)
    {
        TableStyleName const temp(table.GetTableStyleName());
        table.SetTableStyleName(m_TableStyleName);
        m_TableStyleName = temp;
    }
    SaveTable* pOrig = new SaveTable( table );
    // then go also over the ContentNodes of the EndBoxes and collect
    // all paragraph attributes
    if( m_bSaveContentAttr )
        pOrig->SaveContentAttrs( rDoc );

    if (bUndo)
    {
        for (size_t n = m_Undos.size(); 0 < n; --n)
        {
            m_Undos.at(n-1)->UndoImpl(rContext);
        }

        table.SetRowsToRepeat(m_nRepeatHeading);
    }

    m_pSaveTable->RestoreAttr( pTableNd->GetTable(), !bUndo );
    m_pSaveTable.reset( pOrig );
}

void SwUndoTableAutoFormat::UndoImpl(::sw::UndoRedoContext & rContext)
{
    UndoRedo(true, rContext);
}

void SwUndoTableAutoFormat::RedoImpl(::sw::UndoRedoContext & rContext)
{
    UndoRedo(false, rContext);
}

SwUndoTableNdsChg::SwUndoTableNdsChg( SwUndoId nAction,
                                    const SwSelBoxes& rBoxes,
                                    const SwTableNode& rTableNd,
                                    tools::Long nMn, tools::Long nMx,
                                    sal_uInt16 nCnt, bool bFlg, bool bSmHght )
    : SwUndo( nAction, rTableNd.GetDoc() ),
    m_nMin( nMn ), m_nMax( nMx ),
    m_nSttNode( rTableNd.GetIndex() ),
    m_nCount( nCnt ),
    m_bFlag( bFlg ),
    m_bSameHeight( bSmHght )
{
    const SwTable& rTable = rTableNd.GetTable();
    m_pSaveTable.reset( new SaveTable( rTable ) );

    // and remember selection
    ReNewBoxes( rBoxes );
}

void SwUndoTableNdsChg::ReNewBoxes( const SwSelBoxes& rBoxes )
{
    if (rBoxes.size() != m_Boxes.size())
    {
        m_Boxes.clear();
        for (size_t n = 0; n < rBoxes.size(); ++n)
        {
            m_Boxes.insert( rBoxes[n]->GetSttIdx() );
        }
    }
}

SwUndoTableNdsChg::~SwUndoTableNdsChg()
{
}

void SwUndoTableNdsChg::SaveNewBoxes( const SwTableNode& rTableNd,
                                    const SwTableSortBoxes& rOld )
{
    const SwTable& rTable = rTableNd.GetTable();
    const SwTableSortBoxes& rTableBoxes = rTable.GetTabSortBoxes();

    OSL_ENSURE( ! IsDelBox(), "wrong Action" );
    m_xNewSttNds.emplace();

    size_t i = 0;
    for (size_t  n = 0; n < rOld.size(); ++i)
    {
        if( rOld[ n ] == rTableBoxes[ i ] )
            ++n;
        else
            // new box: insert sorted
            m_xNewSttNds->insert( BoxMove(rTableBoxes[ i ]->GetSttIdx()) );
    }

    for( ; i < rTableBoxes.size(); ++i )
        // new box: insert sorted
        m_xNewSttNds->insert( BoxMove(rTableBoxes[ i ]->GetSttIdx()) );
}

static SwTableLine* lcl_FindTableLine( const SwTable& rTable,
                                const SwTableBox& rBox )
{
    SwTableLine* pRet = nullptr;
    // i63949: For nested cells we have to take nLineNo - 1, too, not 0!
    const SwTableLines &rTableLines = ( rBox.GetUpper()->GetUpper() != nullptr ) ?
                                  rBox.GetUpper()->GetUpper()->GetTabLines()
                                : rTable.GetTabLines();
    const SwTableLine* pLine = rBox.GetUpper();
    sal_uInt16 nLineNo = rTableLines.GetPos( pLine );
    pRet = rTableLines[nLineNo - 1];

    return pRet;
}

static const SwTableLines& lcl_FindParentLines( const SwTable& rTable,
                                       const SwTableBox& rBox )
{
    const SwTableLines& rRet =
        ( rBox.GetUpper()->GetUpper() != nullptr ) ?
            rBox.GetUpper()->GetUpper()->GetTabLines() :
            rTable.GetTabLines();

    return rRet;
}

void SwUndoTableNdsChg::SaveNewBoxes( const SwTableNode& rTableNd,
                                    const SwTableSortBoxes& rOld,
                                    const SwSelBoxes& rBoxes,
                                    const std::vector<SwNodeOffset> &rNodeCnts )
{
    const SwTable& rTable = rTableNd.GetTable();
    const SwTableSortBoxes& rTableBoxes = rTable.GetTabSortBoxes();

    OSL_ENSURE( ! IsDelBox(), "wrong Action" );
    m_xNewSttNds.emplace();

    OSL_ENSURE( rTable.IsNewModel() || rOld.size() + m_nCount * rBoxes.size() == rTableBoxes.size(),
        "unexpected boxes" );
    OSL_ENSURE( rOld.size() <= rTableBoxes.size(), "more unexpected boxes" );
    for (size_t n = 0, i = 0; i < rTableBoxes.size(); ++i)
    {
        if( ( n < rOld.size() ) &&
            ( rOld[ n ] == rTableBoxes[ i ] ) )
        {
            // box already known? Then nothing to be done.
            ++n;
        }
        else
        {
            // new box found: insert (obey sort order)
            const SwTableBox* pBox = rTableBoxes[ i ];

            // find the source box. It must be one in rBoxes.
            // We found the right one if it's in the same column as pBox.
            // No, if more than one selected cell in the same column has been split,
            // we have to look for the nearest one (i65201)!
            const SwTableBox* pSourceBox = nullptr;
            const SwTableBox* pCheckBox = nullptr;
            const SwTableLine* pBoxLine = pBox->GetUpper();
            sal_uInt16 nLineDiff = lcl_FindParentLines(rTable,*pBox).GetPos(pBoxLine);
            sal_uInt16 nLineNo = 0;
            for (size_t j = 0; j < rBoxes.size(); ++j)
            {
                pCheckBox = rBoxes[j];
                if( pCheckBox->GetUpper()->GetUpper() == pBox->GetUpper()->GetUpper() )
                {
                    const SwTableLine* pCheckLine = pCheckBox->GetUpper();
                    sal_uInt16 nCheckLine = lcl_FindParentLines( rTable, *pCheckBox ).
                    GetPos( pCheckLine );
                    if( ( !pSourceBox || nCheckLine > nLineNo ) && nCheckLine < nLineDiff )
                    {
                        nLineNo = nCheckLine;
                        pSourceBox = pCheckBox;
                    }
                }
            }

            // find the line number difference
            // (to help determine bNodesMoved flag below)
            nLineDiff = nLineDiff - nLineNo;
            OSL_ENSURE( pSourceBox, "Split source box not found!" );
            // find out how many nodes the source box used to have
            // (to help determine bNodesMoved flag below)
            size_t nNdsPos = 0;
            while( rBoxes[ nNdsPos ] != pSourceBox )
                ++nNdsPos;
            SwNodeOffset nNodes = rNodeCnts[ nNdsPos ];

            // When a new table cell is created, it either gets a new
            // node, or it gets node(s) from elsewhere. The undo must
            // know, of course, and thus we must determine here just
            // where pBox's nodes are from:
            // If 1) the source box has lost nodes, and
            //    2) we're in the node range that got nodes
            // then pBox received nodes from elsewhere.
            // If bNodesMoved is set for pBox the undo must move the
            // boxes back, otherwise it must delete them.
            bool bNodesMoved = pSourceBox &&
                ( nNodes != ( pSourceBox->GetSttNd()->EndOfSectionIndex() -
                              pSourceBox->GetSttIdx() ) )
                && ( nNodes - 1 > SwNodeOffset(nLineDiff) );
            m_xNewSttNds->insert( BoxMove(pBox->GetSttIdx(), bNodesMoved) );
        }
    }
}

void SwUndoTableNdsChg::SaveSection( SwStartNode* pSttNd )
{
    OSL_ENSURE( IsDelBox(), "wrong Action" );
    if (m_pDelSects == nullptr)
        m_pDelSects.reset(new SwUndoSaveSections);

    SwTableNode* pTableNd = pSttNd->FindTableNode();
    std::unique_ptr<SwUndoSaveSection, o3tl::default_delete<SwUndoSaveSection>> pSave(new SwUndoSaveSection);
    pSave->SaveSection( SwNodeIndex( *pSttNd ));

    m_pDelSects->push_back(std::move(pSave));
    m_nSttNode = pTableNd->GetIndex();
}

void SwUndoTableNdsChg::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwNodeIndex aIdx( rDoc.GetNodes(), m_nSttNode );

    SwTableNode *const pTableNd = aIdx.GetNode().GetTableNode();
    OSL_ENSURE( pTableNd, "no TableNode" );
    pTableNd->GetTable().SwitchFormulasToInternalRepresentation();

    CHECK_TABLE( pTableNd->GetTable() )

    FndBox_ aTmpBox( nullptr, nullptr );
    // ? TL_CHART2: notification or locking of controller required ?

    SwChartDataProvider *pPCD = rDoc.getIDocumentChartDataProviderAccess().GetChartDataProvider();
    SwSelBoxes aDelBoxes;
    std::vector< std::pair<SwTableBox *, SwNodeOffset> > aDelNodes;
    if( IsDelBox() )
    {
        // Trick: add missing boxes in any line, they will be connected
        // correctly when calling CreateNew
        SwTableBox* pCpyBox = pTableNd->GetTable().GetTabSortBoxes()[0];
        SwTableBoxes& rLnBoxes = pCpyBox->GetUpper()->GetTabBoxes();

        // restore sections
        for (size_t n = m_pDelSects->size(); n; )
        {
            SwUndoSaveSection *const pSave = (*m_pDelSects)[ --n ].get();
            pSave->RestoreSection( rDoc, &aIdx, SwTableBoxStartNode );
            if( pSave->GetHistory() )
                pSave->GetHistory()->Rollback( rDoc );
            SwTableBox* pBox = new SwTableBox( pCpyBox->GetFrameFormat(), aIdx,
                                                pCpyBox->GetUpper() );
            rLnBoxes.push_back( pBox );
        }
        m_pDelSects->clear();
    }
    else if( !m_xNewSttNds->empty() )
    {
        // Then the nodes have be moved and not deleted!
        // But for that we need a temp array.
        std::vector<BoxMove> aTmp( m_xNewSttNds->begin(), m_xNewSttNds->end() );

        // backwards
        for (size_t n = aTmp.size(); n > 0 ; )
        {
            --n;
            // delete box from table structure
            SwNodeOffset nIdx = aTmp[n].index;
            SwTableBox* pBox = pTableNd->GetTable().GetTableBox( nIdx );
            OSL_ENSURE( pBox, "Where is my TableBox?" );

            // TL_CHART2: notify chart about box to be removed
            if (pPCD)
                pPCD->DeleteBox( &pTableNd->GetTable(), *pBox );

            // insert _before_ deleting the section - otherwise the box
            // has no start node so all boxes sort equal in SwSelBoxes
            aDelBoxes.insert(pBox);

            if( aTmp[n].hasMoved )
            {
                SwNodeRange aRg( *pBox->GetSttNd(), SwNodeOffset(1),
                            *pBox->GetSttNd()->EndOfSectionNode() );

                SwTableLine* pLine = lcl_FindTableLine( pTableNd->GetTable(), *pBox );
                SwNodeIndex aInsPos( *(pLine->GetTabBoxes()[0]->GetSttNd()), 2 );

                // adjust all StartNode indices
                size_t i = n;
                SwNodeOffset nSttIdx = aInsPos.GetIndex() - 2,
                       nNdCnt = aRg.aEnd.GetIndex() - aRg.aStart.GetIndex();
                while( i && aTmp[ --i ].index > nSttIdx )
                    aTmp[ i ].index += nNdCnt;

                // first delete box
                delete pBox;
                // than move nodes
                rDoc.GetNodes().MoveNodes( aRg, rDoc.GetNodes(), aInsPos.GetNode(), false );
            }
            else
            {
                aDelNodes.emplace_back(pBox, nIdx);
            }
        }
    }
    else
    {
        // Remove nodes from nodes array (backwards!)
        std::set<BoxMove>::reverse_iterator it;
        for( it = m_xNewSttNds->rbegin(); it != m_xNewSttNds->rend(); ++it )
        {
            SwNodeOffset nIdx = (*it).index;
            SwTableBox* pBox = pTableNd->GetTable().GetTableBox( nIdx );
            OSL_ENSURE( pBox, "Where's my table box?" );
            // TL_CHART2: notify chart about box to be removed
            if (pPCD)
                pPCD->DeleteBox( &pTableNd->GetTable(), *pBox );
            aDelBoxes.insert(pBox);
            aDelNodes.emplace_back(pBox, nIdx);
        }
    }

    // fdo#57197: before deleting the SwTableBoxes, delete the SwTabFrames
    aTmpBox.SetTableLines(aDelBoxes, pTableNd->GetTable());
    aTmpBox.DelFrames(pTableNd->GetTable());

    // do this _after_ deleting Frames because disposing SwAccessible requires
    // connection to the nodes, see SwAccessibleChild::IsAccessible()
    for (const std::pair<SwTableBox *, SwNodeOffset> & rDelNode : aDelNodes)
    {
        // first disconnect box from node, otherwise ~SwTableBox would
        // access pBox->pSttNd, deleted by DeleteSection
        rDelNode.first->RemoveFromTable();
        rDoc.getIDocumentContentOperations().DeleteSection(rDoc.GetNodes()[ rDelNode.second ]);
    }

    // Remove boxes from table structure
    for( size_t n = 0; n < aDelBoxes.size(); ++n )
    {
        SwTableBox* pCurrBox = aDelBoxes[n];
        SwTableBoxes* pTBoxes = &pCurrBox->GetUpper()->GetTabBoxes();
        pTBoxes->erase( std::find( pTBoxes->begin(), pTBoxes->end(), pCurrBox ) );
        delete pCurrBox;
    }

    m_pSaveTable->CreateNew( pTableNd->GetTable(), true, false );

    // TL_CHART2: need to inform chart of probably changed cell names
    rDoc.UpdateCharts( pTableNd->GetTable().GetFrameFormat()->GetName() );
    if (SwFEShell* pFEShell = rDoc.GetDocShell()->GetFEShell())
    {
        if (officecfg::Office::Writer::Table::Change::ApplyTableAutoFormat::get())
        {
            pFEShell->UpdateTableStyleFormatting(pTableNd);
        }
    }
    if( IsDelBox() )
        m_nSttNode = pTableNd->GetIndex();
    ClearFEShellTabCols(rDoc, nullptr);
    CHECK_TABLE( pTableNd->GetTable() )
}

void SwUndoTableNdsChg::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();

    SwTableNode* pTableNd = rDoc.GetNodes()[ m_nSttNode ]->GetTableNode();
    assert(pTableNd && "no TableNode");
    CHECK_TABLE( pTableNd->GetTable() )

    SwSelBoxes aSelBoxes;
    for (const auto& rBox : m_Boxes)
    {
        SwTableBox* pBox = pTableNd->GetTable().GetTableBox( rBox );
        aSelBoxes.insert( pBox );
    }

    // create SelBoxes and call InsertCell/-Row/SplitTable
    switch( GetId() )
    {
    case SwUndoId::TABLE_INSCOL:
        rDoc.InsertCol( aSelBoxes, m_nCount, m_bFlag );
        break;

    case SwUndoId::TABLE_INSROW:
        rDoc.InsertRow( aSelBoxes, m_nCount, m_bFlag );
        break;

    case SwUndoId::TABLE_SPLIT:
        rDoc.SplitTable( aSelBoxes, m_bFlag, m_nCount, m_bSameHeight );
        break;
    case SwUndoId::TABLE_DELBOX:
    case SwUndoId::ROW_DELETE:
    case SwUndoId::COL_DELETE:
        {
            SwTable &rTable = pTableNd->GetTable();
            rTable.SwitchFormulasToInternalRepresentation();
            if( m_nMax > m_nMin && rTable.IsNewModel() )
                rTable.PrepareDeleteCol( m_nMin, m_nMax );
            rTable.DeleteSel( rDoc, aSelBoxes, nullptr, this, true, true );
            m_nSttNode = pTableNd->GetIndex();
        }
        break;
    default:
        ;
    }
    ClearFEShellTabCols(rDoc, nullptr);
    CHECK_TABLE( pTableNd->GetTable() )
}

SwUndoTableMerge::SwUndoTableMerge( const SwPaM& rTableSel )
    : SwUndo( SwUndoId::TABLE_MERGE, rTableSel.GetDoc() ), SwUndRng( rTableSel )
{
    const SwTableNode* pTableNd = rTableSel.GetPointNode().FindTableNode();
    assert(pTableNd && "Where is the TableNode?");
    m_pSaveTable.reset( new SaveTable( pTableNd->GetTable() ) );
    m_nTableNode = pTableNd->GetIndex();
}

SwUndoTableMerge::~SwUndoTableMerge()
{
    m_pSaveTable.reset();
    m_vMoves.clear();
    m_pHistory.reset();
}

void SwUndoTableMerge::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwNodeIndex aIdx( rDoc.GetNodes(), m_nTableNode );

    SwTableNode *const pTableNd = aIdx.GetNode().GetTableNode();
    OSL_ENSURE( pTableNd, "no TableNode" );

    pTableNd->GetTable().SwitchFormulasToInternalRepresentation();

    // ? TL_CHART2: notification or locking of controller required ?

    // 1. restore deleted boxes:
    // Trick: add missing boxes in any line, they will be connected
    // correctly when calling CreateNew
    SwTableBox *pBox, *pCpyBox = pTableNd->GetTable().GetTabSortBoxes()[0];
    SwTableBoxes& rLnBoxes = pCpyBox->GetUpper()->GetTabBoxes();

    CHECKTABLE(pTableNd->GetTable())

    SwSelBoxes aSelBoxes;
    SwTextFormatColl* pColl = rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD );

    for (const auto& rBox : m_Boxes)
    {
        aIdx = rBox;
        SwStartNode* pSttNd = rDoc.GetNodes().MakeTextSection( aIdx.GetNode(),
                                            SwTableBoxStartNode, pColl );
        pBox = new SwTableBox( pCpyBox->GetFrameFormat(), *pSttNd,
                                pCpyBox->GetUpper() );
        rLnBoxes.push_back( pBox );

        aSelBoxes.insert( pBox );
    }

    CHECKTABLE(pTableNd->GetTable())

    SwChartDataProvider *pPCD = rDoc.getIDocumentChartDataProviderAccess().GetChartDataProvider();
    // 2. deleted the inserted boxes
    // delete nodes (from last to first)
    for( size_t n = m_aNewStartNodes.size(); n; )
    {
        // remove box from table structure
        SwNodeOffset nIdx = m_aNewStartNodes[ --n ];

        if( !nIdx && n )
        {
            nIdx = m_aNewStartNodes[ --n ];
            pBox = pTableNd->GetTable().GetTableBox( nIdx );
            OSL_ENSURE( pBox, "Where is my TableBox?" );

            if( !m_pSaveTable->IsNewModel() )
                rDoc.GetNodes().MakeTextNode(
                    *pBox->GetSttNd()->EndOfSectionNode(), pColl );

            // this was the separator -> restore moved ones
            for (size_t i = m_vMoves.size(); i; )
            {
                SwTextNode* pTextNd = nullptr;
                sal_Int32 nDelPos = 0;
                SwUndoMove *const pUndo = m_vMoves[ --i ].get();
                if( !pUndo->IsMoveRange() )
                {
                    pTextNd = rDoc.GetNodes()[ pUndo->GetDestSttNode() ]->GetTextNode();
                    nDelPos = pUndo->GetDestSttContent() - 1;
                }
                pUndo->UndoImpl(rContext);
                if( pUndo->IsMoveRange() )
                {
                    // delete the unnecessary node
                    aIdx = pUndo->GetEndNode();
                    SwContentNode *pCNd = aIdx.GetNode().GetContentNode();
                    if( pCNd )
                    {
                        SwNodeIndex aTmp( aIdx, -1 );
                        SwContentNode *pMove = aTmp.GetNode().GetContentNode();
                        if( pMove )
                            pCNd->MoveTo( *pMove );
                    }
                    rDoc.GetNodes().Delete( aIdx );
                }
                else if( pTextNd )
                {
                    // also delete not needed attributes
                    SwContentIndex aTmpIdx( pTextNd, nDelPos );
                    if( pTextNd->GetpSwpHints() && pTextNd->GetpSwpHints()->Count() )
                        pTextNd->RstTextAttr( nDelPos, pTextNd->GetText().getLength() - nDelPos + 1 );
                    // delete separator
                    pTextNd->EraseText( aTmpIdx, 1 );
                }
            }
            nIdx = pBox->GetSttIdx();
        }
        else
            pBox = pTableNd->GetTable().GetTableBox( nIdx );

        if( !m_pSaveTable->IsNewModel() )
        {
            // TL_CHART2: notify chart about box to be removed
            if (pPCD)
                pPCD->DeleteBox( &pTableNd->GetTable(), *pBox );

            SwTableBoxes* pTBoxes = &pBox->GetUpper()->GetTabBoxes();
            pTBoxes->erase( std::find(pTBoxes->begin(), pTBoxes->end(), pBox ) );

            // delete indices from section
            {
                SwNodeIndex aTmpIdx( *pBox->GetSttNd() );
                SwDoc::CorrAbs( SwNodeIndex( aTmpIdx, 1 ),
                            SwNodeIndex( *aTmpIdx.GetNode().EndOfSectionNode() ),
                            SwPosition( aTmpIdx, nullptr, 0 ), true );
            }

            delete pBox;
            rDoc.getIDocumentContentOperations().DeleteSection( rDoc.GetNodes()[ nIdx ] );
        }
    }
    CHECKTABLE(pTableNd->GetTable())

    m_pSaveTable->CreateNew( pTableNd->GetTable(), true, false );

    // TL_CHART2: need to inform chart of probably changed cell names
    rDoc.UpdateCharts( pTableNd->GetTable().GetFrameFormat()->GetName() );

    if( m_pHistory )
    {
        m_pHistory->TmpRollback( rDoc, 0 );
        m_pHistory->SetTmpEnd( m_pHistory->Count() );
    }
    SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor());
    pPam->DeleteMark();
    pPam->GetPoint()->Assign(m_nSttNode, m_nSttContent );
    pPam->SetMark();
    pPam->DeleteMark();

    CHECKTABLE(pTableNd->GetTable())
    ClearFEShellTabCols(rDoc, nullptr);
}

void SwUndoTableMerge::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwPaM & rPam( AddUndoRedoPaM(rContext) );
    rDoc.MergeTable(rPam);
}

void SwUndoTableMerge::MoveBoxContent( SwDoc& rDoc, SwNodeRange& rRg, SwNode& rPos )
{
    SwNodeIndex aTmp( rRg.aStart, -1 ), aTmp2( rPos, -1 );
    std::unique_ptr<SwUndoMove> pUndo(new SwUndoMove( rDoc, rRg, rPos ));
    ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
    rDoc.getIDocumentContentOperations().MoveNodeRange( rRg, rPos, m_pSaveTable->IsNewModel() ?
        SwMoveFlags::NO_DELFRMS :
        SwMoveFlags::DEFAULT );
    ++aTmp;
    ++aTmp2;
    pUndo->SetDestRange( aTmp2.GetNode(), rPos, aTmp );

    m_vMoves.push_back(std::move(pUndo));
}

void SwUndoTableMerge::SetSelBoxes( const SwSelBoxes& rBoxes )
{
    // memorize selection
    for (size_t n = 0; n < rBoxes.size(); ++n)
    {
        m_Boxes.insert(rBoxes[n]->GetSttIdx());
    }

    // as separator for inserts of new boxes after shifting
    m_aNewStartNodes.push_back( SwNodeOffset(0) );

    // The new table model does not delete overlapped cells (by row span),
    // so the rBoxes array might be empty even some cells have been merged.
    if( !rBoxes.empty() )
        m_nTableNode = rBoxes[ 0 ]->GetSttNd()->FindTableNode()->GetIndex();
}

void SwUndoTableMerge::SaveCollection( const SwTableBox& rBox )
{
    if( !m_pHistory )
        m_pHistory.reset(new SwHistory);

    SwNodeIndex aIdx( *rBox.GetSttNd(), 1 );
    SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
    if( !pCNd )
        pCNd = SwNodes::GoNext(&aIdx);

    m_pHistory->AddColl(pCNd->GetFormatColl(), aIdx.GetIndex(), pCNd->GetNodeType());
    if( pCNd->HasSwAttrSet() )
        m_pHistory->CopyFormatAttr( *pCNd->GetpSwAttrSet(), aIdx.GetIndex() );
}

SwUndoTableNumFormat::SwUndoTableNumFormat( const SwTableBox& rBox,
                                    const SfxItemSet* pNewSet )
    : SwUndo(SwUndoId::TBLNUMFMT, rBox.GetFrameFormat()->GetDoc())
    , m_nFormatIdx(getSwDefaultTextFormat())
    , m_nNewFormatIdx(0)
    , m_fNum(0.0)
    , m_fNewNum(0.0)
    , m_bNewFormat(false)
    , m_bNewFormula(false)
    , m_bNewValue(false)
{
    m_nNode = rBox.GetSttIdx();

    m_nNodePos = rBox.IsValidNumTextNd( nullptr == pNewSet );
    SwDoc& rDoc = rBox.GetFrameFormat()->GetDoc();

    if( NODE_OFFSET_MAX != m_nNodePos )
    {
        SwTextNode* pTNd = rDoc.GetNodes()[ m_nNodePos ]->GetTextNode();

        m_pHistory.reset(new SwHistory);
        SwRegHistory aRHst( *rBox.GetSttNd(), m_pHistory.get() );
        // always save all text attributes because of possibly overlapping
        // areas of on/off
        m_pHistory->CopyAttr( pTNd->GetpSwpHints(), m_nNodePos, 0,
                            pTNd->GetText().getLength(), true );

        if( pTNd->HasSwAttrSet() )
            m_pHistory->CopyFormatAttr( *pTNd->GetpSwAttrSet(), m_nNodePos );

        m_aStr = pTNd->GetText();
        if( pTNd->GetpSwpHints() )
            pTNd->GetpSwpHints()->DeRegister();
    }

    m_pBoxSet.reset( new SfxItemSet( rDoc.GetAttrPool(), aTableBoxSetRange ) );
    m_pBoxSet->Put( rBox.GetFrameFormat()->GetAttrSet() );

    if( pNewSet )
    {
        if( const SwTableBoxNumFormat* pItem = pNewSet->GetItemIfSet( RES_BOXATR_FORMAT,
                false ))
        {
            m_bNewFormat = true;
            m_nNewFormatIdx = pItem->GetValue();
        }
        if( const SwTableBoxFormula* pItem = pNewSet->GetItemIfSet( RES_BOXATR_FORMULA,
                false ))
        {
            m_bNewFormula = true;
            m_aNewFormula = pItem->GetFormula();
        }
        if( const SwTableBoxValue* pItem = pNewSet->GetItemIfSet( RES_BOXATR_VALUE,
                false ))
        {
            m_bNewValue = true;
            m_fNewNum = pItem->GetValue();
        }
    }

    // is a history needed at all?
    if (m_pHistory && !m_pHistory->Count())
    {
        m_pHistory.reset();
    }
}

SwUndoTableNumFormat::~SwUndoTableNumFormat()
{
    m_pHistory.reset();
    m_pBoxSet.reset();
}

void SwUndoTableNumFormat::UndoImpl(::sw::UndoRedoContext & rContext)
{
    OSL_ENSURE( m_pBoxSet, "Where's the stored item set?" );

    SwDoc & rDoc = rContext.GetDoc();
    SwStartNode* pSttNd = rDoc.GetNodes()[ m_nNode ]->
                            FindStartNodeByType( SwTableBoxStartNode );
    assert(pSttNd && "without StartNode no TableBox");
    SwTableBox* pBox = pSttNd->FindTableNode()->GetTable().GetTableBox(
                                    pSttNd->GetIndex() );
    assert(pBox && "found no TableBox");

    SwTableBoxFormat* pFormat = rDoc.MakeTableBoxFormat();
    pFormat->SetFormatAttr( *m_pBoxSet );
    pBox->ChgFrameFormat( pFormat );

    if( NODE_OFFSET_MAX == m_nNodePos )
        return;

    SwTextNode* pTextNd = rDoc.GetNodes()[ m_nNodePos ]->GetTextNode();
    // If more than one node was deleted then all "node" attributes were also
    // saved
    if( pTextNd->HasSwAttrSet() )
        pTextNd->ResetAllAttr();

    if( pTextNd->GetpSwpHints() && !m_aStr.isEmpty() )
        pTextNd->ClearSwpHintsArr( true );

    // ChgTextToNum(..) only acts when the strings are different. We need to do
    // the same here.
    if( pTextNd->GetText() != m_aStr )
    {
        rDoc.getIDocumentRedlineAccess().DeleteRedline( *( pBox->GetSttNd() ), false, RedlineType::Any );

        SwContentIndex aIdx( pTextNd, 0 );
        if( !m_aStr.isEmpty() )
        {
            pTextNd->EraseText( aIdx );
            pTextNd->InsertText( m_aStr, aIdx,
                SwInsertFlags::NOHINTEXPAND );
        }
    }

    if( m_pHistory )
    {
        sal_uInt16 nTmpEnd = m_pHistory->GetTmpEnd();
        m_pHistory->TmpRollback( rDoc, 0 );
        m_pHistory->SetTmpEnd( nTmpEnd );
    }

    SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor());
    pPam->DeleteMark();
    pPam->GetPoint()->Assign( m_nNode + 1 );
}

namespace {

/** switch the RedlineFlags on the given document, using
 * SetRedlineFlags_intern. This class set the mode in the constructor,
 * and changes it back in the destructor, i.e. it uses the
 * initialization-is-resource-acquisition idiom.
 */
class RedlineFlagsInternGuard
{
    SwDoc& mrDoc;
    RedlineFlags meOldRedlineFlags;

public:
    RedlineFlagsInternGuard(
        SwDoc& rDoc,                      // change mode of this document
        RedlineFlags eNewRedlineFlags,    // new redline mode
        RedlineFlags eRedlineFlagsMask   /*change only bits set in this mask*/);

    ~RedlineFlagsInternGuard();
};

}

RedlineFlagsInternGuard::RedlineFlagsInternGuard(
    SwDoc& rDoc,
    RedlineFlags eNewRedlineFlags,
    RedlineFlags eRedlineFlagsMask )
    : mrDoc( rDoc ),
      meOldRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() )
{
    mrDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( meOldRedlineFlags & ~eRedlineFlagsMask ) |
                                     ( eNewRedlineFlags & eRedlineFlagsMask ) );
}

RedlineFlagsInternGuard::~RedlineFlagsInternGuard()
{
    mrDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( meOldRedlineFlags );
}

void SwUndoTableNumFormat::RedoImpl(::sw::UndoRedoContext & rContext)
{
    // Could the box be changed?
    if( !m_pBoxSet )
        return ;

    SwDoc & rDoc = rContext.GetDoc();
    SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor());

    pPam->DeleteMark();
    pPam->GetPoint()->Assign( m_nNode );

    SwNode * pNd = & pPam->GetPoint()->GetNode();
    SwStartNode* pSttNd = pNd->FindStartNodeByType( SwTableBoxStartNode );
    assert(pSttNd && "without StartNode no TableBox");
    SwTableBox* pBox = pSttNd->FindTableNode()->GetTable().GetTableBox(
                                    pSttNd->GetIndex() );
    OSL_ENSURE( pBox, "found no TableBox" );

    SwFrameFormat* pBoxFormat = pBox->ClaimFrameFormat();
    if( m_bNewFormat || m_bNewFormula || m_bNewValue )
    {
        SfxItemSet aBoxSet(SfxItemSet::makeFixedSfxItemSet<RES_BOXATR_FORMAT, RES_BOXATR_VALUE>(rDoc.GetAttrPool()));

        // Resetting attributes is not enough. In addition, take care that the
        // text will be also formatted correctly.
        pBoxFormat->LockModify();

        if( m_bNewFormula )
            aBoxSet.Put( SwTableBoxFormula( m_aNewFormula ));
        else
            pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMULA );
        if( m_bNewFormat )
            aBoxSet.Put( SwTableBoxNumFormat( m_nNewFormatIdx ));
        else
            pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT );
        if( m_bNewValue )
            aBoxSet.Put( SwTableBoxValue( m_fNewNum ));
        else
            pBoxFormat->ResetFormatAttr( RES_BOXATR_VALUE );
        pBoxFormat->UnlockModify();

        // dvo: When redlining is (was) enabled, setting the attribute
        // will also change the cell content. To allow this, the
        // RedlineFlags::Ignore flag must be removed during Redo. #108450#
        RedlineFlagsInternGuard aGuard( rDoc, RedlineFlags::NONE, RedlineFlags::Ignore );
        pBoxFormat->SetFormatAttr( aBoxSet );
    }
    else if( getSwDefaultTextFormat() != m_nFormatIdx )
    {
        SfxItemSet aBoxSet(SfxItemSet::makeFixedSfxItemSet<RES_BOXATR_FORMAT, RES_BOXATR_VALUE>(rDoc.GetAttrPool()));

        aBoxSet.Put( SwTableBoxNumFormat( m_nFormatIdx ));
        aBoxSet.Put( SwTableBoxValue( m_fNum ));

        // Resetting attributes is not enough. In addition, take care that the
        // text will be also formatted correctly.
        pBoxFormat->LockModify();
        pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMULA );
        pBoxFormat->UnlockModify();

        // dvo: When redlining is (was) enabled, setting the attribute
        // will also change the cell content. To allow this, the
        // RedlineFlags::Ignore flag must be removed during Redo. #108450#
        RedlineFlagsInternGuard aGuard( rDoc, RedlineFlags::NONE, RedlineFlags::Ignore );
        pBoxFormat->SetFormatAttr( aBoxSet );
    }
    else
    {
        // it's no number

        // Resetting attributes is not enough. In addition, take care that the
        // text will be also formatted correctly.
        pBoxFormat->SetFormatAttr( *GetDfltAttr( RES_BOXATR_FORMAT ));

        pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE );
    }

    if( m_bNewFormula )
    {
        // No matter what was set, an update of the table is always a good idea
        rDoc.getIDocumentFieldsAccess().UpdateTableFields(&pSttNd->FindTableNode()->GetTable());
    }

    if( !pNd->IsContentNode() )
        pNd = SwNodes::GoNext(pPam->GetPoint());
}

void SwUndoTableNumFormat::SetBox( const SwTableBox& rBox )
{
    m_nNode = rBox.GetSttIdx();
}

UndoTableCpyTable_Entry::UndoTableCpyTable_Entry( const SwTableBox& rBox )
    : nBoxIdx( rBox.GetSttIdx() ), nOffset( 0 ),
    bJoin( false )
{
}

void UndoTableCpyTable_Entry::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("UndoTableCpyTable_Entry"));

    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("nBoxIdx"));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
                                BAD_CAST(OString::number(sal_Int32(nBoxIdx)).getStr()));
    (void)xmlTextWriterEndElement(pWriter);

    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("nOffset"));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
                                BAD_CAST(OString::number(sal_Int32(nOffset)).getStr()));
    (void)xmlTextWriterEndElement(pWriter);

    if (pBoxNumAttr)
    {
        (void)xmlTextWriterStartElement(pWriter, BAD_CAST("pBoxNumAttr"));
        pBoxNumAttr->dumpAsXml(pWriter);
        (void)xmlTextWriterEndElement(pWriter);
    }

    if (pUndo)
    {
        (void)xmlTextWriterStartElement(pWriter, BAD_CAST("pUndo"));
        pUndo->dumpAsXml(pWriter);
        (void)xmlTextWriterEndElement(pWriter);
    }

    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("bJoin"));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
                                BAD_CAST(OString::boolean(bJoin).getStr()));
    (void)xmlTextWriterEndElement(pWriter);

    (void)xmlTextWriterEndElement(pWriter);
}

SwUndoTableCpyTable::SwUndoTableCpyTable(const SwDoc& rDoc)
    : SwUndo( SwUndoId::TBLCPYTBL, rDoc )
{
}

SwUndoTableCpyTable::~SwUndoTableCpyTable()
{
    m_vArr.clear();
    m_pInsRowUndo.reset();
}

void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    DEBUG_REDLINE( rDoc )

    SwTableNode* pTableNd = nullptr;
    for (size_t n = m_vArr.size(); n; )
    {
        UndoTableCpyTable_Entry *const pEntry = m_vArr[ --n ].get();
        SwNodeOffset nSttPos = pEntry->nBoxIdx + pEntry->nOffset;
        SwStartNode* pSNd = rDoc.GetNodes()[ nSttPos ]->StartOfSectionNode();
        if( !pTableNd )
            pTableNd = pSNd->FindTableNode();

        SwTableBox* pBox = pTableNd->GetTable().GetTableBox( nSttPos );
        if (!pBox)
        {
            SAL_WARN("sw.core",
                     "SwUndoTableCpyTable::UndoImpl: invalid start node index for table box");
            continue;
        }

        SwTableBox& rBox = *pBox;

        SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 );
        rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() );

        // b62341295: Redline for copying tables
        const SwNode *pEndNode = rBox.GetSttNd()->EndOfSectionNode();
        SwPaM aPam( aInsIdx.GetNode(), *pEndNode );
        std::unique_ptr<SwUndoDelete> pUndo;

        if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) )
        {
            bool bDeleteCompleteParagraph = false;
            bool bShiftPam = false;
            // There are a couple of different situations to consider during redlining
            if( pEntry->pUndo )
            {
                SwUndoDelete *const pUndoDelete =
                    dynamic_cast<SwUndoDelete*>(pEntry->pUndo.get());
                SwUndoRedlineDelete *const pUndoRedlineDelete =
                    dynamic_cast<SwUndoRedlineDelete*>(pEntry->pUndo.get());
                assert(pUndoDelete || pUndoRedlineDelete);
                if (pUndoRedlineDelete)
                {
                    // The old content was not empty or he has been merged with the new content
                    bDeleteCompleteParagraph = !pEntry->bJoin; // bJoin is set when merged
                    // Set aTmpIdx to the beginning of the old content
                    SwNodeIndex aTmpIdx( *pEndNode,
                            pUndoRedlineDelete->NodeDiff()-1 );
                    SwTextNode *pText = aTmpIdx.GetNode().GetTextNode();
                    if( pText )
                    {
                        aPam.GetPoint()->Assign(*pText,
                                pUndoRedlineDelete->ContentStart() );
                    }
                    else
                        aPam.GetPoint()->Assign( aTmpIdx );
                }
                else if (pUndoDelete && pUndoDelete->IsDelFullPara())
                {
                    // When the old content was an empty paragraph, but could not be joined
                    // with the new content (e.g. because of a section or table)
                    // We "save" the aPam.Point, we go one step backwards (because later on the
                    // empty paragraph will be inserted by the undo) and set the "ShiftPam-flag
                    // for step forward later on.
                    bDeleteCompleteParagraph = true;
                    bShiftPam = true;
                    aPam.GetPoint()->Assign(*pEndNode, SwNodeOffset(-1));
                }
            }
            rDoc.getIDocumentRedlineAccess().DeleteRedline( aPam, true, RedlineType::Any );

            if( pEntry->pUndo )
            {
                pEntry->pUndo->UndoImpl(rContext);
                pEntry->pUndo.reset();
            }
            if( bShiftPam )
            {
                // The aPam.Point is at the moment at the last position of the new content and has to be
                // moved to the first position of the old content for the SwUndoDelete operation
                aPam.GetPoint()->Assign(aPam.GetPoint()->GetNode(), SwNodeOffset(1));
            }
            pUndo = std::make_unique<SwUndoDelete>(aPam, SwDeleteFlags::Default, bDeleteCompleteParagraph, true);
        }
        else
        {
            pUndo = std::make_unique<SwUndoDelete>(aPam, SwDeleteFlags::Default, true);
            if( pEntry->pUndo )
            {
                pEntry->pUndo->UndoImpl(rContext);
                pEntry->pUndo.reset();
            }
        }
        pEntry->pUndo = std::move(pUndo);

        aInsIdx = rBox.GetSttIdx() + 1;
        rDoc.GetNodes().Delete( aInsIdx );

        SfxItemSet aTmpSet(SfxItemSet::makeFixedSfxItemSet<RES_VERT_ORIENT, RES_VERT_ORIENT,
                           RES_BOXATR_FORMAT, RES_BOXATR_VALUE>(rDoc.GetAttrPool()));
        aTmpSet.Put( rBox.GetFrameFormat()->GetAttrSet() );
        if( aTmpSet.Count() )
        {
            SwFrameFormat* pBoxFormat = rBox.ClaimFrameFormat();
            pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE );
            pBoxFormat->ResetFormatAttr( RES_VERT_ORIENT );
        }

        if( pEntry->pBoxNumAttr )
        {
            rBox.ClaimFrameFormat()->SetFormatAttr( *pEntry->pBoxNumAttr );
            pEntry->pBoxNumAttr.reset();
        }

        if( aTmpSet.Count() )
        {
            pEntry->pBoxNumAttr = std::make_unique<SfxItemSet>(
                SfxItemSet::makeFixedSfxItemSet<RES_VERT_ORIENT, RES_VERT_ORIENT,
                                                RES_BOXATR_FORMAT, RES_BOXATR_VALUE>(rDoc.GetAttrPool()));
            pEntry->pBoxNumAttr->Put( aTmpSet );
        }

        pEntry->nOffset = rBox.GetSttIdx() - pEntry->nBoxIdx;
    }

    if( m_pInsRowUndo )
    {
        m_pInsRowUndo->UndoImpl(rContext);
    }
    DEBUG_REDLINE( rDoc )
}

void SwUndoTableCpyTable::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    DEBUG_REDLINE( rDoc )

    if( m_pInsRowUndo )
    {
        m_pInsRowUndo->RedoImpl(rContext);
    }

    SwTableNode* pTableNd = nullptr;
    for (size_t n = 0; n < m_vArr.size(); ++n)
    {
        UndoTableCpyTable_Entry *const pEntry = m_vArr[ n ].get();
        SwNodeOffset nSttPos = pEntry->nBoxIdx + pEntry->nOffset;
        SwStartNode* pSNd = rDoc.GetNodes()[ nSttPos ]->StartOfSectionNode();
        if( !pTableNd )
            pTableNd = pSNd->FindTableNode();

        SwTableBox& rBox = *pTableNd->GetTable().GetTableBox( nSttPos );

        SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 );

        // b62341295: Redline for copying tables - Start.
        rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() );
        SwPaM aPam( aInsIdx.GetNode(), *rBox.GetSttNd()->EndOfSectionNode());
        std::unique_ptr<SwUndo> pUndo(IDocumentRedlineAccess::IsRedlineOn(GetRedlineFlags())
            ? nullptr
            : std::make_unique<SwUndoDelete>(aPam, SwDeleteFlags::Default, true));
        if( pEntry->pUndo )
        {
            pEntry->pUndo->UndoImpl(rContext);
            if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) )
            {
                // PrepareRedline has to be called with the beginning of the old content
                // When new and old content has been joined, the rIter.pAktPam has been set
                // by the Undo operation to this point.
                // Otherwise aInsIdx has been moved during the Undo operation
                if( pEntry->bJoin )
                {
                    SwPaM& rLastPam =
                        rContext.GetCursorSupplier().GetCurrentShellCursor();
                    pUndo = PrepareRedline( rDoc, rBox, *rLastPam.GetPoint(),
                                            pEntry->bJoin, true );
                }
                else
                {
                    SwPosition aTmpPos( aInsIdx );
                    pUndo = PrepareRedline( rDoc, rBox, aTmpPos, pEntry->bJoin, true );
                }
            }
            pEntry->pUndo.reset();
        }
        pEntry->pUndo = std::move(pUndo);
        // b62341295: Redline for copying tables - End.

        aInsIdx = rBox.GetSttIdx() + 1;
        rDoc.GetNodes().Delete( aInsIdx );

        SfxItemSet aTmpSet(SfxItemSet::makeFixedSfxItemSet<RES_VERT_ORIENT, RES_VERT_ORIENT,
                           RES_BOXATR_FORMAT, RES_BOXATR_VALUE>(rDoc.GetAttrPool()));
        aTmpSet.Put( rBox.GetFrameFormat()->GetAttrSet() );
        if( aTmpSet.Count() )
        {
            SwFrameFormat* pBoxFormat = rBox.ClaimFrameFormat();
            pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE );
            pBoxFormat->ResetFormatAttr( RES_VERT_ORIENT );
        }
        if( pEntry->pBoxNumAttr )
        {
            rBox.ClaimFrameFormat()->SetFormatAttr( *pEntry->pBoxNumAttr );
            pEntry->pBoxNumAttr.reset();
        }

        if( aTmpSet.Count() )
        {
            pEntry->pBoxNumAttr = std::make_unique<SfxItemSet>(
                SfxItemSet::makeFixedSfxItemSet<RES_VERT_ORIENT, RES_VERT_ORIENT,
                                                RES_BOXATR_FORMAT, RES_BOXATR_VALUE>(rDoc.GetAttrPool()));
            pEntry->pBoxNumAttr->Put( aTmpSet );
        }

        pEntry->nOffset = rBox.GetSttIdx() - pEntry->nBoxIdx;
    }
    DEBUG_REDLINE( rDoc )
}

void SwUndoTableCpyTable::AddBoxBefore( const SwTableBox& rBox, bool bDelContent )
{
    if (!m_vArr.empty() && !bDelContent)
        return;

    UndoTableCpyTable_Entry* pEntry = new UndoTableCpyTable_Entry( rBox );
    m_vArr.push_back(std::unique_ptr<UndoTableCpyTable_Entry>(pEntry));

    SwDoc& rDoc = rBox.GetFrameFormat()->GetDoc();
    DEBUG_REDLINE( rDoc )
    if( bDelContent )
    {
        SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 );
        SwTextNode *const pNewNode(rDoc.GetNodes().MakeTextNode(aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl()));
        SwPaM aPam( aInsIdx.GetNode(), *rBox.GetSttNd()->EndOfSectionNode() );

        if( !rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
        {
            {   // move cursors to new node which precedes aPam
                SwPosition const pos(*pNewNode, 0);
                ::PaMCorrAbs(aPam, pos);
            }
            pEntry->pUndo = std::make_unique<SwUndoDelete>(aPam, SwDeleteFlags::Default, true);
        }
    }

    pEntry->pBoxNumAttr = std::make_unique<SfxItemSet>(
        SfxItemSet::makeFixedSfxItemSet<RES_VERT_ORIENT, RES_VERT_ORIENT,
                                        RES_BOXATR_FORMAT, RES_BOXATR_VALUE>(rDoc.GetAttrPool()));
    pEntry->pBoxNumAttr->Put( rBox.GetFrameFormat()->GetAttrSet() );
    if( !pEntry->pBoxNumAttr->Count() )
    {
        pEntry->pBoxNumAttr.reset();
    }
    DEBUG_REDLINE( rDoc )
}

void SwUndoTableCpyTable::AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex& rIdx, bool bDelContent )
{
    UndoTableCpyTable_Entry *const pEntry = m_vArr.back().get();

    // If the content was deleted then remove also the temporarily created node
    if( bDelContent )
    {
        SwDoc& rDoc = rBox.GetFrameFormat()->GetDoc();
        DEBUG_REDLINE( rDoc )

        {   // move cursors to first node which was inserted
            SwPaM pam(SwNodeIndex(*rBox.GetSttNd(), 1));
            assert(pam.GetPoint()->GetNode().IsTextNode());
            pam.SetMark();
            pam.Move(fnMoveForward, GoInContent);
            ::PaMCorrAbs(pam, *pam.GetPoint());
        }
        if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
        {
            SwPosition aTmpPos( rIdx );
            pEntry->pUndo = PrepareRedline( rDoc, rBox, aTmpPos, pEntry->bJoin, false );
        }
        SwNodeIndex aDelIdx( *rBox.GetSttNd(), 1 );
        rBox.GetFrameFormat()->GetDoc().GetNodes().Delete( aDelIdx );
        DEBUG_REDLINE( rDoc )
    }

    pEntry->nOffset = rBox.GetSttIdx() - pEntry->nBoxIdx;
}

// PrepareRedline is called from AddBoxAfter() and from Redo() in slightly different situations.
// bRedo is set by calling from Redo()
// rJoin is false by calling from AddBoxAfter() and will be set if the old and new content has
// been merged.
// rJoin is true if Redo() is calling and the content has already been merged

std::unique_ptr<SwUndo> SwUndoTableCpyTable::PrepareRedline( SwDoc& rDoc, const SwTableBox& rBox,
    SwPosition& rPos, bool& rJoin, bool bRedo )
{
    std::unique_ptr<SwUndo> pUndo;
    // b62341295: Redline for copying tables
    // What's to do?
    // Mark the cell content before rIdx as insertion,
    // mark the cell content behind rIdx as deletion
    // merge text nodes at rIdx if possible
    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld | RedlineFlags::DontCombineRedlines ) & ~RedlineFlags::Ignore );
    SwPosition aInsertEnd( rPos );
    SwTextNode* pText;
    if( !rJoin )
    {
        // If the content is not merged, the end of the insertion is at the end of the node
        // _before_ the given position rPos
        aInsertEnd.Adjust(SwNodeOffset(-1));
        pText = aInsertEnd.GetNode().GetTextNode();
        if( pText )
        {
            aInsertEnd.SetContent(pText->GetText().getLength());
            if( !bRedo && rPos.GetNode().GetTextNode() )
            {   // Try to merge, if not called by Redo()
                rJoin = true;

                // Park this somewhere else so nothing points to the to-be-deleted node.
                rPos.nContent.Assign(pText, 0);

                pText->JoinNext();
            }
        }
        else
            aInsertEnd.nContent.Assign(nullptr, 0);
    }
    // For joined (merged) contents the start of deletion and end of insertion are identical
    // otherwise adjacent nodes.
    SwPosition aDeleteStart( rJoin ? aInsertEnd : rPos );
    if( !rJoin )
    {
        pText = aDeleteStart.GetNode().GetTextNode();
        if( pText )
            aDeleteStart.SetContent( 0 );
    }
    SwPosition aCellEnd( *rBox.GetSttNd()->EndOfSectionNode(), SwNodeOffset(-1) );
    pText = aCellEnd.GetNode().GetTextNode();
    if( pText )
        aCellEnd.SetContent(pText->GetText().getLength());
    if( aDeleteStart != aCellEnd )
    {   // If the old (deleted) part is not empty, here we are...
        SwPaM aDeletePam( aDeleteStart, aCellEnd );
        pUndo = std::make_unique<SwUndoRedlineDelete>( aDeletePam, SwUndoId::DELETE );
        rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDeletePam ), true );
    }
    else if( !rJoin ) // If the old part is empty and joined, we are finished
    {   // if it is not joined, we have to delete this empty paragraph
        aCellEnd.Assign(*rBox.GetSttNd()->EndOfSectionNode());
        SwPaM aTmpPam( aDeleteStart, aCellEnd );
        pUndo = std::make_unique<SwUndoDelete>(aTmpPam, SwDeleteFlags::Default, true);
    }
    SwPosition aCellStart( *rBox.GetSttNd(), SwNodeOffset(2) );
    pText = aCellStart.GetNode().GetTextNode();
    if( pText )
        aCellStart.SetContent( 0 );
    if( aCellStart != aInsertEnd ) // An empty insertion will not been marked
    {
        SwPaM aTmpPam( aCellStart, aInsertEnd );
        rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aTmpPam ), true );
    }

    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
    return pUndo;
}

bool SwUndoTableCpyTable::InsertRow( SwTable& rTable, const SwSelBoxes& rBoxes,
                                sal_uInt16 nCnt )
{
    SwTableNode* pTableNd = const_cast<SwTableNode*>(rTable.GetTabSortBoxes()[0]->
                                GetSttNd()->FindTableNode());

    m_pInsRowUndo.reset( new SwUndoTableNdsChg( SwUndoId::TABLE_INSROW, rBoxes, *pTableNd,
                                       0, 0, nCnt, true, false ) );
    SwTableSortBoxes aTmpLst( rTable.GetTabSortBoxes() );

    bool bRet = rTable.InsertRow( rTable.GetFrameFormat()->GetDoc(), rBoxes, nCnt, /*bBehind*/true );
    if( bRet )
        m_pInsRowUndo->SaveNewBoxes( *pTableNd, aTmpLst );
    else
    {
        m_pInsRowUndo.reset();
    }
    return bRet;
}

void SwUndoTableCpyTable::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoTableCpyTable"));

    for (const auto& pEntry : m_vArr)
    {
        pEntry->dumpAsXml(pWriter);
    }

    if (m_pInsRowUndo)
    {
        m_pInsRowUndo->dumpAsXml(pWriter);
    }

    (void)xmlTextWriterEndElement(pWriter);
}

bool SwUndoTableCpyTable::IsEmpty() const
{
    return !m_pInsRowUndo && m_vArr.empty();
}

SwUndoCpyTable::SwUndoCpyTable(const SwDoc& rDoc)
    : SwUndo( SwUndoId::CPYTBL, rDoc ), m_nTableNode( 0 )
{
}

SwUndoCpyTable::~SwUndoCpyTable()
{
}

void SwUndoCpyTable::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwTableNode* pTNd = rDoc.GetNodes()[ m_nTableNode ]->GetTableNode();

    // move hard page breaks into next node
    SwContentNode* pNextNd = rDoc.GetNodes()[ pTNd->EndOfSectionIndex()+1 ]->GetContentNode();
    if( pNextNd )
    {
        SwFrameFormat* pTableFormat = pTNd->GetTable().GetFrameFormat();

        if( const SwFormatPageDesc* pItem = pTableFormat->GetItemIfSet( RES_PAGEDESC,
            false ) )
            pNextNd->SetAttr( *pItem );

        if( const SvxFormatBreakItem* pItem = pTableFormat->GetItemIfSet( RES_BREAK,
            false ) )
            pNextNd->SetAttr( *pItem );
    }

    SwPaM aPam( *pTNd, *pTNd->EndOfSectionNode(), SwNodeOffset(0) , SwNodeOffset(1) );
    m_pDelete.reset(new SwUndoDelete(aPam, SwDeleteFlags::Default, true));
}

void SwUndoCpyTable::RedoImpl(::sw::UndoRedoContext & rContext)
{
    m_pDelete->UndoImpl(rContext);
    m_pDelete.reset();
}

SwUndoSplitTable::SwUndoSplitTable( const SwTableNode& rTableNd,
        std::unique_ptr<SwSaveRowSpan> pRowSp, SplitTable_HeadlineOption const eMode,
        bool const bNewSize, FloatingMode const eFloatingMode)
    : SwUndo( SwUndoId::SPLIT_TABLE, rTableNd.GetDoc() )
    , m_nTableNode( rTableNd.GetIndex() )
    , m_nOffset( 0 )
    , m_eFloatingMode(eFloatingMode)
    , mpSaveRowSpan( std::move(pRowSp) ),
    m_nMode( eMode ), m_nFormulaEnd( 0 ), m_bCalcNewSize( bNewSize )
{
    switch( m_nMode )
    {
    case SplitTable_HeadlineOption::BoxAttrAllCopy:
            m_pHistory.reset(new SwHistory);
            [[fallthrough]];
    case SplitTable_HeadlineOption::BorderCopy:
    case SplitTable_HeadlineOption::BoxAttrCopy:
        m_pSavedTable.reset(new SaveTable( rTableNd.GetTable(), 1, false ));
        break;
    default: break;
    }
}

SwUndoSplitTable::~SwUndoSplitTable()
{
    m_pSavedTable.reset();
    m_pHistory.reset();
    mpSaveRowSpan.reset();
}

void SwUndoSplitTable::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc& rDoc = rContext.GetDoc();
    SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor());

    SwPosition& rPtPos = *pPam->GetPoint();

    if (m_eFloatingMode != FloatingMode::Not)
    {
        // index members are *before* frame-split
        auto const nEndOfOldFlyIndex{m_nTableNode + m_nOffset
            + (m_eFloatingMode == FloatingMode::FloatingAnchoredBefore ? 1 : 0)};
        rDoc.GetNodes().MergeFloatingTableFrame(nEndOfOldFlyIndex);
    }
    else
    {
        rPtPos.Assign(m_nTableNode + m_nOffset);
        assert(rPtPos.GetNode().IsTextNode()); // inserted text node
        assert(rPtPos.GetNode().GetContentNode()->Len() == 0); // empty para inserted

        // avoid asserts from ~SwContentIndexReg
        SwNodeIndex const idx(rDoc.GetNodes(), m_nTableNode + m_nOffset);
        {
            SwPaM pam(idx);
            pam.Move(fnMoveBackward, GoInContent);
            ::PaMCorrAbs(*pPam, *pam.GetPoint());
        }

        // remove implicitly created paragraph again
        rDoc.GetNodes().Delete( idx );
    }

    rPtPos.Assign( m_nTableNode + m_nOffset );
    SwTableNode* pTableNd = rPtPos.GetNode().GetTableNode();
    assert(pTableNd);
    SwTable& rTable = pTableNd->GetTable();
    rTable.SwitchFormulasToInternalRepresentation();

    switch( m_nMode )
    {
    case SplitTable_HeadlineOption::BoxAttrAllCopy:
        if( m_pHistory )
            m_pHistory->TmpRollback( rDoc, m_nFormulaEnd );
        [[fallthrough]];
    case SplitTable_HeadlineOption::BoxAttrCopy:
    case SplitTable_HeadlineOption::BorderCopy:
        {
            m_pSavedTable->CreateNew( rTable, false );
            m_pSavedTable->RestoreAttr( rTable );
        }
        break;

    case SplitTable_HeadlineOption::ContentCopy:
        // the created first line has to be removed again
        {
            SwSelBoxes aSelBoxes;
            SwTableBox* pBox = rTable.GetTableBox( m_nTableNode + m_nOffset + 1 );
            SwTable::SelLineFromBox( pBox, aSelBoxes );
            FndBox_ aTmpBox( nullptr, nullptr );
            aTmpBox.SetTableLines( aSelBoxes, rTable );
            aTmpBox.DelFrames( rTable );
            rTable.DeleteSel( rDoc, aSelBoxes, nullptr, nullptr, false, false );
        }
        break;
    default: break;
    }

    rDoc.GetNodes().MergeTable( rPtPos.GetNode() );

    if( m_pHistory )
    {
        m_pHistory->TmpRollback( rDoc, 0 );
        m_pHistory->SetTmpEnd( m_pHistory->Count() );
    }
    if( mpSaveRowSpan )
    {
        pTableNd = rPtPos.GetNode().FindTableNode();
        if( pTableNd )
            pTableNd->GetTable().RestoreRowSpan( *mpSaveRowSpan );
    }
    ClearFEShellTabCols(rDoc, nullptr);
}

void SwUndoSplitTable::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc& rDoc = rContext.GetDoc();
    SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor());

    pPam->DeleteMark();
    pPam->GetPoint()->Assign( m_nTableNode );
    rDoc.SplitTable( *pPam->GetPoint(), m_nMode, m_bCalcNewSize );

    ClearFEShellTabCols(rDoc, nullptr);
}

void SwUndoSplitTable::RepeatImpl(::sw::RepeatContext & rContext)
{
    SwPaM *const pPam = & rContext.GetRepeatPaM();
    SwDoc& rDoc = rContext.GetDoc();

    rDoc.SplitTable( *pPam->GetPoint(), m_nMode, m_bCalcNewSize );
    ClearFEShellTabCols(rDoc, nullptr);
}

void SwUndoSplitTable::SaveFormula( SwHistory& rHistory )
{
    if( !m_pHistory )
        m_pHistory.reset(new SwHistory);

    m_nFormulaEnd = rHistory.Count();
    m_pHistory->Move( 0, &rHistory );
}

SwUndoMergeTable::SwUndoMergeTable( const SwTableNode& rTableNd,
                                const SwTableNode& rDelTableNd,
                                bool bWithPrv )
    : SwUndo( SwUndoId::MERGE_TABLE, rTableNd.GetDoc() ),
    m_bWithPrev( bWithPrv )
{
    // memorize end node of the last table cell that'll stay in position
    if( m_bWithPrev )
        m_nTableNode = rDelTableNd.EndOfSectionIndex() - 1;
    else
        m_nTableNode = rTableNd.EndOfSectionIndex() - 1;

    m_aName = rDelTableNd.GetTable().GetFrameFormat()->GetName();
    m_pSaveTable.reset(new SaveTable( rDelTableNd.GetTable() ));

    if (m_bWithPrev)
        m_pSaveHdl.reset( new SaveTable( rTableNd.GetTable(), 1 ) );
}

SwUndoMergeTable::~SwUndoMergeTable()
{
    m_pSaveTable.reset();
    m_pSaveHdl.reset();
    m_pHistory.reset();
}

void SwUndoMergeTable::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc& rDoc = rContext.GetDoc();
    SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor());

    pPam->DeleteMark();
    SwPosition& rPtPos = *pPam->GetPoint();
    rPtPos.Assign( m_nTableNode);

    SwTableNode* pTableNd = rPtPos.GetNode().FindTableNode();
    SwTable* pTable = &pTableNd->GetTable();
    pTable->SwitchFormulasToInternalRepresentation();

    // get lines for layout update
    FndBox_ aFndBox( nullptr, nullptr );
    aFndBox.SetTableLines( *pTable );
    aFndBox.DelFrames( *pTable );
    // ? TL_CHART2: notification or locking of controller required ?

    SwTableNode* pNew = rDoc.GetNodes().SplitTable( rPtPos.GetNode() );

    // update layout
    aFndBox.MakeFrames( *pTable );
    // ? TL_CHART2: notification or locking of controller required ?

    if( m_bWithPrev )
    {
        // move name
        pNew->GetTable().GetFrameFormat()->SetFormatName( pTable->GetFrameFormat()->GetName() );
        m_pSaveHdl->RestoreAttr( pNew->GetTable() );
    }
    else
        pTable = &pNew->GetTable();

    pTable->GetFrameFormat()->SetFormatName( m_aName );
    m_pSaveTable->RestoreAttr( *pTable );

    if( m_pHistory )
    {
        m_pHistory->TmpRollback( rDoc, 0 );
        m_pHistory->SetTmpEnd( m_pHistory->Count() );
    }

    // create frames for the new table
    pNew->MakeOwnFrames();

    // position cursor somewhere in content
    SwNodes::GoNext(&rPtPos);

    ClearFEShellTabCols(rDoc, nullptr);

    // TL_CHART2: need to inform chart of probably changed cell names
    SwChartDataProvider *pPCD = rDoc.getIDocumentChartDataProviderAccess().GetChartDataProvider();
    if (pPCD)
    {
        rDoc.UpdateCharts( pTable->GetFrameFormat()->GetName() );
        rDoc.UpdateCharts( pNew->GetTable().GetFrameFormat()->GetName() );
    }
}

void SwUndoMergeTable::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc& rDoc = rContext.GetDoc();
    SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor());

    pPam->DeleteMark();
    if( m_bWithPrev )
        pPam->GetPoint()->Assign( m_nTableNode + 3 );
    else
        pPam->GetPoint()->Assign( m_nTableNode );

    rDoc.MergeTable( *pPam->GetPoint(), m_bWithPrev );

    ClearFEShellTabCols(rDoc, nullptr);
}

void SwUndoMergeTable::RepeatImpl(::sw::RepeatContext & rContext)
{
    SwDoc& rDoc = rContext.GetDoc();
    SwPaM *const pPam = & rContext.GetRepeatPaM();

    rDoc.MergeTable( *pPam->GetPoint(), m_bWithPrev );
    ClearFEShellTabCols(rDoc, nullptr);
}

void SwUndoMergeTable::SaveFormula( SwHistory& rHistory )
{
    if( !m_pHistory )
        m_pHistory.reset( new SwHistory );
    m_pHistory->Move( 0, &rHistory );
}

void InsertSort( std::vector<sal_uInt16>& rArr, sal_uInt16 nIdx )
{
    size_t nO = rArr.size();
    size_t nU = 0;
    if( nO > 0 )
    {
        nO--;
        while( nU <= nO )
        {
            const size_t nM = nU + ( nO - nU ) / 2;
            if ( rArr[nM] == nIdx )
            {
                OSL_FAIL( "Index already exists. This should never happen." );
                return;
            }
            if( rArr[nM] < nIdx )
                nU = nM + 1;
            else if( nM == 0 )
                break;
            else
                nO = nM - 1;
        }
    }
    rArr.insert( rArr.begin() + nU, nIdx );
}

#if OSL_DEBUG_LEVEL > 0
void CheckTable( const SwTable& rTable )
{
    const SwNodes& rNds = rTable.GetFrameFormat()->GetDoc().GetNodes();
    const SwTableSortBoxes& rSrtArr = rTable.GetTabSortBoxes();
    for (size_t n = 0; n < rSrtArr.size(); ++n)
    {
        const SwTableBox* pBox = rSrtArr[ n ];
        const SwNode* pNd = pBox->GetSttNd();
        OSL_ENSURE( rNds[ pBox->GetSttIdx() ] == pNd, "Box with wrong StartNode"  );
    }
}
#endif

SwUndoTableStyleMake::SwUndoTableStyleMake(TableStyleName aName, const SwDoc& rDoc)
    : SwUndo(SwUndoId::TBLSTYLE_CREATE, rDoc),
    m_sName(std::move(aName))
{ }

SwUndoTableStyleMake::~SwUndoTableStyleMake()
{ }

void SwUndoTableStyleMake::UndoImpl(::sw::UndoRedoContext & rContext)
{
    m_pAutoFormat = rContext.GetDoc().DelTableStyle(m_sName, true);
}

void SwUndoTableStyleMake::RedoImpl(::sw::UndoRedoContext & rContext)
{
    if (m_pAutoFormat)
    {
        SwTableAutoFormat* pFormat = rContext.GetDoc().MakeTableStyle(m_sName);
        if (pFormat)
        {
            *pFormat = *m_pAutoFormat;
            m_pAutoFormat.reset();
        }
    }
}

SwRewriter SwUndoTableStyleMake::GetRewriter() const
{
    SwRewriter aResult;
    aResult.AddRule(UndoArg1, m_sName.toString());
    return aResult;
}

SwUndoTableStyleDelete::SwUndoTableStyleDelete(std::unique_ptr<SwTableAutoFormat> pAutoFormat, std::vector<SwTable*>&& rAffectedTables, const SwDoc& rDoc)
    : SwUndo(SwUndoId::TBLSTYLE_DELETE, rDoc),
    m_pAutoFormat(std::move(pAutoFormat)),
    m_rAffectedTables(std::move(rAffectedTables))
{ }

SwUndoTableStyleDelete::~SwUndoTableStyleDelete()
{ }

void SwUndoTableStyleDelete::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwTableAutoFormat* pNewFormat = rContext.GetDoc().MakeTableStyle(m_pAutoFormat->GetName());
    *pNewFormat = *m_pAutoFormat;
    for (size_t i=0; i < m_rAffectedTables.size(); i++)
        m_rAffectedTables[i]->SetTableStyleName(m_pAutoFormat->GetName());
}

void SwUndoTableStyleDelete::RedoImpl(::sw::UndoRedoContext & rContext)
{
    // Don't need to remember deleted table style nor affected tables, because they must be the same as these already known.
    rContext.GetDoc().DelTableStyle(m_pAutoFormat->GetName());
}

SwRewriter SwUndoTableStyleDelete::GetRewriter() const
{
    SwRewriter aResult;
    aResult.AddRule(UndoArg1, m_pAutoFormat->GetName().toString());
    return aResult;
}

SwUndoTableStyleUpdate::SwUndoTableStyleUpdate(const SwTableAutoFormat& rNewFormat, const SwTableAutoFormat& rOldFormat, const SwDoc& rDoc)
    : SwUndo(SwUndoId::TBLSTYLE_UPDATE, rDoc)
    , m_pOldFormat(new SwTableAutoFormat(rOldFormat))
    , m_pNewFormat(new SwTableAutoFormat(rNewFormat))
{ }

SwUndoTableStyleUpdate::~SwUndoTableStyleUpdate()
{ }

void SwUndoTableStyleUpdate::UndoImpl(::sw::UndoRedoContext & rContext)
{
    rContext.GetDoc().ChgTableStyle(m_pNewFormat->GetName(), *m_pOldFormat);
}

void SwUndoTableStyleUpdate::RedoImpl(::sw::UndoRedoContext & rContext)
{
    rContext.GetDoc().ChgTableStyle(m_pNewFormat->GetName(), *m_pNewFormat);
}

SwRewriter SwUndoTableStyleUpdate::GetRewriter() const
{
    SwRewriter aResult;
    aResult.AddRule(UndoArg1, m_pNewFormat->GetName().toString());
    return aResult;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
