/* -*- 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 <config_wasm_strip.h>
#include <memory>
#include <fesh.hxx>
#include <hintids.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/protitem.hxx>
#include <editeng/boxitem.hxx>
#include <svl/stritem.hxx>
#include <editeng/shaditem.hxx>
#include <fmtfsize.hxx>
#include <fmtornt.hxx>
#include <fmtfordr.hxx>
#include <fmtpdsc.hxx>
#include <fmtanchr.hxx>
#include <fmtlsplt.hxx>
#include <frmatr.hxx>
#include <cellfrm.hxx>
#include <pagefrm.hxx>
#include <tabcol.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <UndoManager.hxx>
#include <DocumentSettingManager.hxx>
#include <IDocumentChartDataProviderAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentState.hxx>
#include <cntfrm.hxx>
#include <pam.hxx>
#include <swcrsr.hxx>
#include <swtable.hxx>
#include <swundo.hxx>
#include <tblsel.hxx>
#include <poolfmt.hxx>
#include <tabfrm.hxx>
#include <UndoCore.hxx>
#include <UndoRedline.hxx>
#include <UndoDelete.hxx>
#include <UndoNumbering.hxx>
#include <UndoTable.hxx>
#include <hints.hxx>
#include <tblafmt.hxx>
#include <frminf.hxx>
#include <cellatr.hxx>
#include <swtblfmt.hxx>
#include <swddetbl.hxx>
#include <mvsave.hxx>
#include <docary.hxx>
#include <redline.hxx>
#include <rolbck.hxx>
#include <tblrwcl.hxx>
#include <editsh.hxx>
#include <txtfrm.hxx>
#include <section.hxx>
#include <frmtool.hxx>
#include <node2lay.hxx>
#include <strings.hrc>
#include <docsh.hxx>
#include <unochart.hxx>
#include <node.hxx>
#include <ndtxt.hxx>
#include <cstdlib>
#include <map>
#include <algorithm>
#include <rootfrm.hxx>
#include <fldupde.hxx>
#include <calbck.hxx>
#include <fntcache.hxx>
#include <frameformats.hxx>
#include <o3tl/numeric.hxx>
#include <o3tl/string_view.hxx>
#include <svl/numformat.hxx>
#include <tools/datetimeutils.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>

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

using ::editeng::SvxBorderLine;
using namespace ::com::sun::star;

const sal_Unicode T2T_PARA = 0x0a;

static void lcl_SetDfltBoxAttr( SwFrameFormat& rFormat, sal_uInt8 nId )
{
    bool bTop = false, bBottom = false, bLeft = false, bRight = false;
    switch ( nId )
    {
    case 0: bTop = bBottom = bLeft = true;          break;
    case 1: bTop = bBottom = bLeft = bRight = true; break;
    case 2: bBottom = bLeft = true;                 break;
    case 3: bBottom = bLeft = bRight = true;        break;
    }

    const bool bHTML = rFormat.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE);
    Color aCol( bHTML ? COL_GRAY : COL_BLACK );
    // Default border in Writer: 0.5pt (matching Word)
    SvxBorderLine aLine( &aCol, SvxBorderLineWidth::VeryThin );
    if ( bHTML )
    {
        aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
    }
    SvxBoxItem aBox(RES_BOX);
    aBox.SetAllDistances(55);
    if ( bTop )
        aBox.SetLine( &aLine, SvxBoxItemLine::TOP );
    if ( bBottom )
        aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM );
    if ( bLeft )
        aBox.SetLine( &aLine, SvxBoxItemLine::LEFT );
    if ( bRight )
        aBox.SetLine( &aLine, SvxBoxItemLine::RIGHT );
    rFormat.SetFormatAttr( aBox );
}

typedef std::map<SwFrameFormat *, SwTableBoxFormat *> DfltBoxAttrMap_t;
typedef std::vector<DfltBoxAttrMap_t *> DfltBoxAttrList_t;

static void
lcl_SetDfltBoxAttr(SwTableBox& rBox, DfltBoxAttrList_t & rBoxFormatArr,
        sal_uInt8 const nId, SwTableAutoFormat const*const pAutoFormat = nullptr)
{
    DfltBoxAttrMap_t * pMap = rBoxFormatArr[ nId ];
    if (!pMap)
    {
        pMap = new DfltBoxAttrMap_t;
        rBoxFormatArr[ nId ] = pMap;
    }

    SwTableBoxFormat* pNewTableBoxFormat = nullptr;
    SwFrameFormat* pBoxFrameFormat = rBox.GetFrameFormat();
    DfltBoxAttrMap_t::iterator const iter(pMap->find(pBoxFrameFormat));
    if (pMap->end() != iter)
    {
        pNewTableBoxFormat = iter->second;
    }
    else
    {
        SwDoc* pDoc = pBoxFrameFormat->GetDoc();
        // format does not exist, so create it
        pNewTableBoxFormat = pDoc->MakeTableBoxFormat();
        pNewTableBoxFormat->SetFormatAttr( pBoxFrameFormat->GetAttrSet().Get( RES_FRM_SIZE ) );

        if( pAutoFormat )
            pAutoFormat->UpdateToSet( nId, false, false,
                                    const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pNewTableBoxFormat->GetAttrSet())),
                                    SwTableAutoFormatUpdateFlags::Box,
                                    pDoc->GetNumberFormatter() );
        else
            ::lcl_SetDfltBoxAttr( *pNewTableBoxFormat, nId );

        (*pMap)[pBoxFrameFormat] = pNewTableBoxFormat;
    }
    rBox.ChgFrameFormat( pNewTableBoxFormat );
}

static SwTableBoxFormat *lcl_CreateDfltBoxFormat( SwDoc &rDoc, std::vector<SwTableBoxFormat*> &rBoxFormatArr,
                                    sal_uInt16 nCols, sal_uInt8 nId )
{
    if ( !rBoxFormatArr[nId] )
    {
        SwTableBoxFormat* pBoxFormat = rDoc.MakeTableBoxFormat();
        if( USHRT_MAX != nCols )
            pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable,
                                            USHRT_MAX / nCols, 0 ));
        ::lcl_SetDfltBoxAttr( *pBoxFormat, nId );
        rBoxFormatArr[ nId ] = pBoxFormat;
    }
    return rBoxFormatArr[nId];
}

static SwTableBoxFormat *lcl_CreateAFormatBoxFormat( SwDoc &rDoc, std::vector<SwTableBoxFormat*> &rBoxFormatArr,
                                    const SwTableAutoFormat& rAutoFormat,
                                    const sal_uInt16 nRows, const sal_uInt16 nCols, sal_uInt8 nId )
{
    if( !rBoxFormatArr[nId] )
    {
        SwTableBoxFormat* pBoxFormat = rDoc.MakeTableBoxFormat();
        rAutoFormat.UpdateToSet( nId, nRows==1, nCols==1,
                                const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pBoxFormat->GetAttrSet())),
                                SwTableAutoFormatUpdateFlags::Box,
                                rDoc.GetNumberFormatter( ) );
        if( USHRT_MAX != nCols )
            pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable,
                                            USHRT_MAX / nCols, 0 ));
        rBoxFormatArr[ nId ] = pBoxFormat;
    }
    return rBoxFormatArr[nId];
}

SwTableNode* SwDoc::IsIdxInTable( const SwNodeIndex& rIdx ) { return IsInTable(rIdx.GetNode()); }

SwTableNode* SwDoc::IsInTable(const SwNode& rIdx)
{
    SwNode* pNd = const_cast<SwNode*>(&rIdx);
    do {
        pNd = pNd->StartOfSectionNode();
        SwTableNode* pTableNd = pNd->GetTableNode();
        if( pTableNd )
            return pTableNd;
    } while ( pNd->GetIndex() );
    return nullptr;
}

/**
 * Insert a new Box before the InsPos
 */
bool SwNodes::InsBoxen( SwTableNode* pTableNd,
                        SwTableLine* pLine,
                        SwTableBoxFormat* pBoxFormat,
                        SwTextFormatColl* pTextColl,
                        const SfxItemSet* pAutoAttr,
                        sal_uInt16 nInsPos,
                        sal_uInt16 nCnt )
{
    if( !nCnt )
        return false;
    OSL_ENSURE( pLine, "No valid Line" );

    // Move Index after the Line's last Box
    SwNodeOffset nIdxPos(0);
    SwTableBox *pPrvBox = nullptr, *pNxtBox = nullptr;
    if( !pLine->GetTabBoxes().empty() )
    {
        if( nInsPos < pLine->GetTabBoxes().size() )
        {
            pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable(),
                            pLine->GetTabBoxes()[ nInsPos ] );
            if( nullptr == pPrvBox )
                pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable() );
        }
        else
        {
            pNxtBox = pLine->FindNextBox( pTableNd->GetTable(),
                            pLine->GetTabBoxes().back() );
            if( nullptr == pNxtBox )
                pNxtBox = pLine->FindNextBox( pTableNd->GetTable() );
        }
    }
    else
    {
        pNxtBox = pLine->FindNextBox( pTableNd->GetTable() );
        if( nullptr == pNxtBox )
            pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable() );
    }

    if( !pPrvBox && !pNxtBox )
    {
        bool bSetIdxPos = true;
        if( !pTableNd->GetTable().GetTabLines().empty() && !nInsPos )
        {
            const SwTableLine* pTableLn = pLine;
            while( pTableLn->GetUpper() )
                pTableLn = pTableLn->GetUpper()->GetUpper();

            if( pTableNd->GetTable().GetTabLines()[ 0 ] == pTableLn )
            {
                // Before the Table's first Box
                while( !( pNxtBox = pLine->GetTabBoxes()[0])->GetTabLines().empty() )
                    pLine = pNxtBox->GetTabLines()[0];
                nIdxPos = pNxtBox->GetSttIdx();
                bSetIdxPos = false;
            }
        }
        if( bSetIdxPos )
            // Tables without content or at the end; move before the End
            nIdxPos = pTableNd->EndOfSectionIndex();
    }
    else if( pNxtBox ) // There is a successor
        nIdxPos = pNxtBox->GetSttIdx();
    else // There is a predecessor
        nIdxPos = pPrvBox->GetSttNd()->EndOfSectionIndex() + 1;

    SwNodeIndex aEndIdx( *this, nIdxPos );
    for( sal_uInt16 n = 0; n < nCnt; ++n )
    {
        SwStartNode* pSttNd = new SwStartNode( aEndIdx.GetNode(), SwNodeType::Start,
                                                SwTableBoxStartNode );
        pSttNd->m_pStartOfSection = pTableNd;
        new SwEndNode( aEndIdx.GetNode(), *pSttNd );

        pPrvBox = new SwTableBox( pBoxFormat, *pSttNd, pLine );

        SwTableBoxes & rTabBoxes = pLine->GetTabBoxes();
        sal_uInt16 nRealInsPos = nInsPos + n;
        if (nRealInsPos > rTabBoxes.size())
            nRealInsPos = rTabBoxes.size();

        rTabBoxes.insert( rTabBoxes.begin() + nRealInsPos, pPrvBox );

        if( ! pTextColl->IsAssignedToListLevelOfOutlineStyle()
            && RES_CONDTXTFMTCOLL != pTextColl->Which()
        )
            new SwTextNode( *pSttNd->EndOfSectionNode(), pTextColl, pAutoAttr );
        else
        {
            // Handle Outline numbering correctly!
            SwTextNode* pTNd = new SwTextNode(
                            *pSttNd->EndOfSectionNode(),
                            GetDoc().GetDfltTextFormatColl(),
                            pAutoAttr );
            pTNd->ChgFormatColl( pTextColl );
        }
    }
    return true;
}

/**
 * Insert a new Table
 */
const SwTable* SwDoc::InsertTable( const SwInsertTableOptions& rInsTableOpts,
                                   const SwPosition& rPos, sal_uInt16 nRows,
                                   sal_uInt16 nCols, sal_Int16 eAdjust,
                                   const SwTableAutoFormat* pTAFormat,
                                   const std::vector<sal_uInt16> *pColArr,
                                   bool bCalledFromShell,
                                   bool bNewModel,
                                   const OUString& rTableName )
{
    assert(nRows && "Table without line?");
    assert(nCols && "Table without rows?");

    {
        // Do not copy into Footnotes!
        if( rPos.GetNode() < GetNodes().GetEndOfInserts() &&
            rPos.GetNode().GetIndex() >= GetNodes().GetEndOfInserts().StartOfSectionIndex() )
            return nullptr;

        // If the ColumnArray has a wrong count, ignore it!
        if( pColArr &&
            static_cast<size_t>(nCols + ( text::HoriOrientation::NONE == eAdjust ? 2 : 1 )) != pColArr->size() )
            pColArr = nullptr;
    }

    OUString aTableName = rTableName;
    if (aTableName.isEmpty() || FindTableFormatByName(aTableName) != nullptr)
        aTableName = GetUniqueTableName();

    if( GetIDocumentUndoRedo().DoesUndo() )
    {
        GetIDocumentUndoRedo().AppendUndo(
            std::make_unique<SwUndoInsTable>( rPos, nCols, nRows, o3tl::narrowing<sal_uInt16>(eAdjust),
                                      rInsTableOpts, pTAFormat, pColArr,
                                      aTableName));
    }

    // Start with inserting the Nodes and get the AutoFormat for the Table
    SwTextFormatColl *pBodyColl = getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE ),
                 *pHeadColl = pBodyColl;

    bool bDfltBorders( rInsTableOpts.mnInsMode & SwInsertTableFlags::DefaultBorder );

    if( (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) && (1 != nRows || !bDfltBorders) )
        pHeadColl = getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE_HDLN );

    const sal_uInt16 nRowsToRepeat =
            SwInsertTableFlags::Headline == (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) ?
            rInsTableOpts.mnRowsToRepeat :
            0;

    /* Save content node to extract FRAMEDIR from. */
    const SwContentNode * pContentNd = rPos.GetNode().GetContentNode();

    /* If we are called from a shell pass the attrset from
        pContentNd (aka the node the table is inserted at) thus causing
        SwNodes::InsertTable to propagate an adjust item if
        necessary. */
    SwTableNode *pTableNd = SwNodes::InsertTable(
        rPos.GetNode(),
        nCols,
        pBodyColl,
        nRows,
        nRowsToRepeat,
        pHeadColl,
        bCalledFromShell ? &pContentNd->GetSwAttrSet() : nullptr );

    // Create the Box/Line/Table construct
    SwTableLineFormat* pLineFormat = MakeTableLineFormat();
    SwTableFormat* pTableFormat = MakeTableFrameFormat( aTableName, GetDfltFrameFormat() );

    /* If the node to insert the table at is a context node and has a
       non-default FRAMEDIR propagate it to the table. */
    if (pContentNd)
    {
        const SwAttrSet & aNdSet = pContentNd->GetSwAttrSet();
        if (const SvxFrameDirectionItem* pItem = aNdSet.GetItemIfSet( RES_FRAMEDIR ))
        {
            pTableFormat->SetFormatAttr( *pItem );
        }
    }

    // Set Orientation at the Table's Format
    pTableFormat->SetFormatAttr( SwFormatHoriOrient( 0, eAdjust ) );
    // All lines use the left-to-right Fill-Order!
    pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT ));

    // Set USHRT_MAX as the Table's default SSize
    SwTwips nWidth = USHRT_MAX;
    if( pColArr )
    {
        sal_uInt16 nSttPos = pColArr->front();
        sal_uInt16 nLastPos = pColArr->back();
        if( text::HoriOrientation::NONE == eAdjust )
        {
            sal_uInt16 nFrameWidth = nLastPos;
            nLastPos = (*pColArr)[ pColArr->size()-2 ];
            pTableFormat->SetFormatAttr( SvxLRSpaceItem( nSttPos, nFrameWidth - nLastPos, 0, 0, RES_LR_SPACE ) );
        }
        nWidth = nLastPos - nSttPos;
    }
    else
    {
        nWidth /= nCols;
        nWidth *= nCols; // to avoid rounding problems
    }
    pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth ));
    if( !(rInsTableOpts.mnInsMode & SwInsertTableFlags::SplitLayout) )
        pTableFormat->SetFormatAttr( SwFormatLayoutSplit( false ));

    // Move the hard PageDesc/PageBreak Attributes if needed
    SwContentNode* pNextNd = GetNodes()[ pTableNd->EndOfSectionIndex()+1 ]
                            ->GetContentNode();
    if( pNextNd && pNextNd->HasSwAttrSet() )
    {
        const SfxItemSet* pNdSet = pNextNd->GetpSwAttrSet();
        if( const SwFormatPageDesc* pItem = pNdSet->GetItemIfSet( RES_PAGEDESC, false ) )
        {
            pTableFormat->SetFormatAttr( *pItem );
            pNextNd->ResetAttr( RES_PAGEDESC );
            pNdSet = pNextNd->GetpSwAttrSet();
        }
        const SvxFormatBreakItem* pItem;
        if( pNdSet && (pItem = pNdSet->GetItemIfSet( RES_BREAK, false )) )
        {
            pTableFormat->SetFormatAttr( *pItem );
            pNextNd->ResetAttr( RES_BREAK );
        }
    }

    SwTable& rNdTable = pTableNd->GetTable();
    rNdTable.RegisterToFormat( *pTableFormat );

    rNdTable.SetRowsToRepeat( nRowsToRepeat );
    rNdTable.SetTableModel( bNewModel );

    std::vector<SwTableBoxFormat*> aBoxFormatArr;
    SwTableBoxFormat* pBoxFormat = nullptr;
    if( !bDfltBorders && !pTAFormat )
    {
        pBoxFormat = MakeTableBoxFormat();
        pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX / nCols, 0 ));
    }
    else
    {
        const sal_uInt16 nBoxArrLen = pTAFormat ? 16 : 4;
        aBoxFormatArr.resize( nBoxArrLen, nullptr );
    }
    SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1> aCharSet( GetAttrPool() );

    SwNodeIndex aNdIdx( *pTableNd, 1 ); // Set to StartNode of first Box
    SwTableLines& rLines = rNdTable.GetTabLines();
    for( sal_uInt16 n = 0; n < nRows; ++n )
    {
        SwTableLine* pLine = new SwTableLine( pLineFormat, nCols, nullptr );
        rLines.insert( rLines.begin() + n, pLine );
        SwTableBoxes& rBoxes = pLine->GetTabBoxes();
        for( sal_uInt16 i = 0; i < nCols; ++i )
        {
            SwTableBoxFormat *pBoxF;
            if( pTAFormat )
            {
                sal_uInt8 nId = SwTableAutoFormat::CountPos(i, nCols, n, nRows);
                pBoxF = ::lcl_CreateAFormatBoxFormat( *this, aBoxFormatArr, *pTAFormat,
                                                nRows, nCols, nId );

                // Set the Paragraph/Character Attributes if needed
                if( pTAFormat->IsFont() || pTAFormat->IsJustify() )
                {
                    aCharSet.ClearItem();
                    pTAFormat->UpdateToSet( nId, nRows==1, nCols==1, aCharSet,
                                        SwTableAutoFormatUpdateFlags::Char, nullptr );
                    if( aCharSet.Count() )
                        GetNodes()[ aNdIdx.GetIndex()+1 ]->GetContentNode()->
                            SetAttr( aCharSet );
                }
            }
            else if( bDfltBorders )
            {
                sal_uInt8 nBoxId = (i < nCols - 1 ? 0 : 1) + (n ? 2 : 0 );
                pBoxF = ::lcl_CreateDfltBoxFormat( *this, aBoxFormatArr, nCols, nBoxId);
            }
            else
                pBoxF = pBoxFormat;

            // For AutoFormat on input: the columns are set when inserting the Table
            // The Array contains the columns positions and not their widths!
            if( pColArr )
            {
                nWidth = (*pColArr)[ i + 1 ] - (*pColArr)[ i ];
                if( pBoxF->GetFrameSize().GetWidth() != nWidth )
                {
                    if( pBoxF->HasWriterListeners() ) // Create new Format
                    {
                        SwTableBoxFormat *pNewFormat = MakeTableBoxFormat();
                        *pNewFormat = *pBoxF;
                        pBoxF = pNewFormat;
                    }
                    pBoxF->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth ));
                }
            }

            SwTableBox *pBox = new SwTableBox( pBoxF, aNdIdx, pLine);
            rBoxes.insert( rBoxes.begin() + i, pBox );
            aNdIdx += SwNodeOffset(3); // StartNode, TextNode, EndNode  == 3 Nodes
        }
    }
    // Insert Frames
    pTableNd->MakeOwnFrames();

    // To-Do - add 'SwExtraRedlineTable' also ?
    if( getIDocumentRedlineAccess().IsRedlineOn() || (!getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() ))
    {
        SwPaM aPam( *pTableNd->EndOfSectionNode(), *pTableNd, SwNodeOffset(1) );
        if( getIDocumentRedlineAccess().IsRedlineOn() )
            getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
        else
            getIDocumentRedlineAccess().SplitRedline( aPam );
    }

    getIDocumentState().SetModified();
    CHECK_TABLE(rNdTable);
    return &rNdTable;
}

SwTableNode* SwNodes::InsertTable( SwNode& rNd,
                                   sal_uInt16 nBoxes,
                                   SwTextFormatColl* pContentTextColl,
                                   sal_uInt16 nLines,
                                   sal_uInt16 nRepeat,
                                   SwTextFormatColl* pHeadlineTextColl,
                                   const SwAttrSet * pAttrSet)
{
    if( !nBoxes )
        return nullptr;

    // If Lines is given, create the Matrix from Lines and Boxes
    if( !pHeadlineTextColl || !nLines )
        pHeadlineTextColl = pContentTextColl;

    SwTableNode * pTableNd = new SwTableNode( rNd );
    SwEndNode* pEndNd = new SwEndNode( rNd, *pTableNd );

    if( !nLines ) // For the for loop
        ++nLines;

    SwTextFormatColl* pTextColl = pHeadlineTextColl;
    for( sal_uInt16 nL = 0; nL < nLines; ++nL )
    {
        for( sal_uInt16 nB = 0; nB < nBoxes; ++nB )
        {
            SwStartNode* pSttNd = new SwStartNode( *pEndNd, SwNodeType::Start,
                                                    SwTableBoxStartNode );
            pSttNd->m_pStartOfSection = pTableNd;

            SwTextNode * pTmpNd = new SwTextNode( *pEndNd, pTextColl );

            // #i60422# Propagate some more attributes.
            const SfxPoolItem* pItem = nullptr;
            if ( nullptr != pAttrSet )
            {
                static const sal_uInt16 aPropagateItems[] = {
                    RES_PARATR_ADJUST,
                    RES_CHRATR_FONT, RES_CHRATR_FONTSIZE,
                    RES_CHRATR_CJK_FONT, RES_CHRATR_CJK_FONTSIZE,
                    RES_CHRATR_CTL_FONT, RES_CHRATR_CTL_FONTSIZE, 0 };

                const sal_uInt16* pIdx = aPropagateItems;
                while ( *pIdx != 0 )
                {
                    if ( SfxItemState::SET != pTmpNd->GetSwAttrSet().GetItemState( *pIdx ) &&
                         SfxItemState::SET == pAttrSet->GetItemState( *pIdx, true, &pItem ) )
                        static_cast<SwContentNode *>(pTmpNd)->SetAttr(*pItem);
                    ++pIdx;
                }
            }

            new SwEndNode( *pEndNd, *pSttNd );
        }
        if ( nL + 1 >= nRepeat )
            pTextColl = pContentTextColl;
    }
    return pTableNd;
}

/**
 * Text to Table
 */
const SwTable* SwDoc::TextToTable( const SwInsertTableOptions& rInsTableOpts,
                                   const SwPaM& rRange, sal_Unicode cCh,
                                   sal_Int16 eAdjust,
                                   const SwTableAutoFormat* pTAFormat )
{
    // See if the selection contains a Table
    auto [pStt, pEnd] = rRange.StartEnd(); // SwPosition*
    {
        SwNodeOffset nCnt = pStt->GetNodeIndex();
        for( ; nCnt <= pEnd->GetNodeIndex(); ++nCnt )
            if( !GetNodes()[ nCnt ]->IsTextNode() )
                return nullptr;
    }

    if (GetIDocumentUndoRedo().DoesUndo())
    {
        GetIDocumentUndoRedo().StartUndo(SwUndoId::TEXTTOTABLE, nullptr);
    }

    // tdf#153115 first, remove all redlines; splitting them at cell boundaries
    // would be tricky to implement, and it's unclear what the value of
    // existing redlines is once it's been converted to a table
    getIDocumentRedlineAccess().AcceptRedline(rRange, true);

    // Save first node in the selection if it is a context node
    SwContentNode * pSttContentNd = pStt->GetNode().GetContentNode();

    SwPaM aOriginal( *pStt, *pEnd );
    pStt = aOriginal.GetMark();
    pEnd = aOriginal.GetPoint();

    SwUndoTextToTable* pUndo = nullptr;
    if( GetIDocumentUndoRedo().DoesUndo() )
    {
        pUndo = new SwUndoTextToTable( aOriginal, rInsTableOpts, cCh,
                    o3tl::narrowing<sal_uInt16>(eAdjust), pTAFormat );
        GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );

        // Do not add splitting the TextNode to the Undo history
        GetIDocumentUndoRedo().DoUndo( false );
    }

    ::PaMCorrAbs( aOriginal, *pEnd );

    // Make sure that the range is on Node Edges
    SwNodeRange aRg( pStt->GetNode(), pEnd->GetNode() );
    if( pStt->GetContentIndex() )
        getIDocumentContentOperations().SplitNode( *pStt, false );

    bool bEndContent = 0 != pEnd->GetContentIndex();

    // Do not split at the End of a Line (except at the End of the Doc)
    if( bEndContent )
    {
        if( pEnd->GetNode().GetContentNode()->Len() != pEnd->GetContentIndex()
            || pEnd->GetNodeIndex() >= GetNodes().GetEndOfContent().GetIndex()-1 )
        {
            getIDocumentContentOperations().SplitNode( *pEnd, false );
            const_cast<SwPosition*>(pEnd)->Adjust(SwNodeOffset(-1));
            // A Node and at the End?
            if( pStt->GetNodeIndex() >= pEnd->GetNodeIndex() )
                --aRg.aStart;
        }
        else
            ++aRg.aEnd;
    }

    if( aRg.aEnd.GetIndex() == aRg.aStart.GetIndex() )
    {
        OSL_FAIL( "empty range" );
        ++aRg.aEnd;
    }

    // We always use Upper to insert the Table
    SwNode2LayoutSaveUpperFrames aNode2Layout( aRg.aStart.GetNode() );

    GetIDocumentUndoRedo().DoUndo( nullptr != pUndo );

    // Create the Box/Line/Table construct
    SwTableBoxFormat* pBoxFormat = MakeTableBoxFormat();
    SwTableLineFormat* pLineFormat = MakeTableLineFormat();
    SwTableFormat* pTableFormat = MakeTableFrameFormat( GetUniqueTableName(), GetDfltFrameFormat() );

    // All Lines have a left-to-right Fill Order
    pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT ));
    // The Table's SSize is USHRT_MAX
    pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX ));
    if( !(rInsTableOpts.mnInsMode & SwInsertTableFlags::SplitLayout) )
        pTableFormat->SetFormatAttr( SwFormatLayoutSplit( false ));

    /* If the first node in the selection is a context node and if it
       has an item FRAMEDIR set (no default) propagate the item to the
       replacing table. */
    if (pSttContentNd)
    {
        const SwAttrSet & aNdSet = pSttContentNd->GetSwAttrSet();
        if (const SvxFrameDirectionItem *pItem = aNdSet.GetItemIfSet( RES_FRAMEDIR ) )
        {
            pTableFormat->SetFormatAttr( *pItem );
        }
    }

    //Resolves: tdf#87977, tdf#78599, disable broadcasting modifications
    //until after RegisterToFormat is completed
    bool bEnableSetModified = getIDocumentState().IsEnableSetModified();
    getIDocumentState().SetEnableSetModified(false);

    SwTableNode* pTableNd = GetNodes().TextToTable(
            aRg, cCh, pTableFormat, pLineFormat, pBoxFormat,
            getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ), pUndo );

    SwTable& rNdTable = pTableNd->GetTable();

    const sal_uInt16 nRowsToRepeat =
            SwInsertTableFlags::Headline == (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) ?
            rInsTableOpts.mnRowsToRepeat :
            0;
    rNdTable.SetRowsToRepeat(nRowsToRepeat);

    bool bUseBoxFormat = false;
    if( !pBoxFormat->HasWriterListeners() )
    {
        // The Box's Formats already have the right size, we must only set
        // the right Border/AutoFormat.
        bUseBoxFormat = true;
        pTableFormat->SetFormatAttr( pBoxFormat->GetFrameSize() );
        delete pBoxFormat;
        eAdjust = text::HoriOrientation::NONE;
    }

    // Set Orientation in the Table's Format
    pTableFormat->SetFormatAttr( SwFormatHoriOrient( 0, eAdjust ) );
    rNdTable.RegisterToFormat(*pTableFormat);

    if( pTAFormat || ( rInsTableOpts.mnInsMode & SwInsertTableFlags::DefaultBorder) )
    {
        sal_uInt8 nBoxArrLen = pTAFormat ? 16 : 4;
        std::unique_ptr< DfltBoxAttrList_t > aBoxFormatArr1;
        std::optional< std::vector<SwTableBoxFormat*> > aBoxFormatArr2;
        if( bUseBoxFormat )
        {
            aBoxFormatArr1.reset(new DfltBoxAttrList_t( nBoxArrLen, nullptr ));
        }
        else
        {
            aBoxFormatArr2 = std::vector<SwTableBoxFormat*>( nBoxArrLen, nullptr );
        }

        SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1> aCharSet( GetAttrPool() );

        SwHistory* pHistory = pUndo ? &pUndo->GetHistory() : nullptr;

        SwTableBoxFormat *pBoxF = nullptr;
        SwTableLines& rLines = rNdTable.GetTabLines();
        const SwTableLines::size_type nRows = rLines.size();
        for( SwTableLines::size_type n = 0; n < nRows; ++n )
        {
            SwTableBoxes& rBoxes = rLines[ n ]->GetTabBoxes();
            const SwTableBoxes::size_type nCols = rBoxes.size();
            for( SwTableBoxes::size_type i = 0; i < nCols; ++i )
            {
                SwTableBox* pBox = rBoxes[ i ];
                bool bChgSz = false;

                if( pTAFormat )
                {
                    sal_uInt8 nId = static_cast<sal_uInt8>(!n ? 0 : (( n+1 == nRows )
                                            ? 12 : (4 * (1 + ((n-1) & 1 )))));
                    nId = nId + static_cast<sal_uInt8>(!i ? 0 :
                                ( i+1 == nCols ? 3 : (1 + ((i-1) & 1))));
                    if( bUseBoxFormat )
                        ::lcl_SetDfltBoxAttr( *pBox, *aBoxFormatArr1, nId, pTAFormat );
                    else
                    {
                        bChgSz = nullptr == (*aBoxFormatArr2)[ nId ];
                        pBoxF = ::lcl_CreateAFormatBoxFormat( *this, *aBoxFormatArr2,
                                                *pTAFormat, USHRT_MAX, USHRT_MAX, nId );
                    }

                    // Set Paragraph/Character Attributes if needed
                    if( pTAFormat->IsFont() || pTAFormat->IsJustify() )
                    {
                        aCharSet.ClearItem();
                        pTAFormat->UpdateToSet( nId, nRows==1, nCols==1, aCharSet,
                                            SwTableAutoFormatUpdateFlags::Char, nullptr );
                        if( aCharSet.Count() )
                        {
                            SwNodeOffset nSttNd = pBox->GetSttIdx()+1;
                            SwNodeOffset nEndNd = pBox->GetSttNd()->EndOfSectionIndex();
                            for( ; nSttNd < nEndNd; ++nSttNd )
                            {
                                SwContentNode* pNd = GetNodes()[ nSttNd ]->GetContentNode();
                                if( pNd )
                                {
                                    if( pHistory )
                                    {
                                        SwRegHistory aReg( pNd, *pNd, pHistory );
                                        pNd->SetAttr( aCharSet );
                                    }
                                    else
                                        pNd->SetAttr( aCharSet );
                                }
                            }
                        }
                    }
                }
                else
                {
                    sal_uInt8 nId = (i < nCols - 1 ? 0 : 1) + (n ? 2 : 0 );
                    if( bUseBoxFormat )
                        ::lcl_SetDfltBoxAttr( *pBox, *aBoxFormatArr1, nId );
                    else
                    {
                        bChgSz = nullptr == (*aBoxFormatArr2)[ nId ];
                        pBoxF = ::lcl_CreateDfltBoxFormat( *this, *aBoxFormatArr2,
                                                        USHRT_MAX, nId );
                    }
                }

                if( !bUseBoxFormat )
                {
                    if( bChgSz )
                        pBoxF->SetFormatAttr( pBox->GetFrameFormat()->GetFrameSize() );
                    pBox->ChgFrameFormat( pBoxF );
                }
            }
        }

        if( bUseBoxFormat )
        {
            for( sal_uInt8 i = 0; i < nBoxArrLen; ++i )
            {
                delete (*aBoxFormatArr1)[ i ];
            }
        }
    }

    // Check the boxes for numbers
    if( IsInsTableFormatNum() )
    {
        for (size_t nBoxes = rNdTable.GetTabSortBoxes().size(); nBoxes; )
        {
            ChkBoxNumFormat(*rNdTable.GetTabSortBoxes()[ --nBoxes ], false);
        }
    }

    SwNodeOffset nIdx = pTableNd->GetIndex();
    aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 );

    {
        SwPaM& rTmp = const_cast<SwPaM&>(rRange); // Point always at the Start
        rTmp.DeleteMark();
        rTmp.GetPoint()->Assign( *pTableNd );
        GetNodes().GoNext( rTmp.GetPoint() );
    }

    if( pUndo )
    {
        GetIDocumentUndoRedo().EndUndo( SwUndoId::TEXTTOTABLE, nullptr );
    }

    getIDocumentState().SetEnableSetModified(bEnableSetModified);
    getIDocumentState().SetModified();
    getIDocumentFieldsAccess().SetFieldsDirty(true, nullptr, SwNodeOffset(0));
    return &rNdTable;
}

static void lcl_RemoveBreaks(SwContentNode & rNode, SwTableFormat *const pTableFormat)
{
    // delete old layout frames, new ones need to be created...
    rNode.DelFrames(nullptr);

    if (!rNode.IsTextNode())
    {
        return;
    }

    SwTextNode & rTextNode = *rNode.GetTextNode();
    // remove PageBreaks/PageDesc/ColBreak
    SfxItemSet const* pSet = rTextNode.GetpSwAttrSet();
    if (!pSet)
        return;

    if (const SvxFormatBreakItem* pItem = pSet->GetItemIfSet(RES_BREAK, false))
    {
        if (pTableFormat)
        {
            pTableFormat->SetFormatAttr(*pItem);
        }
        rTextNode.ResetAttr(RES_BREAK);
        pSet = rTextNode.GetpSwAttrSet();
    }

    const SwFormatPageDesc* pPageDescItem;
    if (pSet
        && (pPageDescItem = pSet->GetItemIfSet(RES_PAGEDESC, false))
        && pPageDescItem->GetPageDesc())
    {
        if (pTableFormat)
        {
            pTableFormat->SetFormatAttr(*pPageDescItem);
        }
        rTextNode.ResetAttr(RES_PAGEDESC);
    }
}

/**
 * balance lines in table, insert empty boxes so all lines have the size
 */
static void
lcl_BalanceTable(SwTable & rTable, size_t const nMaxBoxes,
    SwTableNode & rTableNd, SwTableBoxFormat & rBoxFormat, SwTextFormatColl & rTextColl,
    SwUndoTextToTable *const pUndo, std::vector<sal_uInt16> *const pPositions)
{
    for (size_t n = 0; n < rTable.GetTabLines().size(); ++n)
    {
        SwTableLine *const pCurrLine = rTable.GetTabLines()[ n ];
        size_t const nBoxes = pCurrLine->GetTabBoxes().size();
        if (nMaxBoxes != nBoxes)
        {
            rTableNd.GetNodes().InsBoxen(&rTableNd, pCurrLine, &rBoxFormat, &rTextColl,
                    nullptr, nBoxes, nMaxBoxes - nBoxes);

            if (pUndo)
            {
                for (size_t i = nBoxes; i < nMaxBoxes; ++i)
                {
                    pUndo->AddFillBox( *pCurrLine->GetTabBoxes()[i] );
                }
            }

            // if the first line is missing boxes, the width array is useless!
            if (!n && pPositions)
            {
                pPositions->clear();
            }
        }
    }
}

static void
lcl_SetTableBoxWidths(SwTable & rTable, size_t const nMaxBoxes,
        SwTableBoxFormat & rBoxFormat, SwDoc & rDoc,
        std::vector<sal_uInt16> *const pPositions)
{
    if (pPositions && !pPositions->empty())
    {
        SwTableLines& rLns = rTable.GetTabLines();
        sal_uInt16 nLastPos = 0;
        for (size_t n = 0; n < pPositions->size(); ++n)
        {
            SwTableBoxFormat *pNewFormat = rDoc.MakeTableBoxFormat();
            pNewFormat->SetFormatAttr(
                    SwFormatFrameSize(SwFrameSize::Variable, (*pPositions)[n] - nLastPos));
            for (size_t nTmpLine = 0; nTmpLine < rLns.size(); ++nTmpLine)
            {
                // Have to do an Add here, because the BoxFormat
                // is still needed by the caller
                pNewFormat->Add( rLns[ nTmpLine ]->GetTabBoxes()[ n ] );
            }

            nLastPos = (*pPositions)[ n ];
        }

        // propagate size upwards from format, so the table gets the right size
        SAL_WARN_IF(rBoxFormat.HasWriterListeners(), "sw.core",
                "who is still registered in the format?");
        rBoxFormat.SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nLastPos ));
    }
    else
    {
        size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX;
        rBoxFormat.SetFormatAttr(SwFormatFrameSize(SwFrameSize::Variable, nWidth));
    }
}

SwTableNode* SwNodes::TextToTable( const SwNodeRange& rRange, sal_Unicode cCh,
                                    SwTableFormat* pTableFormat,
                                    SwTableLineFormat* pLineFormat,
                                    SwTableBoxFormat* pBoxFormat,
                                    SwTextFormatColl* pTextColl,
                                    SwUndoTextToTable* pUndo )
{
    if( rRange.aStart >= rRange.aEnd )
        return nullptr;

    SwTableNode * pTableNd = new SwTableNode( rRange.aStart.GetNode() );
    new SwEndNode( rRange.aEnd.GetNode(), *pTableNd );

    SwDoc& rDoc = GetDoc();
    std::vector<sal_uInt16> aPosArr;
    SwTable& rTable = pTableNd->GetTable();
    SwTableBox* pBox;
    sal_uInt16 nBoxes, nLines, nMaxBoxes = 0;

    SwNodeIndex aSttIdx( *pTableNd, 1 );
    SwNodeIndex aEndIdx( rRange.aEnd, -1 );
    for( nLines = 0, nBoxes = 0;
        aSttIdx.GetIndex() < aEndIdx.GetIndex();
        aSttIdx += SwNodeOffset(2), nLines++, nBoxes = 0 )
    {
        SwTextNode* pTextNd = aSttIdx.GetNode().GetTextNode();
        OSL_ENSURE( pTextNd, "Only add TextNodes to the Table" );

        if( !nLines && 0x0b == cCh )
        {
            cCh = 0x09;

            // Get the separator's position from the first Node, in order for the Boxes to be set accordingly
            SwTextFrameInfo aFInfo( static_cast<SwTextFrame*>(pTextNd->getLayoutFrame( pTextNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() )) );
            if( aFInfo.IsOneLine() ) // only makes sense in this case
            {
                OUString const& rText(pTextNd->GetText());
                for (sal_Int32 nChPos = 0; nChPos < rText.getLength(); ++nChPos)
                {
                    if (rText[nChPos] == cCh)
                    {
                        // sw_redlinehide: no idea if this makes any sense...
                        TextFrameIndex const nPos(aFInfo.GetFrame()->MapModelToView(pTextNd, nChPos));
                        aPosArr.push_back( o3tl::narrowing<sal_uInt16>(
                            aFInfo.GetCharPos(nPos+TextFrameIndex(1), false)) );
                    }
                }

                aPosArr.push_back(
                                o3tl::narrowing<sal_uInt16>(aFInfo.GetFrame()->IsVertical() ?
                                aFInfo.GetFrame()->getFramePrintArea().Bottom() :
                                aFInfo.GetFrame()->getFramePrintArea().Right()) );

            }
        }

        lcl_RemoveBreaks(*pTextNd, (0 == nLines) ? pTableFormat : nullptr);

        // Set the TableNode as StartNode for all TextNodes in the Table
        pTextNd->m_pStartOfSection = pTableNd;

        SwTableLine* pLine = new SwTableLine( pLineFormat, 1, nullptr );
        rTable.GetTabLines().insert(rTable.GetTabLines().begin() + nLines, pLine);

        SwStartNode* pSttNd;
        SwPosition aCntPos( aSttIdx, pTextNd, 0);

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

        if( T2T_PARA != cCh )
        {
            for (sal_Int32 nChPos = 0; nChPos < pTextNd->GetText().getLength();)
            {
                if (pTextNd->GetText()[nChPos] == cCh)
                {
                    aCntPos.SetContent(nChPos);
                    std::function<void (SwTextNode *, sw::mark::RestoreMode, bool)> restoreFunc(
                        [&](SwTextNode *const pNewNode, sw::mark::RestoreMode const eMode, bool)
                        {
                            if (!pContentStore->Empty())
                            {
                                pContentStore->Restore(*pNewNode, nChPos, nChPos + 1, eMode);
                            }
                        });
                    SwContentNode *const pNewNd =
                        pTextNd->SplitContentNode(aCntPos, &restoreFunc);

                    // Delete separator and correct search string
                    pTextNd->EraseText( aCntPos, 1 );
                    nChPos = 0;

                    // Set the TableNode as StartNode for all TextNodes in the Table
                    const SwNodeIndex aTmpIdx( aCntPos.GetNode(), -1 );
                    pSttNd = new SwStartNode( aTmpIdx.GetNode(), SwNodeType::Start,
                                                SwTableBoxStartNode );
                    new SwEndNode( aCntPos.GetNode(), *pSttNd );
                    pNewNd->m_pStartOfSection = pSttNd;

                    // Assign Section to the Box
                    pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine );
                    pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox );
                }
                else
                {
                    ++nChPos;
                }
            }
        }

        // Now for the last substring
        if( !pContentStore->Empty())
            pContentStore->Restore( *pTextNd, pTextNd->GetText().getLength(), pTextNd->GetText().getLength()+1 );

        pSttNd = new SwStartNode( aCntPos.GetNode(), SwNodeType::Start, SwTableBoxStartNode );
        const SwNodeIndex aTmpIdx( aCntPos.GetNode(), 1 );
        new SwEndNode( aTmpIdx.GetNode(), *pSttNd  );
        pTextNd->m_pStartOfSection = pSttNd;

        pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine );
        pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox );
        if( nMaxBoxes < nBoxes )
            nMaxBoxes = nBoxes;
    }

    lcl_BalanceTable(rTable, nMaxBoxes, *pTableNd, *pBoxFormat, *pTextColl,
            pUndo, &aPosArr);
    lcl_SetTableBoxWidths(rTable, nMaxBoxes, *pBoxFormat, rDoc, &aPosArr);

    return pTableNd;
}

const SwTable* SwDoc::TextToTable( const std::vector< std::vector<SwNodeRange> >& rTableNodes )
{
    if (rTableNodes.empty())
        return nullptr;

    const std::vector<SwNodeRange>& rFirstRange = *rTableNodes.begin();

    if (rFirstRange.empty())
        return nullptr;

    const std::vector<SwNodeRange>& rLastRange = *rTableNodes.rbegin();

    if (rLastRange.empty())
        return nullptr;

    /* Save first node in the selection if it is a content node. */
    SwContentNode * pSttContentNd = rFirstRange.begin()->aStart.GetNode().GetContentNode();

    const SwNodeRange& rStartRange = *rFirstRange.begin();
    const SwNodeRange& rEndRange = *rLastRange.rbegin();

    //!!! not necessarily TextNodes !!!
    SwPaM aOriginal( rStartRange.aStart, rEndRange.aEnd );
    const SwPosition *pStt = aOriginal.GetMark();
    SwPosition *pEnd = aOriginal.GetPoint();

    bool const bUndo(GetIDocumentUndoRedo().DoesUndo());
    if (bUndo)
    {
        // Do not add splitting the TextNode to the Undo history
        GetIDocumentUndoRedo().DoUndo(false);
    }

    ::PaMCorrAbs( aOriginal, *pEnd );

    // make sure that the range is on Node Edges
    SwNodeRange aRg( pStt->GetNode(), pEnd->GetNode() );
    if( pStt->GetContentIndex() )
        getIDocumentContentOperations().SplitNode( *pStt, false );

    bool bEndContent = 0 != pEnd->GetContentIndex();

    // Do not split at the End of a Line (except at the End of the Doc)
    if( bEndContent )
    {
        if( pEnd->GetNode().GetContentNode()->Len() != pEnd->GetContentIndex()
            || pEnd->GetNodeIndex() >= GetNodes().GetEndOfContent().GetIndex()-1 )
        {
            getIDocumentContentOperations().SplitNode( *pEnd, false );
            pEnd->Adjust(SwNodeOffset(-1));
            // A Node and at the End?
            if( pStt->GetNodeIndex() >= pEnd->GetNodeIndex() )
                --aRg.aStart;
        }
        else
            ++aRg.aEnd;
    }

    assert(aRg.aEnd.GetNode() == pEnd->GetNode());
    assert(aRg.aStart.GetNode() == pStt->GetNode());
    if( aRg.aEnd.GetIndex() == aRg.aStart.GetIndex() )
    {
        OSL_FAIL( "empty range" );
        ++aRg.aEnd;
    }


    {
        // TODO: this is not Undo-able - only good enough for file import
        IDocumentRedlineAccess & rIDRA(getIDocumentRedlineAccess());
        SwNodeIndex const prev(rTableNodes.begin()->begin()->aStart, -1);
        SwNodeIndex const* pPrev(&prev);
        // pPrev could point to non-textnode now
        for (const auto& rRow : rTableNodes)
        {
            for (const auto& rCell : rRow)
            {
                assert(SwNodeIndex(*pPrev, +1) == rCell.aStart);
                SwPaM pam(rCell.aStart, 0, *pPrev,
                        (pPrev->GetNode().IsContentNode())
                            ? pPrev->GetNode().GetContentNode()->Len() : 0);
                rIDRA.SplitRedline(pam);
                pPrev = &rCell.aEnd;
            }
        }
        // another one to break between last cell and node after table
        SwPaM pam(pPrev->GetNode(), SwNodeOffset(+1), 0,
                  pPrev->GetNode(), SwNodeOffset(0),
                    (pPrev->GetNode().IsContentNode())
                        ? pPrev->GetNode().GetContentNode()->Len() : 0);
        rIDRA.SplitRedline(pam);
    }

    // We always use Upper to insert the Table
    SwNode2LayoutSaveUpperFrames aNode2Layout( aRg.aStart.GetNode() );

    GetIDocumentUndoRedo().DoUndo(bUndo);

    // Create the Box/Line/Table construct
    SwTableBoxFormat* pBoxFormat = MakeTableBoxFormat();
    SwTableLineFormat* pLineFormat = MakeTableLineFormat();
    SwTableFormat* pTableFormat = MakeTableFrameFormat( GetUniqueTableName(), GetDfltFrameFormat() );

    // All Lines have a left-to-right Fill Order
    pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT ));
    // The Table's SSize is USHRT_MAX
    pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX ));

    /* If the first node in the selection is a context node and if it
       has an item FRAMEDIR set (no default) propagate the item to the
       replacing table. */
    if (pSttContentNd)
    {
        const SwAttrSet & aNdSet = pSttContentNd->GetSwAttrSet();
        if (const SvxFrameDirectionItem* pItem = aNdSet.GetItemIfSet( RES_FRAMEDIR ))
        {
            pTableFormat->SetFormatAttr( *pItem );
        }
    }

    //Resolves: tdf#87977, tdf#78599, disable broadcasting modifications
    //until after RegisterToFormat is completed
    bool bEnableSetModified = getIDocumentState().IsEnableSetModified();
    getIDocumentState().SetEnableSetModified(false);

    SwTableNode* pTableNd = GetNodes().TextToTable(
            rTableNodes, pTableFormat, pLineFormat, pBoxFormat );

    SwTable& rNdTable = pTableNd->GetTable();
    rNdTable.RegisterToFormat(*pTableFormat);

    if( !pBoxFormat->HasWriterListeners() )
    {
        // The Box's Formats already have the right size, we must only set
        // the right Border/AutoFormat.
        pTableFormat->SetFormatAttr( pBoxFormat->GetFrameSize() );
        delete pBoxFormat;
    }

    SwNodeOffset nIdx = pTableNd->GetIndex();
    aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 );

    getIDocumentState().SetEnableSetModified(bEnableSetModified);
    getIDocumentState().SetModified();
    getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) );
    return &rNdTable;
}

void SwNodes::ExpandRangeForTableBox(const SwNodeRange & rRange, std::optional<SwNodeRange>& rExpandedRange)
{
    bool bChanged = false;

    SwNodeIndex aNewStart = rRange.aStart;
    SwNodeIndex aNewEnd = rRange.aEnd;

    SwNodeIndex aEndIndex = rRange.aEnd;
    SwNodeIndex aIndex = rRange.aStart;

    while (aIndex < aEndIndex)
    {
        SwNode& rNode = aIndex.GetNode();

        if (rNode.IsStartNode())
        {
            // advance aIndex to the end node of this start node
            SwNode * pEndNode = rNode.EndOfSectionNode();
            aIndex = *pEndNode;

            if (aIndex > aNewEnd)
            {
                aNewEnd = aIndex;
                bChanged = true;
            }
        }
        else if (rNode.IsEndNode())
        {
            SwNode * pStartNode = rNode.StartOfSectionNode();
            if (pStartNode->GetIndex() < aNewStart.GetIndex())
            {
                aNewStart = *pStartNode;
                bChanged = true;
            }
        }

        if (aIndex < aEndIndex)
            ++aIndex;
    }

    SwNode * pNode = &aIndex.GetNode();
    while (pNode->IsEndNode() && aIndex < Count() - 1)
    {
        SwNode * pStartNode = pNode->StartOfSectionNode();
        aNewStart = *pStartNode;
        aNewEnd = aIndex;
        bChanged = true;

        ++aIndex;
        pNode = &aIndex.GetNode();
    }

    if (bChanged)
        rExpandedRange.emplace(aNewStart, aNewEnd);
}

static void
lcl_SetTableBoxWidths2(SwTable & rTable, size_t const nMaxBoxes,
        SwTableBoxFormat & rBoxFormat, SwDoc & rDoc)
{
    // rhbz#820283, fdo#55462: set default box widths so table width is covered
    SwTableLines & rLines = rTable.GetTabLines();
    for (size_t nTmpLine = 0; nTmpLine < rLines.size(); ++nTmpLine)
    {
        SwTableBoxes & rBoxes = rLines[nTmpLine]->GetTabBoxes();
        assert(!rBoxes.empty()); // ensured by convertToTable
        size_t const nMissing = nMaxBoxes - rBoxes.size();
        if (nMissing)
        {
            // default width for box at the end of an incomplete line
            SwTableBoxFormat *const pNewFormat = rDoc.MakeTableBoxFormat();
            size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX;
            pNewFormat->SetFormatAttr( SwFormatFrameSize(SwFrameSize::Variable,
                        nWidth * (nMissing + 1)) );
            pNewFormat->Add(rBoxes.back());
        }
    }
    size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX;
    // default width for all boxes not at the end of an incomplete line
    rBoxFormat.SetFormatAttr(SwFormatFrameSize(SwFrameSize::Variable, nWidth));
}

SwTableNode* SwNodes::TextToTable( const SwNodes::TableRanges_t & rTableNodes,
                                    SwTableFormat* pTableFormat,
                                    SwTableLineFormat* pLineFormat,
                                    SwTableBoxFormat* pBoxFormat  )
{
    if( rTableNodes.empty() )
        return nullptr;

    SwTableNode * pTableNd = new SwTableNode( rTableNodes.begin()->begin()->aStart.GetNode() );
    //insert the end node after the last text node
    SwNodeIndex aInsertIndex( rTableNodes.rbegin()->rbegin()->aEnd );
    ++aInsertIndex;

    //!! ownership will be transferred in c-tor to SwNodes array.
    //!! Thus no real problem here...
    new SwEndNode( aInsertIndex.GetNode(), *pTableNd );

    SwDoc& rDoc = GetDoc();
    SwTable& rTable = pTableNd->GetTable();
    SwTableBox* pBox;
    sal_uInt16 nLines, nMaxBoxes = 0;

    SwNodeIndex aNodeIndex = rTableNodes.begin()->begin()->aStart;
    // delete frames of all contained content nodes
    for( nLines = 0; aNodeIndex <= rTableNodes.rbegin()->rbegin()->aEnd; ++aNodeIndex,++nLines )
    {
        SwNode& rNode = aNodeIndex.GetNode();
        if( rNode.IsContentNode() )
        {
            lcl_RemoveBreaks(static_cast<SwContentNode&>(rNode),
                    (0 == nLines) ? pTableFormat : nullptr);
        }
    }

    nLines = 0;
    for( const auto& rRow : rTableNodes )
    {
        sal_uInt16 nBoxes = 0;
        SwTableLine* pLine = new SwTableLine( pLineFormat, 1, nullptr );
        rTable.GetTabLines().insert(rTable.GetTabLines().begin() + nLines, pLine);

        for( const auto& rCell : rRow )
        {
            SwNodeIndex aCellEndIdx(rCell.aEnd);
            ++aCellEndIdx;
            SwStartNode* pSttNd = new SwStartNode( rCell.aStart.GetNode(), SwNodeType::Start,
                                        SwTableBoxStartNode );

            // Quotation of http://nabble.documentfoundation.org/Some-strange-lines-by-taking-a-look-at-the-bt-of-fdo-51916-tp3994561p3994639.html
            // SwNode's constructor adds itself to the same SwNodes array as the other node (pSttNd).
            // So this statement is only executed for the side-effect.
            new SwEndNode( aCellEndIdx.GetNode(), *pSttNd );

            //set the start node on all node of the current cell
            SwNodeIndex aCellNodeIdx = rCell.aStart;
            for(;aCellNodeIdx <= rCell.aEnd; ++aCellNodeIdx )
            {
                aCellNodeIdx.GetNode().m_pStartOfSection = pSttNd;
                //skip start/end node pairs
                if( aCellNodeIdx.GetNode().IsStartNode() )
                    aCellNodeIdx.Assign(*aCellNodeIdx.GetNode().EndOfSectionNode());
            }

            // assign Section to the Box
            pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine );
            pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox );
        }
        if( nMaxBoxes < nBoxes )
            nMaxBoxes = nBoxes;

        nLines++;
    }

    lcl_SetTableBoxWidths2(rTable, nMaxBoxes, *pBoxFormat, rDoc);

    return pTableNd;
}

/**
 * Table to Text
 */
bool SwDoc::TableToText( const SwTableNode* pTableNd, sal_Unicode cCh )
{
    if( !pTableNd )
        return false;

    // #i34471#
    // If this is triggered by SwUndoTableToText::Repeat() nobody ever deleted
    // the table cursor.
    SwEditShell* pESh = GetEditShell();
    if (pESh && pESh->IsTableMode())
        pESh->ClearMark();

    SwNodeRange aRg( *pTableNd, SwNodeOffset(0), *pTableNd->EndOfSectionNode() );
    std::unique_ptr<SwUndoTableToText> pUndo;
    SwNodeRange* pUndoRg = nullptr;
    if (GetIDocumentUndoRedo().DoesUndo())
    {
        GetIDocumentUndoRedo().ClearRedo();
        pUndoRg = new SwNodeRange( aRg.aStart, SwNodeOffset(-1), aRg.aEnd, SwNodeOffset(+1) );
        pUndo.reset(new SwUndoTableToText( pTableNd->GetTable(), cCh ));
    }

    SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() );
    aMsgHint.m_eFlags = TBL_BOXNAME;
    getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint );

    bool bRet = GetNodes().TableToText( aRg, cCh, pUndo.get() );
    if( pUndoRg )
    {
        ++pUndoRg->aStart;
        --pUndoRg->aEnd;
        pUndo->SetRange( *pUndoRg );
        GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
        delete pUndoRg;
    }

    if( bRet )
        getIDocumentState().SetModified();

    return bRet;
}

namespace {

/**
 * Use the ForEach method from PtrArray to recreate Text from a Table.
 * The Boxes can also contain Lines!
 */
struct DelTabPara
{
    SwTextNode* pLastNd;
    SwNodes& rNds;
    SwUndoTableToText* pUndo;
    sal_Unicode cCh;

    DelTabPara( SwNodes& rNodes, sal_Unicode cChar, SwUndoTableToText* pU ) :
        pLastNd(nullptr), rNds( rNodes ), pUndo( pU ), cCh( cChar ) {}
};

}

// Forward declare so that the Lines and Boxes can use recursion
static void lcl_DelBox( SwTableBox* pBox, DelTabPara* pDelPara );

static void lcl_DelLine( SwTableLine* pLine, DelTabPara* pPara )
{
    assert(pPara && "The parameters are missing!");
    DelTabPara aPara( *pPara );
    for( auto& rpBox : pLine->GetTabBoxes() )
        lcl_DelBox(rpBox, &aPara );
    if( pLine->GetUpper() ) // Is there a parent Box?
        // Return the last TextNode
        pPara->pLastNd = aPara.pLastNd;
}

static void lcl_DelBox( SwTableBox* pBox, DelTabPara* pDelPara )
{
    assert(pDelPara && "The parameters are missing");

    // Delete the Box's Lines
    if( !pBox->GetTabLines().empty() )
    {
        for( SwTableLine* pLine : pBox->GetTabLines() )
            lcl_DelLine( pLine, pDelPara );
    }
    else
    {
        SwDoc& rDoc = pDelPara->rNds.GetDoc();
        SwNodeRange aDelRg( *pBox->GetSttNd(), SwNodeOffset(0),
                            *pBox->GetSttNd()->EndOfSectionNode() );
        // Delete the Section
        pDelPara->rNds.SectionUp( &aDelRg );
        const SwTextNode* pCurTextNd = nullptr;
        if (T2T_PARA != pDelPara->cCh && pDelPara->pLastNd)
            pCurTextNd = aDelRg.aStart.GetNode().GetTextNode();
        if (nullptr != pCurTextNd)
        {
            // Join the current text node with the last from the previous box if possible
            SwNodeOffset nNdIdx = aDelRg.aStart.GetIndex();
            --aDelRg.aStart;
            if( pDelPara->pLastNd == &aDelRg.aStart.GetNode() )
            {
                // Inserting the separator
                SwContentIndex aCntIdx( pDelPara->pLastNd,
                                 pDelPara->pLastNd->GetText().getLength());
                pDelPara->pLastNd->InsertText( OUString(pDelPara->cCh), aCntIdx,
                    SwInsertFlags::EMPTYEXPAND );
                if( pDelPara->pUndo )
                    pDelPara->pUndo->AddBoxPos( rDoc, nNdIdx, aDelRg.aEnd.GetIndex(),
                                                aCntIdx.GetIndex() );

                const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
                const sal_Int32 nOldTextLen = aCntIdx.GetIndex();
                pContentStore->Save(rDoc, nNdIdx, SAL_MAX_INT32);

                pDelPara->pLastNd->JoinNext();

                if( !pContentStore->Empty() )
                    pContentStore->Restore( rDoc, pDelPara->pLastNd->GetIndex(), nOldTextLen );
            }
            else if( pDelPara->pUndo )
            {
                ++aDelRg.aStart;
                pDelPara->pUndo->AddBoxPos( rDoc, nNdIdx, aDelRg.aEnd.GetIndex() );
            }
        }
        else if( pDelPara->pUndo )
            pDelPara->pUndo->AddBoxPos( rDoc, aDelRg.aStart.GetIndex(), aDelRg.aEnd.GetIndex() );
        --aDelRg.aEnd;
        pDelPara->pLastNd = aDelRg.aEnd.GetNode().GetTextNode();

        // Do not take over the NumberFormatting's adjustment
        if( pDelPara->pLastNd && pDelPara->pLastNd->HasSwAttrSet() )
            pDelPara->pLastNd->ResetAttr( RES_PARATR_ADJUST );
    }
}

bool SwNodes::TableToText( const SwNodeRange& rRange, sal_Unicode cCh,
                            SwUndoTableToText* pUndo )
{
    // Is a Table selected?
    if (rRange.aStart.GetIndex() >= rRange.aEnd.GetIndex())
        return false;
    SwTableNode *const pTableNd(rRange.aStart.GetNode().GetTableNode());
    if (nullptr == pTableNd ||
        &rRange.aEnd.GetNode() != pTableNd->EndOfSectionNode() )
        return false;

    // If the Table was alone in a Section, create the Frames via the Table's Upper
    std::optional<SwNode2LayoutSaveUpperFrames> oNode2Layout;
    SwNode* pFrameNd = FindPrvNxtFrameNode( rRange.aStart.GetNode(), &rRange.aEnd.GetNode() );
    SwNodeIndex aFrameIdx( pFrameNd ? *pFrameNd: rRange.aStart.GetNode() );
    if( !pFrameNd )
        // Collect all Uppers
        oNode2Layout.emplace(*pTableNd);

    // Delete the Frames
    pTableNd->DelFrames();

    // "Delete" the Table and merge all Lines/Boxes
    DelTabPara aDelPara( *this, cCh, pUndo );
    for( SwTableLine *pLine : pTableNd->m_pTable->GetTabLines() )
        lcl_DelLine( pLine, &aDelPara );

    // We just created a TextNode with fitting separator for every TableLine.
    // Now we only need to delete the TableSection and create the Frames for the
    // new TextNode.
    SwNodeRange aDelRg( rRange.aStart, rRange.aEnd );

    // If the Table has PageDesc/Break Attributes, carry them over to the
    // first Text Node
    {
        // What about UNDO?
        const SfxItemSet& rTableSet = pTableNd->m_pTable->GetFrameFormat()->GetAttrSet();
        const SvxFormatBreakItem* pBreak = rTableSet.GetItemIfSet( RES_BREAK, false );
        const SwFormatPageDesc* pDesc = rTableSet.GetItemIfSet( RES_PAGEDESC, false );

        if( pBreak || pDesc )
        {
            SwNodeIndex aIdx( *pTableNd  );
            SwContentNode* pCNd = GoNext( &aIdx );
            if( pBreak )
                pCNd->SetAttr( *pBreak );
            if( pDesc )
                pCNd->SetAttr( *pDesc );
        }
    }

    SectionUp( &aDelRg ); // Delete this Section and by that the Table
    // #i28006#
    SwNodeOffset nStt = aDelRg.aStart.GetIndex(), nEnd = aDelRg.aEnd.GetIndex();
    if( !pFrameNd )
    {
        oNode2Layout->RestoreUpperFrames( *this,
                        aDelRg.aStart.GetIndex(), aDelRg.aEnd.GetIndex() );
        oNode2Layout.reset();
    }
    else
    {
        SwContentNode *pCNd;
        SwSectionNode *pSNd;
        while( aDelRg.aStart.GetIndex() < nEnd )
        {
            pCNd = aDelRg.aStart.GetNode().GetContentNode();
            if( nullptr != pCNd )
            {
                if( pFrameNd->IsContentNode() )
                    static_cast<SwContentNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(*pCNd);
                else if( pFrameNd->IsTableNode() )
                    static_cast<SwTableNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aDelRg.aStart);
                else if( pFrameNd->IsSectionNode() )
                    static_cast<SwSectionNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aDelRg.aStart);
                pFrameNd = pCNd;
            }
            else
            {
                pSNd = aDelRg.aStart.GetNode().GetSectionNode();
                if( pSNd )
                {
                    if( !pSNd->GetSection().IsHidden() && !pSNd->IsContentHidden() )
                    {
                        pSNd->MakeOwnFrames(&aFrameIdx, &aDelRg.aEnd);
                        break;
                    }
                    aDelRg.aStart = *pSNd->EndOfSectionNode();
                }
            }
            ++aDelRg.aStart;
        }
    }

    // #i28006# Fly frames have to be restored even if the table was
    // #alone in the section
    const SwFrameFormats& rFlyArr = *GetDoc().GetSpzFrameFormats();
    for( auto pFly : rFlyArr )
    {
        SwFrameFormat *const pFormat = pFly;
        const SwFormatAnchor& rAnchor = pFormat->GetAnchor();
        SwNode const*const pAnchorNode = rAnchor.GetAnchorNode();
        if (pAnchorNode &&
            ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) ||
             (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId())) &&
            nStt <= pAnchorNode->GetIndex() &&
            pAnchorNode->GetIndex() < nEnd )
        {
            pFormat->MakeFrames();
        }
    }

    return true;
}

/**
 * Inserting Columns/Rows
 */
void SwDoc::InsertCol( const SwCursor& rCursor, sal_uInt16 nCnt, bool bBehind )
{
    if( !::CheckSplitCells( rCursor, nCnt + 1, SwTableSearchType::Col ) )
        return;

    // Find the Boxes via the Layout
    SwSelBoxes aBoxes;
    ::GetTableSel( rCursor, aBoxes, SwTableSearchType::Col );

    if( !aBoxes.empty() )
        InsertCol( aBoxes, nCnt, bBehind );
}

bool SwDoc::InsertCol( const SwSelBoxes& rBoxes, sal_uInt16 nCnt, bool bBehind )
{
    OSL_ENSURE( !rBoxes.empty(), "No valid Box list" );
    SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode());
    if( !pTableNd )
        return false;

    SwTable& rTable = pTableNd->GetTable();
    if( dynamic_cast<const SwDDETable*>( &rTable) !=  nullptr)
        return false;

    SwTableSortBoxes aTmpLst;
    std::unique_ptr<SwUndoTableNdsChg> pUndo;
    if (GetIDocumentUndoRedo().DoesUndo())
    {
        pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_INSCOL, rBoxes, *pTableNd,
                                     0, 0, nCnt, bBehind, false ));
        aTmpLst.insert( rTable.GetTabSortBoxes() );
    }

    bool bRet(false);
    {
        ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo());

        SwTableFormulaUpdate aMsgHint( &rTable );
        aMsgHint.m_eFlags = TBL_BOXPTR;
        getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint );

        bRet = rTable.InsertCol(*this, rBoxes, nCnt, bBehind);
        if (bRet)
        {
            getIDocumentState().SetModified();
            ::ClearFEShellTabCols(*this, nullptr);
            getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) );
        }
    }

    if( pUndo && bRet )
    {
        pUndo->SaveNewBoxes( *pTableNd, aTmpLst );
        GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
    }
    return bRet;
}

void SwDoc::InsertRow( const SwCursor& rCursor, sal_uInt16 nCnt, bool bBehind )
{
    // Find the Boxes via the Layout
    SwSelBoxes aBoxes;
    GetTableSel( rCursor, aBoxes, SwTableSearchType::Row );

    if( !aBoxes.empty() )
        InsertRow( aBoxes, nCnt, bBehind );
}

bool SwDoc::InsertRow( const SwSelBoxes& rBoxes, sal_uInt16 nCnt, bool bBehind )
{
    OSL_ENSURE( !rBoxes.empty(), "No valid Box list" );
    SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode());
    if( !pTableNd )
        return false;

    SwTable& rTable = pTableNd->GetTable();
    if( dynamic_cast<const SwDDETable*>( &rTable) !=  nullptr)
        return false;

    SwTableSortBoxes aTmpLst;
    std::unique_ptr<SwUndoTableNdsChg> pUndo;
    if (GetIDocumentUndoRedo().DoesUndo())
    {
        pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_INSROW,rBoxes, *pTableNd,
                                     0, 0, nCnt, bBehind, false ));
        aTmpLst.insert( rTable.GetTabSortBoxes() );
    }

    bool bRet(false);
    {
        ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo());

        SwTableFormulaUpdate aMsgHint( &rTable );
        aMsgHint.m_eFlags = TBL_BOXPTR;
        getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint );

        bRet = rTable.InsertRow( this, rBoxes, nCnt, bBehind );
        if (bRet)
        {
            getIDocumentState().SetModified();
            ::ClearFEShellTabCols(*this, nullptr);
            getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) );
        }
    }

    if( pUndo && bRet )
    {
        pUndo->SaveNewBoxes( *pTableNd, aTmpLst );
        GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
    }
    return bRet;

}

/**
 * Deleting Columns/Rows
 */
void SwDoc::DeleteRow( const SwCursor& rCursor )
{
    // Find the Boxes via the Layout
    SwSelBoxes aBoxes;
    GetTableSel( rCursor, aBoxes, SwTableSearchType::Row );
    if( ::HasProtectedCells( aBoxes ))
        return;

    // Remove the Cursor from the to-be-deleted Section.
    // The Cursor is placed after the table, except for
    //  - when there's another Line, we place it in that one
    //  - when a Line precedes it, we place it in that one
    {
        SwTableNode* pTableNd = rCursor.GetPointNode().FindTableNode();

        if(dynamic_cast<const SwDDETable*>( & pTableNd->GetTable()) !=  nullptr)
            return;

        // Find all Boxes/Lines
        FndBox_ aFndBox( nullptr, nullptr );
        {
            FndPara aPara( aBoxes, &aFndBox );
            ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara );
        }

        if( aFndBox.GetLines().empty() )
            return;

        if (SwEditShell* pESh = GetEditShell())
        {
            pESh->KillPams();
            // FIXME: actually we should be iterating over all Shells!
        }

        FndBox_* pFndBox = &aFndBox;
        while( 1 == pFndBox->GetLines().size() &&
                1 == pFndBox->GetLines().front()->GetBoxes().size() )
        {
            FndBox_ *const pTmp = pFndBox->GetLines().front()->GetBoxes()[0].get();
            if( pTmp->GetBox()->GetSttNd() )
                break; // Else it gets too far
            pFndBox = pTmp;
        }

        SwTableLine* pDelLine = pFndBox->GetLines().back()->GetLine();
        SwTableBox* pDelBox = pDelLine->GetTabBoxes().back();
        while( !pDelBox->GetSttNd() )
        {
            SwTableLine* pLn = pDelBox->GetTabLines()[
                        pDelBox->GetTabLines().size()-1 ];
            pDelBox = pLn->GetTabBoxes().back();
        }
        SwTableBox* pNextBox = pDelLine->FindNextBox( pTableNd->GetTable(),
                                                        pDelBox );
        while( pNextBox &&
                pNextBox->GetFrameFormat()->GetProtect().IsContentProtected() )
            pNextBox = pNextBox->FindNextBox( pTableNd->GetTable(), pNextBox );

        if( !pNextBox ) // No succeeding Boxes? Then take the preceding one
        {
            pDelLine = pFndBox->GetLines().front()->GetLine();
            pDelBox = pDelLine->GetTabBoxes()[ 0 ];
            while( !pDelBox->GetSttNd() )
                pDelBox = pDelBox->GetTabLines()[0]->GetTabBoxes()[0];
            pNextBox = pDelLine->FindPreviousBox( pTableNd->GetTable(),
                                                        pDelBox );
            while( pNextBox &&
                    pNextBox->GetFrameFormat()->GetProtect().IsContentProtected() )
                pNextBox = pNextBox->FindPreviousBox( pTableNd->GetTable(), pNextBox );
        }

        SwNodeOffset nIdx;
        if( pNextBox ) // Place the Cursor here
            nIdx = pNextBox->GetSttIdx() + 1;
        else // Else after the Table
            nIdx = pTableNd->EndOfSectionIndex() + 1;

        SwNodeIndex aIdx( GetNodes(), nIdx );
        SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
        if( !pCNd )
            pCNd = GetNodes().GoNext( &aIdx );

        if( pCNd )
        {
            // Change the Shell's Cursor or the one passed?
            SwPaM* pPam = const_cast<SwPaM*>(static_cast<SwPaM const *>(&rCursor));
            pPam->GetPoint()->Assign(aIdx);
            pPam->SetMark(); // Both want a part of it
            pPam->DeleteMark();
        }
    }

    // Thus delete the Rows
    GetIDocumentUndoRedo().StartUndo(SwUndoId::ROW_DELETE, nullptr);
    DeleteRowCol( aBoxes );
    GetIDocumentUndoRedo().EndUndo(SwUndoId::ROW_DELETE, nullptr);
}

void SwDoc::DeleteCol( const SwCursor& rCursor )
{
    // Find the Boxes via the Layout
    SwSelBoxes aBoxes;
    GetTableSel( rCursor, aBoxes, SwTableSearchType::Col );
    if( ::HasProtectedCells( aBoxes ))
        return;

    // The Cursors need to be removed from the to-be-deleted range.
    // Always place them after/on top of the Table; they are always set
    // to the old position via the document position.
    if (SwEditShell* pESh = GetEditShell())
    {
        const SwNode* pNd = rCursor.GetPointNode().FindTableBoxStartNode();
        pESh->ParkCursor( *pNd );
    }

    // Thus delete the Columns
    GetIDocumentUndoRedo().StartUndo(SwUndoId::COL_DELETE, nullptr);
    DeleteRowCol(aBoxes, SwDoc::RowColMode::DeleteColumn);
    GetIDocumentUndoRedo().EndUndo(SwUndoId::COL_DELETE, nullptr);
}

void SwDoc::DelTable(SwTableNode *const pTableNd)
{
    {
        // tdf#156267 remove DdeBookmarks before deleting nodes
        SwPaM aTmpPaM(*pTableNd, *pTableNd->EndOfSectionNode());
        SwDataChanged aTmp(aTmpPaM);
    }

    bool bNewTextNd = false;
    // Is it alone in a FlyFrame?
    SwNodeIndex aIdx( *pTableNd, -1 );
    const SwStartNode* pSttNd = aIdx.GetNode().GetStartNode();
    if (pSttNd)
    {
        const SwNodeOffset nTableEnd = pTableNd->EndOfSectionIndex() + 1;
        const SwNodeOffset nSectEnd = pSttNd->EndOfSectionIndex();
        if (nTableEnd == nSectEnd)
        {
            if (SwFlyStartNode == pSttNd->GetStartNodeType())
            {
                SwFrameFormat* pFormat = pSttNd->GetFlyFormat();
                if (pFormat)
                {
                    // That's the FlyFormat we're looking for
                    getIDocumentLayoutAccess().DelLayoutFormat( pFormat );
                    return;
                }
            }
            // No Fly? Thus Header or Footer: always leave a TextNode
            // We can forget about Undo then!
            bNewTextNd = true;
        }
    }

    // No Fly? Then it is a Header or Footer, so keep always a TextNode
    ++aIdx;
    if (GetIDocumentUndoRedo().DoesUndo())
    {
        GetIDocumentUndoRedo().ClearRedo();
        SwPaM aPaM( *pTableNd->EndOfSectionNode(), aIdx.GetNode() );

        if (bNewTextNd)
        {
            const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 );
            GetNodes().MakeTextNode( aTmpIdx.GetNode(),
                        getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) );
        }

        // Save the cursors (UNO and otherwise)
        SwPaM const* pSavePaM(nullptr);
        SwPaM forwardPaM(*pTableNd->EndOfSectionNode());
        if (forwardPaM.Move(fnMoveForward, GoInNode))
        {
            pSavePaM = &forwardPaM;
        }
        SwPaM backwardPaM(*pTableNd);
        if (backwardPaM.Move(fnMoveBackward, GoInNode))
        {
            if (pSavePaM == nullptr
                    // try to stay in the same outer table cell
                || (forwardPaM.GetPoint()->GetNode().FindTableNode() != pTableNd->StartOfSectionNode()->FindTableNode()
                    && forwardPaM.GetPoint()->GetNode().StartOfSectionIndex()
                        < backwardPaM.GetPoint()->GetNode().StartOfSectionIndex()))
            {
                pSavePaM = &backwardPaM;
            }
        }
        assert(pSavePaM); // due to bNewTextNd this must succeed
        {
            SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode());
            ::PaMCorrAbs(tmpPaM, *pSavePaM->GetPoint());
        }

        // Move hard PageBreaks to the succeeding Node
        bool bSavePageBreak = false, bSavePageDesc = false;
        SwNodeOffset nNextNd = pTableNd->EndOfSectionIndex()+1;
        SwContentNode* pNextNd = GetNodes()[ nNextNd ]->GetContentNode();
        if (pNextNd)
        {
            SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat();
            const SfxPoolItem *pItem;
            if (SfxItemState::SET == pTableFormat->GetItemState(RES_PAGEDESC,
                    false, &pItem))
            {
                pNextNd->SetAttr( *pItem );
                bSavePageDesc = true;
            }

            if (SfxItemState::SET == pTableFormat->GetItemState(RES_BREAK,
                    false, &pItem))
            {
                pNextNd->SetAttr( *pItem );
                bSavePageBreak = true;
            }
        }
        std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aPaM, SwDeleteFlags::Default));
        if (bNewTextNd)
            pUndo->SetTableDelLastNd();
        pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc );
        pUndo->SetTableName(pTableNd->GetTable().GetFrameFormat()->GetName());
        GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
    }
    else
    {
        if (bNewTextNd)
        {
            const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 );
            GetNodes().MakeTextNode( aTmpIdx.GetNode(),
                        getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) );
        }

        // Save the cursors (UNO and otherwise)
        SwPaM const* pSavePaM(nullptr);
        SwPaM forwardPaM(*pTableNd->EndOfSectionNode());
        if (forwardPaM.Move(fnMoveForward, GoInNode))
        {
            pSavePaM = &forwardPaM;
        }
        SwPaM backwardPaM(*pTableNd);
        if (backwardPaM.Move(fnMoveBackward, GoInNode))
        {
            if (pSavePaM == nullptr
                    // try to stay in the same outer table cell
                || (forwardPaM.GetPoint()->GetNode().FindTableNode() != pTableNd->StartOfSectionNode()->FindTableNode()
                    && forwardPaM.GetPoint()->GetNode().StartOfSectionIndex()
                        < backwardPaM.GetPoint()->GetNode().StartOfSectionIndex()))
            {
                pSavePaM = &backwardPaM;
            }
        }
        assert(pSavePaM); // due to bNewTextNd this must succeed
        {
            SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode());
            ::PaMCorrAbs(tmpPaM, *pSavePaM->GetPoint());
        }

        // Move hard PageBreaks to the succeeding Node
        SwContentNode* pNextNd = GetNodes()[ pTableNd->EndOfSectionIndex()+1 ]->GetContentNode();
        if (pNextNd)
        {
            SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat();
            const SfxPoolItem *pItem;
            if (SfxItemState::SET == pTableFormat->GetItemState(RES_PAGEDESC,
                    false, &pItem))
            {
                pNextNd->SetAttr( *pItem );
            }

            if (SfxItemState::SET == pTableFormat->GetItemState(RES_BREAK,
                    false, &pItem))
            {
                pNextNd->SetAttr( *pItem );
            }
        }

        pTableNd->DelFrames();
        getIDocumentContentOperations().DeleteSection( pTableNd );
    }

    if (SwFEShell* pFEShell = GetDocShell()->GetFEShell())
        pFEShell->UpdateTableStyleFormatting();

    getIDocumentState().SetModified();
    getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) );
}

bool SwDoc::DeleteRowCol(const SwSelBoxes& rBoxes, RowColMode const eMode)
{
    if (!(eMode & SwDoc::RowColMode::DeleteProtected)
        && ::HasProtectedCells(rBoxes))
    {
        return false;
    }

    OSL_ENSURE( !rBoxes.empty(), "No valid Box list" );
    SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode());
    if( !pTableNd )
        return false;

    if (!(eMode & SwDoc::RowColMode::DeleteProtected)
        && dynamic_cast<const SwDDETable*>(&pTableNd->GetTable()) != nullptr)
    {
        return false;
    }

    ::ClearFEShellTabCols(*this, nullptr);
    SwSelBoxes aSelBoxes( rBoxes );
    SwTable &rTable = pTableNd->GetTable();
    tools::Long nMin = 0;
    tools::Long nMax = 0;
    if( rTable.IsNewModel() )
    {
        if (eMode & SwDoc::RowColMode::DeleteColumn)
            rTable.ExpandColumnSelection( aSelBoxes, nMin, nMax );
        else
            rTable.FindSuperfluousRows( aSelBoxes );
    }

    // Are we deleting the whole Table?
    const SwNodeOffset nTmpIdx1 = pTableNd->GetIndex();
    const SwNodeOffset nTmpIdx2 = aSelBoxes.back()->GetSttNd()->EndOfSectionIndex() + 1;
    if( pTableNd->GetTable().GetTabSortBoxes().size() == aSelBoxes.size() &&
        aSelBoxes[0]->GetSttIdx()-1 == nTmpIdx1 &&
        nTmpIdx2 == pTableNd->EndOfSectionIndex() )
    {
        DelTable(pTableNd);
        return true;
    }

    std::unique_ptr<SwUndoTableNdsChg> pUndo;
    if (GetIDocumentUndoRedo().DoesUndo())
    {
        pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_DELBOX, aSelBoxes, *pTableNd,
                                     nMin, nMax, 0, false, false ));
    }

    bool bRet(false);
    {
        ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo());

        SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() );
        aMsgHint.m_eFlags = TBL_BOXPTR;
        getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint );

        if (rTable.IsNewModel())
        {
            if (eMode & SwDoc::RowColMode::DeleteColumn)
                rTable.PrepareDeleteCol( nMin, nMax );
            rTable.FindSuperfluousRows( aSelBoxes );
            if (pUndo)
                pUndo->ReNewBoxes( aSelBoxes );
        }
        bRet = rTable.DeleteSel( this, aSelBoxes, nullptr, pUndo.get(), true, true );
        if (bRet)
        {
            if (SwFEShell* pFEShell = GetDocShell()->GetFEShell())
                pFEShell->UpdateTableStyleFormatting();

            getIDocumentState().SetModified();
            getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) );
        }
    }

    if( pUndo && bRet )
    {
        GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
    }

    return bRet;
}

/**
 * Split up/merge Boxes in the Table
 */
bool SwDoc::SplitTable( const SwSelBoxes& rBoxes, bool bVert, sal_uInt16 nCnt,
                      bool bSameHeight )
{
    OSL_ENSURE( !rBoxes.empty() && nCnt, "No valid Box list" );
    SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode());
    if( !pTableNd )
        return false;

    SwTable& rTable = pTableNd->GetTable();
    if( dynamic_cast<const SwDDETable*>( &rTable) !=  nullptr)
        return false;

    std::vector<SwNodeOffset> aNdsCnts;
    SwTableSortBoxes aTmpLst;
    std::unique_ptr<SwUndoTableNdsChg> pUndo;
    if (GetIDocumentUndoRedo().DoesUndo())
    {
        pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_SPLIT, rBoxes, *pTableNd, 0, 0,
                                     nCnt, bVert, bSameHeight ));

        aTmpLst.insert( rTable.GetTabSortBoxes() );
        if( !bVert )
        {
            for (size_t n = 0; n < rBoxes.size(); ++n)
            {
                const SwStartNode* pSttNd = rBoxes[ n ]->GetSttNd();
                aNdsCnts.push_back( pSttNd->EndOfSectionIndex() -
                                    pSttNd->GetIndex() );
            }
        }
    }

    bool bRet(false);
    {
        ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo());

        SwTableFormulaUpdate aMsgHint( &rTable );
        aMsgHint.m_eFlags = TBL_BOXPTR;
        getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint );

        if (bVert)
            bRet = rTable.SplitCol(*this, rBoxes, nCnt);
        else
            bRet = rTable.SplitRow(*this, rBoxes, nCnt, bSameHeight);

        if (bRet)
        {
            if (SwFEShell* pFEShell = GetDocShell()->GetFEShell())
                pFEShell->UpdateTableStyleFormatting();

            getIDocumentState().SetModified();
            getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) );
        }
    }

    if( pUndo && bRet )
    {
        if( bVert )
            pUndo->SaveNewBoxes( *pTableNd, aTmpLst );
        else
            pUndo->SaveNewBoxes( *pTableNd, aTmpLst, rBoxes, aNdsCnts );
        GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
    }

    return bRet;
}

TableMergeErr SwDoc::MergeTable( SwPaM& rPam )
{
    // Check if the current cursor's Point/Mark are inside a Table
    SwTableNode* pTableNd = rPam.GetPointNode().FindTableNode();
    if( !pTableNd )
        return TableMergeErr::NoSelection;
    SwTable& rTable = pTableNd->GetTable();
    if( dynamic_cast<const SwDDETable*>( &rTable) !=  nullptr )
        return TableMergeErr::NoSelection;
    TableMergeErr nRet = TableMergeErr::NoSelection;
    if( !rTable.IsNewModel() )
    {
        nRet =::CheckMergeSel( rPam );
        if( TableMergeErr::Ok != nRet )
            return nRet;
        nRet = TableMergeErr::NoSelection;
    }

    // #i33394#
    GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_MERGE, nullptr );

    RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags();
    getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);

    std::unique_ptr<SwUndoTableMerge> pUndo;
    if (GetIDocumentUndoRedo().DoesUndo())
        pUndo.reset(new SwUndoTableMerge( rPam ));

    // Find the Boxes via the Layout
    SwSelBoxes aBoxes;
    SwSelBoxes aMerged;
    SwTableBox* pMergeBox;

    if( !rTable.PrepareMerge( rPam, aBoxes, aMerged, &pMergeBox, pUndo.get() ) )
    {   // No cells found to merge
        getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
        if( pUndo )
        {
            pUndo.reset();
            SwUndoId nLastUndoId(SwUndoId::EMPTY);
            if (GetIDocumentUndoRedo().GetLastUndoInfo(nullptr, & nLastUndoId)
                && (SwUndoId::REDLINE == nLastUndoId))
            {
                // FIXME: why is this horrible cleanup necessary?
                SwUndoRedline *const pU = dynamic_cast<SwUndoRedline*>(
                        GetUndoManager().RemoveLastUndo());
                if (pU && pU->GetRedlSaveCount())
                {
                    SwEditShell *const pEditShell(GetEditShell());
                    assert(pEditShell);
                    ::sw::UndoRedoContext context(*this, *pEditShell);
                    static_cast<SfxUndoAction *>(pU)->UndoWithContext(context);
                }
                delete pU;
            }
        }
    }
    else
    {
        // The PaMs need to be removed from the to-be-deleted range. Thus always place
        // them at the end of/on top of the Table; it's always set to the old position via
        // the Document Position.
        // For a start remember an index for the temporary position, because we cannot
        // access it after GetMergeSel
        {
            rPam.DeleteMark();
            rPam.GetPoint()->Assign(*pMergeBox->GetSttNd());
            rPam.SetMark();
            rPam.DeleteMark();

            SwPaM* pTmp = &rPam;
            while( &rPam != ( pTmp = pTmp->GetNext() ))
                for( int i = 0; i < 2; ++i )
                    pTmp->GetBound( static_cast<bool>(i) ) = *rPam.GetPoint();

            if (SwTableCursor* pTableCursor = dynamic_cast<SwTableCursor*>(&rPam))
            {
                // tdf#135098 update selection so rPam's m_SelectedBoxes is updated
                // to not contain the soon to-be-deleted SwTableBox so if the rPam
                // is queried via a11y it doesn't claim the deleted cell still
                // exists
                pTableCursor->NewTableSelection();
            }
        }

        // Merge them
        SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() );
        aMsgHint.m_eFlags = TBL_BOXPTR;
        getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint );

        if( pTableNd->GetTable().Merge( this, aBoxes, aMerged, pMergeBox, pUndo.get() ))
        {
            nRet = TableMergeErr::Ok;

            getIDocumentState().SetModified();
            getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) );
            if( pUndo )
            {
                GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
            }
        }

        rPam.GetPoint()->Assign( *pMergeBox->GetSttNd() );
        rPam.Move();

        ::ClearFEShellTabCols(*this, nullptr);
        getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
    }
    GetIDocumentUndoRedo().EndUndo( SwUndoId::TABLE_MERGE, nullptr );
    return nRet;
}

SwTableNode::SwTableNode( const SwNode& rWhere )
    : SwStartNode( rWhere, SwNodeType::Table )
{
    m_pTable.reset(new SwTable);
}

SwTableNode::~SwTableNode()
{
    // Notify UNO wrappers
    GetTable().GetFrameFormat()->GetNotifier().Broadcast(SfxHint(SfxHintId::Dying));
    DelFrames();
    m_pTable->SetTableNode(this); // set this so that ~SwDDETable can read it!
    m_pTable.reset();
}

SwTabFrame *SwTableNode::MakeFrame( SwFrame* pSib )
{
    return new SwTabFrame( *m_pTable, pSib );
}

/**
 * Creates all Views from the Document for the preceding Node. The resulting ContentFrames
 * are added to the corresponding Layout.
 */
void SwTableNode::MakeFramesForAdjacentContentNode(const SwNodeIndex & rIdx)
{
    if( !GetTable().GetFrameFormat()->HasWriterListeners()) // Do we actually have Frame?
        return;

    SwFrame *pFrame;
    SwContentNode * pNode = rIdx.GetNode().GetContentNode();

    OSL_ENSURE( pNode, "No ContentNode or CopyNode and new Node is identical");

    bool bBefore = rIdx < GetIndex();

    SwNode2Layout aNode2Layout( *this, rIdx.GetIndex() );

    while( nullptr != (pFrame = aNode2Layout.NextFrame()) )
    {
        if ( ( pFrame->getRootFrame()->HasMergedParas() &&
                                !pNode->IsCreateFrameWhenHidingRedlines() ) ||
              // tdf#153819 table deletion with change tracking:
              // table node without frames in Hide Changes mode
              !pFrame->GetUpper() )
        {
            continue;
        }
        SwFrame *pNew = pNode->MakeFrame( pFrame );
        // Will the Node receive Frames before or after?
        if ( bBefore )
            // The new one precedes me
            pNew->Paste( pFrame->GetUpper(), pFrame );
        else
            // The new one succeeds me
            pNew->Paste( pFrame->GetUpper(), pFrame->GetNext() );
    }
}

/**
 * Create a TableFrame for every Shell and insert before the corresponding ContentFrame.
 */
void SwTableNode::MakeOwnFrames(SwPosition* pIdxBehind)
{
    SwNode *pNd = GetNodes().FindPrvNxtFrameNode( *this, EndOfSectionNode() );
    if( !pNd )
    {
        if (pIdxBehind)
            pIdxBehind->Assign(*this);
        return;
    }
    if (pIdxBehind)
        pIdxBehind->Assign(*pNd);

    SwFrame *pFrame( nullptr );
    SwLayoutFrame *pUpper( nullptr );
    SwNode2Layout aNode2Layout( *pNd, GetIndex() );
    while( nullptr != (pUpper = aNode2Layout.UpperFrame( pFrame, *this )) )
    {
        if (pUpper->getRootFrame()->HasMergedParas()
            && !IsCreateFrameWhenHidingRedlines())
        {
            continue;
        }
        SwTabFrame* pNew = MakeFrame( pUpper );
        pNew->Paste( pUpper, pFrame );
        // #i27138#
        // notify accessibility paragraphs objects about changed
        // CONTENT_FLOWS_FROM/_TO relation.
        // Relation CONTENT_FLOWS_FROM for next paragraph will change
        // and relation CONTENT_FLOWS_TO for previous paragraph will change.
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
        {
            SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() );
            if ( pViewShell && pViewShell->GetLayout() &&
                 pViewShell->GetLayout()->IsAnyShellAccessible() )
            {
                auto pNext = pNew->FindNextCnt( true );
                auto pPrev = pNew->FindPrevCnt();
                pViewShell->InvalidateAccessibleParaFlowRelation(
                            pNext ? pNext->DynCastTextFrame() : nullptr,
                            pPrev ? pPrev->DynCastTextFrame() : nullptr );
            }
        }
#endif
        pNew->RegistFlys();
    }
}

void SwTableNode::DelFrames(SwRootFrame const*const pLayout)
{
    /* For a start, cut out and delete the TabFrames (which will also delete the Columns and Rows)
       The TabFrames are attached to the FrameFormat of the SwTable.
       We need to delete them in a more cumbersome way, for the Master to also delete the Follows. */

    SwIterator<SwTabFrame,SwFormat> aIter( *(m_pTable->GetFrameFormat()) );
    SwTabFrame *pFrame = aIter.First();
    while ( pFrame )
    {
        bool bAgain = false;
        {
            if (!pFrame->IsFollow() && (!pLayout || pLayout == pFrame->getRootFrame()))
            {
                while ( pFrame->HasFollow() )
                    pFrame->JoinAndDelFollows();
                // #i27138#
                // notify accessibility paragraphs objects about changed
                // CONTENT_FLOWS_FROM/_TO relation.
                // Relation CONTENT_FLOWS_FROM for current next paragraph will change
                // and relation CONTENT_FLOWS_TO for current previous paragraph will change.
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
                if (!GetDoc().IsInDtor())
                {
                    SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() );
                    if ( pViewShell && pViewShell->GetLayout() &&
                         pViewShell->GetLayout()->IsAnyShellAccessible() )
                    {
                        auto pNext = pFrame->FindNextCnt( true );
                        auto pPrev = pFrame->FindPrevCnt();
                        pViewShell->InvalidateAccessibleParaFlowRelation(
                            pNext ? pNext->DynCastTextFrame() : nullptr,
                            pPrev ? pPrev->DynCastTextFrame() : nullptr );
                    }
                }
#endif
                if (pFrame->GetUpper())
                    pFrame->Cut();
                SwFrame::DestroyFrame(pFrame);
                bAgain = true;
            }
        }
        pFrame = bAgain ? aIter.First() : aIter.Next();
    }
}

void SwTableNode::SetNewTable( std::unique_ptr<SwTable> pNewTable, bool bNewFrames )
{
    DelFrames();
    m_pTable->SetTableNode(this);
    m_pTable = std::move(pNewTable);
    if( bNewFrames )
    {
        MakeOwnFrames();
    }
}

void SwTableNode::RemoveRedlines()
{
    SwDoc& rDoc = GetDoc();
    SwTable& rTable = GetTable();
    rDoc.getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteAllTableRedlines(rDoc, rTable, true, RedlineType::Any);
}

void SwTableNode::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTableNode"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(sal_Int32(GetIndex())).getStr()));

    if (m_pTable)
    {
        (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTable"));
        (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", m_pTable.get());
        m_pTable->GetFrameFormat()->dumpAsXml(pWriter);
        for (const auto& pLine : m_pTable->GetTabLines())
        {
            (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTableLine"));
            (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", pLine);
            pLine->GetFrameFormat()->dumpAsXml(pWriter);
            (void)xmlTextWriterEndElement(pWriter);
        }
        (void)xmlTextWriterEndElement(pWriter);
    }

    // (void)xmlTextWriterEndElement(pWriter); - it is a start node, so don't end, will make xml better nested
}

void SwDoc::GetTabCols( SwTabCols &rFill, const SwCellFrame* pBoxFrame )
{
    OSL_ENSURE( pBoxFrame, "pBoxFrame needs to be specified!" );
    if( !pBoxFrame )
        return;

    SwTabFrame *pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame();
    const SwTableBox* pBox = pBoxFrame->GetTabBox();

    // Set fixed points, LeftMin in Document coordinates, all others relative
    SwRectFnSet aRectFnSet(pTab);
    const SwPageFrame* pPage = pTab->FindPageFrame();
    const sal_uLong nLeftMin = aRectFnSet.GetLeft(pTab->getFrameArea()) -
                           aRectFnSet.GetLeft(pPage->getFrameArea());
    const sal_uLong nRightMax = aRectFnSet.GetRight(pTab->getFrameArea()) -
                            aRectFnSet.GetLeft(pPage->getFrameArea());

    rFill.SetLeftMin ( nLeftMin );
    rFill.SetLeft    ( aRectFnSet.GetLeft(pTab->getFramePrintArea()) );
    rFill.SetRight   ( aRectFnSet.GetRight(pTab->getFramePrintArea()));
    rFill.SetRightMax( nRightMax - nLeftMin );

    pTab->GetTable()->GetTabCols( rFill, pBox );
}

// Here are some little helpers used in SwDoc::GetTabRows

#define ROWFUZZY 25

namespace {

struct FuzzyCompare
{
    bool operator() ( tools::Long s1, tools::Long s2 ) const;
};

}

bool FuzzyCompare::operator() ( tools::Long s1, tools::Long s2 ) const
{
    return ( s1 < s2 && std::abs( s1 - s2 ) > ROWFUZZY );
}

static bool lcl_IsFrameInColumn( const SwCellFrame& rFrame, SwSelBoxes const & rBoxes )
{
    for (size_t i = 0; i < rBoxes.size(); ++i)
    {
        if ( rFrame.GetTabBox() == rBoxes[ i ] )
            return true;
    }

    return false;
}

void SwDoc::GetTabRows( SwTabCols &rFill, const SwCellFrame* pBoxFrame )
{
    OSL_ENSURE( pBoxFrame, "GetTabRows called without pBoxFrame" );

    // Make code robust:
    if ( !pBoxFrame )
        return;

    // #i39552# Collection of the boxes of the current
    // column has to be done at the beginning of this function, because
    // the table may be formatted in ::GetTableSel.
    SwDeletionChecker aDelCheck( pBoxFrame );

    SwSelBoxes aBoxes;
    const SwContentFrame* pContent = ::GetCellContent( *pBoxFrame );
    if ( pContent && pContent->IsTextFrame() )
    {
        const SwPosition aPos(*static_cast<const SwTextFrame*>(pContent)->GetTextNodeFirst());
        const SwCursor aTmpCursor( aPos, nullptr );
        ::GetTableSel( aTmpCursor, aBoxes, SwTableSearchType::Col );
    }

    // Make code robust:
    if ( aDelCheck.HasBeenDeleted() )
    {
        OSL_FAIL( "Current box has been deleted during GetTabRows()" );
        return;
    }

    // Make code robust:
    const SwTabFrame* pTab = pBoxFrame->FindTabFrame();
    OSL_ENSURE( pTab, "GetTabRows called without a table" );
    if ( !pTab )
        return;

    const SwFrame* pFrame = pTab->GetNextLayoutLeaf();

    // Set fixed points, LeftMin in Document coordinates, all others relative
    SwRectFnSet aRectFnSet(pTab);
    const SwPageFrame* pPage = pTab->FindPageFrame();
    const tools::Long nLeftMin  = ( aRectFnSet.IsVert() ?
                             pTab->GetPrtLeft() - pPage->getFrameArea().Left() :
                             pTab->GetPrtTop() - pPage->getFrameArea().Top() );
    const tools::Long nLeft     = aRectFnSet.IsVert() ? LONG_MAX : 0;
    const tools::Long nRight    = aRectFnSet.GetHeight(pTab->getFramePrintArea());
    const tools::Long nRightMax = aRectFnSet.IsVert() ? nRight : LONG_MAX;

    rFill.SetLeftMin( nLeftMin );
    rFill.SetLeft( nLeft );
    rFill.SetRight( nRight );
    rFill.SetRightMax( nRightMax );

    typedef std::map< tools::Long, std::pair< tools::Long, long >, FuzzyCompare > BoundaryMap;
    BoundaryMap aBoundaries;
    BoundaryMap::iterator aIter;
    std::pair< tools::Long, long > aPair;

    typedef std::map< tools::Long, bool > HiddenMap;
    HiddenMap aHidden;
    HiddenMap::iterator aHiddenIter;

    while ( pFrame && pTab->IsAnLower( pFrame ) )
    {
        if ( pFrame->IsCellFrame() && pFrame->FindTabFrame() == pTab )
        {
            // upper and lower borders of current cell frame:
            tools::Long nUpperBorder = aRectFnSet.GetTop(pFrame->getFrameArea());
            tools::Long nLowerBorder = aRectFnSet.GetBottom(pFrame->getFrameArea());

            // get boundaries for nUpperBorder:
            aIter = aBoundaries.find( nUpperBorder );
            if ( aIter == aBoundaries.end() )
            {
                aPair.first = nUpperBorder; aPair.second = LONG_MAX;
                aBoundaries[ nUpperBorder ] = aPair;
            }

            // get boundaries for nLowerBorder:
            aIter = aBoundaries.find( nLowerBorder );
            if ( aIter == aBoundaries.end() )
            {
                aPair.first = nUpperBorder; aPair.second = LONG_MAX;
            }
            else
            {
                nLowerBorder = (*aIter).first;
                tools::Long nNewLowerBorderUpperBoundary = std::max( (*aIter).second.first, nUpperBorder );
                aPair.first = nNewLowerBorderUpperBoundary; aPair.second = LONG_MAX;
            }
            aBoundaries[ nLowerBorder ] = aPair;

            // calculate hidden flags for entry nUpperBorder/nLowerBorder:
            tools::Long nTmpVal = nUpperBorder;
            for ( sal_uInt8 i = 0; i < 2; ++i )
            {
                aHiddenIter = aHidden.find( nTmpVal );
                if ( aHiddenIter == aHidden.end() )
                    aHidden[ nTmpVal ] = !lcl_IsFrameInColumn( *static_cast<const SwCellFrame*>(pFrame), aBoxes );
                else
                {
                    if ( aHidden[ nTmpVal ] &&
                         lcl_IsFrameInColumn( *static_cast<const SwCellFrame*>(pFrame), aBoxes ) )
                        aHidden[ nTmpVal ] = false;
                }
                nTmpVal = nLowerBorder;
            }
        }

        pFrame = pFrame->GetNextLayoutLeaf();
    }

    // transfer calculated values from BoundaryMap and HiddenMap into rFill:
    size_t nIdx = 0;
    for ( const auto& rEntry : aBoundaries )
    {
        const tools::Long nTabTop = aRectFnSet.GetPrtTop(*pTab);
        const tools::Long nKey = aRectFnSet.YDiff( rEntry.first, nTabTop );
        const std::pair< tools::Long, long > aTmpPair = rEntry.second;
        const tools::Long nFirst = aRectFnSet.YDiff( aTmpPair.first, nTabTop );
        const tools::Long nSecond = aTmpPair.second;

        aHiddenIter = aHidden.find( rEntry.first );
        const bool bHidden = aHiddenIter != aHidden.end() && (*aHiddenIter).second;
        rFill.Insert( nKey, nFirst, nSecond, bHidden, nIdx++ );
    }

    // delete first and last entry
    OSL_ENSURE( rFill.Count(), "Deleting from empty vector. Fasten your seatbelts!" );
    // #i60818# There may be only one entry in rFill. Make
    // code robust by checking count of rFill.
    if ( rFill.Count() ) rFill.Remove( 0 );
    if ( rFill.Count() ) rFill.Remove( rFill.Count() - 1 );
    rFill.SetLastRowAllowedToChange( !pTab->HasFollowFlowLine() );
}

void SwDoc::SetTabCols( const SwTabCols &rNew, bool bCurRowOnly,
                        const SwCellFrame* pBoxFrame )
{
    const SwTableBox* pBox = nullptr;
    SwTabFrame *pTab = nullptr;

    if( pBoxFrame )
    {
        pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame();
        pBox = pBoxFrame->GetTabBox();
    }
    else
    {
        OSL_ENSURE( false, "must specify pBoxFrame" );
        return ;
    }

    // If the Table is still using relative values (USHRT_MAX)
    // we need to switch to absolute ones.
    SwTable& rTab = *pTab->GetTable();
    const SwFormatFrameSize& rTableFrameSz = rTab.GetFrameFormat()->GetFrameSize();
    SwRectFnSet aRectFnSet(pTab);
    // #i17174# - With fix for #i9040# the shadow size is taken
    // from the table width. Thus, add its left and right size to current table
    // printing area width in order to get the correct table size attribute.
    SwTwips nPrtWidth = aRectFnSet.GetWidth(pTab->getFramePrintArea());
    {
        SvxShadowItem aShadow( rTab.GetFrameFormat()->GetShadow() );
        nPrtWidth += aShadow.CalcShadowSpace( SvxShadowItemSide::LEFT ) +
                     aShadow.CalcShadowSpace( SvxShadowItemSide::RIGHT );
    }
    if( nPrtWidth != rTableFrameSz.GetWidth() )
    {
        SwFormatFrameSize aSz( rTableFrameSz );
        aSz.SetWidth( nPrtWidth );
        rTab.GetFrameFormat()->SetFormatAttr( aSz );
    }

    SwTabCols aOld( rNew.Count() );

    const SwPageFrame* pPage = pTab->FindPageFrame();
    const sal_uLong nLeftMin = aRectFnSet.GetLeft(pTab->getFrameArea()) -
                           aRectFnSet.GetLeft(pPage->getFrameArea());
    const sal_uLong nRightMax = aRectFnSet.GetRight(pTab->getFrameArea()) -
                            aRectFnSet.GetLeft(pPage->getFrameArea());

    // Set fixed points, LeftMin in Document coordinates, all others relative
    aOld.SetLeftMin ( nLeftMin );
    aOld.SetLeft    ( aRectFnSet.GetLeft(pTab->getFramePrintArea()) );
    aOld.SetRight   ( aRectFnSet.GetRight(pTab->getFramePrintArea()));
    aOld.SetRightMax( nRightMax - nLeftMin );

    rTab.GetTabCols( aOld, pBox );
    SetTabCols(rTab, rNew, aOld, pBox, bCurRowOnly );
}

void SwDoc::SetTabRows( const SwTabCols &rNew, bool bCurColOnly,
                        const SwCellFrame* pBoxFrame )
{
    SwTabFrame *pTab = nullptr;

    if( pBoxFrame )
    {
        pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame();
    }
    else
    {
        OSL_ENSURE( false, "must specify pBoxFrame" );
        return ;
    }

    // If the Table is still using relative values (USHRT_MAX)
    // we need to switch to absolute ones.
    SwRectFnSet aRectFnSet(pTab);
    SwTabCols aOld( rNew.Count() );

    // Set fixed points, LeftMin in Document coordinates, all others relative
    const SwPageFrame* pPage = pTab->FindPageFrame();

    aOld.SetRight( aRectFnSet.GetHeight(pTab->getFramePrintArea()) );
    tools::Long nLeftMin;
    if ( aRectFnSet.IsVert() )
    {
        nLeftMin = pTab->GetPrtLeft() - pPage->getFrameArea().Left();
        aOld.SetLeft    ( LONG_MAX );
        aOld.SetRightMax( aOld.GetRight() );

    }
    else
    {
        nLeftMin = pTab->GetPrtTop() - pPage->getFrameArea().Top();
        aOld.SetLeft    ( 0 );
        aOld.SetRightMax( LONG_MAX );
    }
    aOld.SetLeftMin ( nLeftMin );

    GetTabRows( aOld, pBoxFrame );

    GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_ATTR, nullptr );

    // check for differences between aOld and rNew:
    const size_t nCount = rNew.Count();
    const SwTable* pTable = pTab->GetTable();
    OSL_ENSURE( pTable, "My colleague told me, this couldn't happen" );

    for ( size_t i = 0; i <= nCount; ++i )
    {
        const size_t nIdxStt = aRectFnSet.IsVert() ? nCount - i : i - 1;
        const size_t nIdxEnd = aRectFnSet.IsVert() ? nCount - i - 1 : i;

        const tools::Long nOldRowStart = i == 0  ? 0 : aOld[ nIdxStt ];
        const tools::Long nOldRowEnd =   i == nCount ? aOld.GetRight() : aOld[ nIdxEnd ];
        const tools::Long nOldRowHeight = nOldRowEnd - nOldRowStart;

        const tools::Long nNewRowStart = i == 0  ? 0 : rNew[ nIdxStt ];
        const tools::Long nNewRowEnd =   i == nCount ? rNew.GetRight() : rNew[ nIdxEnd ];
        const tools::Long nNewRowHeight = nNewRowEnd - nNewRowStart;

        const tools::Long nDiff = nNewRowHeight - nOldRowHeight;
        if ( std::abs( nDiff ) >= ROWFUZZY )
        {
            // For the old table model pTextFrame and pLine will be set for every box.
            // For the new table model pTextFrame will be set if the box is not covered,
            // but the pLine will be set if the box is not an overlapping box
            // In the new table model the row height can be adjusted,
            // when both variables are set.
            const SwTextFrame* pTextFrame = nullptr;
            const SwTableLine* pLine = nullptr;

            // Iterate over all SwCellFrames with Bottom = nOldPos
            const SwFrame* pFrame = pTab->GetNextLayoutLeaf();
            while ( pFrame && pTab->IsAnLower( pFrame ) )
            {
                if ( pFrame->IsCellFrame() && pFrame->FindTabFrame() == pTab )
                {
                    const tools::Long nLowerBorder = aRectFnSet.GetBottom(pFrame->getFrameArea());
                    const sal_uLong nTabTop = aRectFnSet.GetPrtTop(*pTab);
                    if ( std::abs( aRectFnSet.YInc( nTabTop, nOldRowEnd ) - nLowerBorder ) <= ROWFUZZY )
                    {
                        if ( !bCurColOnly || pFrame == pBoxFrame )
                        {
                            const SwFrame* pContent = ::GetCellContent( static_cast<const SwCellFrame&>(*pFrame) );

                            if ( pContent && pContent->IsTextFrame() )
                            {
                                const SwTableBox* pBox = static_cast<const SwCellFrame*>(pFrame)->GetTabBox();
                                const sal_Int32 nRowSpan = pBox->getRowSpan();
                                if( nRowSpan > 0 ) // Not overlapped
                                    pTextFrame = static_cast<const SwTextFrame*>(pContent);
                                if( nRowSpan < 2 ) // Not overlapping for row height
                                    pLine = pBox->GetUpper();
                                if( pLine && pTextFrame ) // always for old table model
                                {
                                    // The new row height must not to be calculated from an overlapping box
                                    SwFormatFrameSize aNew( pLine->GetFrameFormat()->GetFrameSize() );
                                    const tools::Long nNewSize = aRectFnSet.GetHeight(pFrame->getFrameArea()) + nDiff;
                                    if( nNewSize != aNew.GetHeight() )
                                    {
                                        aNew.SetHeight( nNewSize );
                                        if ( SwFrameSize::Variable == aNew.GetHeightSizeType() )
                                            aNew.SetHeightSizeType( SwFrameSize::Minimum );
                                        // This position must not be in an overlapped box
                                        const SwPosition aPos(*static_cast<const SwTextFrame*>(pContent)->GetTextNodeFirst());
                                        const SwCursor aTmpCursor( aPos, nullptr );
                                        SetRowHeight( aTmpCursor, aNew );
                                        // For the new table model we're done, for the old one
                                        // there might be another (sub)row to adjust...
                                        if( pTable->IsNewModel() )
                                            break;
                                    }
                                    pLine = nullptr;
                                }
                            }
                        }
                    }
                }
                pFrame = pFrame->GetNextLayoutLeaf();
            }
        }
    }

    GetIDocumentUndoRedo().EndUndo( SwUndoId::TABLE_ATTR, nullptr );

    ::ClearFEShellTabCols(*this, nullptr);
}

/**
 * Direct access for UNO
 */
void SwDoc::SetTabCols(SwTable& rTab, const SwTabCols &rNew, const SwTabCols &rOld,
                                const SwTableBox *pStart, bool bCurRowOnly )
{
    if (GetIDocumentUndoRedo().DoesUndo())
    {
        GetIDocumentUndoRedo().AppendUndo(
            std::make_unique<SwUndoAttrTable>( *rTab.GetTableNode(), true ));
    }
    rTab.SetTabCols( rNew, rOld, pStart, bCurRowOnly );
    ::ClearFEShellTabCols(*this, nullptr);
    getIDocumentState().SetModified();
}

void SwDoc::SetRowsToRepeat( SwTable &rTable, sal_uInt16 nSet )
{
    if( nSet == rTable.GetRowsToRepeat() )
        return;

    if (GetIDocumentUndoRedo().DoesUndo())
    {
        GetIDocumentUndoRedo().AppendUndo(
            std::make_unique<SwUndoTableHeadline>(rTable, rTable.GetRowsToRepeat(), nSet) );
    }

    rTable.SetRowsToRepeat(nSet);
    const SwMsgPoolItem aChg(RES_TBLHEADLINECHG);
    rTable.GetFrameFormat()->CallSwClientNotify(sw::LegacyModifyHint(&aChg, &aChg));
    getIDocumentState().SetModified();
}

void SwCollectTableLineBoxes::AddToUndoHistory( const SwContentNode& rNd )
{
    if( m_pHistory )
        m_pHistory->Add( rNd.GetFormatColl(), rNd.GetIndex(), SwNodeType::Text );
}

void SwCollectTableLineBoxes::AddBox( const SwTableBox& rBox )
{
    m_aPositionArr.push_back(m_nWidth);
    SwTableBox* p = const_cast<SwTableBox*>(&rBox);
    m_Boxes.push_back(p);
    m_nWidth = m_nWidth + o3tl::narrowing<sal_uInt16>(rBox.GetFrameFormat()->GetFrameSize().GetWidth());
}

const SwTableBox* SwCollectTableLineBoxes::GetBoxOfPos( const SwTableBox& rBox )
{
    const SwTableBox* pRet = nullptr;

    if( !m_aPositionArr.empty() )
    {
        std::vector<sal_uInt16>::size_type n;
        for( n = 0; n < m_aPositionArr.size(); ++n )
            if( m_aPositionArr[ n ] == m_nWidth )
                break;
            else if( m_aPositionArr[ n ] > m_nWidth )
            {
                if( n )
                    --n;
                break;
            }

        if( n >= m_aPositionArr.size() )
            --n;

        m_nWidth = m_nWidth + o3tl::narrowing<sal_uInt16>(rBox.GetFrameFormat()->GetFrameSize().GetWidth());
        pRet = m_Boxes[ n ];
    }
    return pRet;
}

bool SwCollectTableLineBoxes::Resize( sal_uInt16 nOffset, sal_uInt16 nOldWidth )
{
    if( !m_aPositionArr.empty() )
    {
        std::vector<sal_uInt16>::size_type n;
        for( n = 0; n < m_aPositionArr.size(); ++n )
        {
            if( m_aPositionArr[ n ] == nOffset )
                break;
            else if( m_aPositionArr[ n ] > nOffset )
            {
                if( n )
                    --n;
                break;
            }
        }

        m_aPositionArr.erase( m_aPositionArr.begin(), m_aPositionArr.begin() + n );
        m_Boxes.erase(m_Boxes.begin(), m_Boxes.begin() + n);

        size_t nArrSize = m_aPositionArr.size();
        if (nArrSize)
        {
            if (nOldWidth == 0)
                throw o3tl::divide_by_zero();

            // Adapt the positions to the new Size
            for( n = 0; n < nArrSize; ++n )
            {
                sal_uLong nSize = m_nWidth;
                nSize *= ( m_aPositionArr[ n ] - nOffset );
                nSize /= nOldWidth;
                m_aPositionArr[ n ] = sal_uInt16( nSize );
            }
        }
    }
    return !m_aPositionArr.empty();
}

bool sw_Line_CollectBox( const SwTableLine*& rpLine, void* pPara )
{
    SwCollectTableLineBoxes* pSplPara = static_cast<SwCollectTableLineBoxes*>(pPara);
    if( pSplPara->IsGetValues() )
        for( const auto& rpBox : const_cast<SwTableLine*>(rpLine)->GetTabBoxes() )
            sw_Box_CollectBox(rpBox, pSplPara );
    else
        for( auto& rpBox : const_cast<SwTableLine*>(rpLine)->GetTabBoxes() )
            sw_BoxSetSplitBoxFormats(rpBox, pSplPara );
    return true;
}

void sw_Box_CollectBox( const SwTableBox* pBox, SwCollectTableLineBoxes* pSplPara )
{
    auto nLen = pBox->GetTabLines().size();
    if( nLen )
    {
        // Continue with the actual Line
        if( pSplPara->IsGetFromTop() )
            nLen = 0;
        else
            --nLen;

        const SwTableLine* pLn = pBox->GetTabLines()[ nLen ];
        sw_Line_CollectBox( pLn, pSplPara );
    }
    else
        pSplPara->AddBox( *pBox );
}

void sw_BoxSetSplitBoxFormats( SwTableBox* pBox, SwCollectTableLineBoxes* pSplPara )
{
    auto nLen = pBox->GetTabLines().size();
    if( nLen )
    {
        // Continue with the actual Line
        if( pSplPara->IsGetFromTop() )
            nLen = 0;
        else
            --nLen;

        const SwTableLine* pLn = pBox->GetTabLines()[ nLen ];
        sw_Line_CollectBox( pLn, pSplPara );
    }
    else
    {
        const SwTableBox* pSrcBox = pSplPara->GetBoxOfPos( *pBox );
        SwFrameFormat* pFormat = pSrcBox->GetFrameFormat();

        if( SplitTable_HeadlineOption::BorderCopy == pSplPara->GetMode() )
        {
            const SvxBoxItem& rBoxItem = pBox->GetFrameFormat()->GetBox();
            if( !rBoxItem.GetTop() )
            {
                SvxBoxItem aNew( rBoxItem );
                aNew.SetLine( pFormat->GetBox().GetBottom(), SvxBoxItemLine::TOP );
                if( aNew != rBoxItem )
                    pBox->ClaimFrameFormat()->SetFormatAttr( aNew );
            }
        }
        else
        {
            SfxItemSetFixed<RES_LR_SPACE,    RES_UL_SPACE,
                           RES_PROTECT,     RES_PROTECT,
                           RES_VERT_ORIENT, RES_VERT_ORIENT,
                           RES_BACKGROUND,  RES_SHADOW>
                aTmpSet( pFormat->GetDoc()->GetAttrPool() );
            aTmpSet.Put( pFormat->GetAttrSet() );
            if( aTmpSet.Count() )
                pBox->ClaimFrameFormat()->SetFormatAttr( aTmpSet );

            if( SplitTable_HeadlineOption::BoxAttrAllCopy == pSplPara->GetMode() )
            {
                SwNodeIndex aIdx( *pSrcBox->GetSttNd(), 1 );
                SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
                if( !pCNd )
                    pCNd = aIdx.GetNodes().GoNext( &aIdx );
                aIdx = *pBox->GetSttNd();
                SwContentNode* pDNd = aIdx.GetNodes().GoNext( &aIdx );

                // If the Node is alone in the Section
                if( SwNodeOffset(2) == pDNd->EndOfSectionIndex() -
                        pDNd->StartOfSectionIndex() )
                {
                    pSplPara->AddToUndoHistory( *pDNd );
                    pDNd->ChgFormatColl( pCNd->GetFormatColl() );
                }
            }

            // note conditional template
            pBox->GetSttNd()->CheckSectionCondColl();
        }
    }
}

/**
 * Splits a Table in the top-level Line which contains the Index.
 * All succeeding top-level Lines go into a new Table/Node.
 *
 * @param bCalcNewSize true
 *                     Calculate the new Size for both from the
 *                     Boxes' Max; but only if Size is using absolute
 *                     values (USHRT_MAX)
 */
void SwDoc::SplitTable( const SwPosition& rPos, SplitTable_HeadlineOption eHdlnMode,
                        bool bCalcNewSize )
{
    SwNode* pNd = &rPos.GetNode();
    SwTableNode* pTNd = pNd->FindTableNode();
    if( !pTNd || pNd->IsTableNode() )
        return;

    if( dynamic_cast<const SwDDETable*>( &pTNd->GetTable() ) !=  nullptr)
        return;

    SwTable& rTable = pTNd->GetTable();
    rTable.SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout

    SwTableFormulaUpdate aMsgHint( &rTable );

    SwHistory aHistory;
    if (GetIDocumentUndoRedo().DoesUndo())
    {
        aMsgHint.m_pHistory = &aHistory;
    }

    {
        SwNodeOffset nSttIdx = pNd->FindTableBoxStartNode()->GetIndex();

        // Find top-level Line
        SwTableBox* pBox = rTable.GetTableBox( nSttIdx );
        if( pBox )
        {
            SwTableLine* pLine = pBox->GetUpper();
            while( pLine->GetUpper() )
                pLine = pLine->GetUpper()->GetUpper();

            // pLine contains the top-level Line now
            aMsgHint.m_nSplitLine = rTable.GetTabLines().GetPos( pLine );
        }

        OUString sNewTableNm( GetUniqueTableName() );
        aMsgHint.m_aData.pNewTableNm = &sNewTableNm;
        aMsgHint.m_eFlags = TBL_SPLITTBL;
        getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint );
    }

    // Find Lines for the Layout update
    FndBox_ aFndBox( nullptr, nullptr );
    aFndBox.SetTableLines( rTable );
    aFndBox.DelFrames( rTable );

    SwTableNode* pNew = GetNodes().SplitTable( rPos.GetNode(), false, bCalcNewSize );

    if( pNew )
    {
        std::unique_ptr<SwSaveRowSpan> pSaveRowSp = pNew->GetTable().CleanUpTopRowSpan( rTable.GetTabLines().size() );
        SwUndoSplitTable* pUndo = nullptr;
        if (GetIDocumentUndoRedo().DoesUndo())
        {
            pUndo = new SwUndoSplitTable(
                        *pNew, std::move(pSaveRowSp), eHdlnMode, bCalcNewSize);
            GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo));
            if( aHistory.Count() )
                pUndo->SaveFormula( aHistory );
        }

        switch( eHdlnMode )
        {
        // Set the lower Border of the preceding Line to
        // the upper Border of the current one
        case SplitTable_HeadlineOption::BorderCopy:
            {
                SwCollectTableLineBoxes aPara( false, eHdlnMode );
                SwTableLine* pLn = rTable.GetTabLines()[
                            rTable.GetTabLines().size() - 1 ];
                for( const auto& rpBox : pLn->GetTabBoxes() )
                    sw_Box_CollectBox(rpBox, &aPara );

                aPara.SetValues( true );
                pLn = pNew->GetTable().GetTabLines()[ 0 ];
                for( auto& rpBox : pLn->GetTabBoxes() )
                    sw_BoxSetSplitBoxFormats(rpBox, &aPara );

                // Switch off repeating Header
                pNew->GetTable().SetRowsToRepeat( 0 );
            }
            break;

        // Take over the Attributes of the first Line to the new one
        case SplitTable_HeadlineOption::BoxAttrCopy:
        case SplitTable_HeadlineOption::BoxAttrAllCopy:
            {
                SwHistory* pHst = nullptr;
                if( SplitTable_HeadlineOption::BoxAttrAllCopy == eHdlnMode && pUndo )
                    pHst = pUndo->GetHistory();

                SwCollectTableLineBoxes aPara( true, eHdlnMode, pHst );
                SwTableLine* pLn = rTable.GetTabLines()[ 0 ];
                for( const auto& rpBox : pLn->GetTabBoxes() )
                    sw_Box_CollectBox(rpBox, &aPara );

                aPara.SetValues( true );
                pLn = pNew->GetTable().GetTabLines()[ 0 ];
                for( auto& rpBox : pLn->GetTabBoxes() )
                    sw_BoxSetSplitBoxFormats(rpBox, &aPara );
            }
            break;

        case SplitTable_HeadlineOption::ContentCopy:
            rTable.CopyHeadlineIntoTable( *pNew );
            if( pUndo )
                pUndo->SetTableNodeOffset( pNew->GetIndex() );
            break;

        case SplitTable_HeadlineOption::NONE:
            // Switch off repeating the Header
            pNew->GetTable().SetRowsToRepeat( 0 );
            break;
        }

        // And insert Frames
        pNew->MakeOwnFrames();

        // Insert a paragraph between the Table
        GetNodes().MakeTextNode( *pNew,
                                getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ) );
    }

    // Update Layout
    aFndBox.MakeFrames( rTable );

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

    // update table style formatting of both the tables
    if (SwFEShell* pFEShell = GetDocShell()->GetFEShell())
    {
        pFEShell->UpdateTableStyleFormatting(pTNd);
        pFEShell->UpdateTableStyleFormatting(pNew);
    }

    getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) );
}

static bool lcl_ChgTableSize( SwTable& rTable )
{
    // The Attribute must not be set via the Modify or else all Boxes are
    // set back to 0.
    // So lock the Format.
    SwFrameFormat* pFormat = rTable.GetFrameFormat();
    SwFormatFrameSize aTableMaxSz( pFormat->GetFrameSize() );

    if( USHRT_MAX == aTableMaxSz.GetWidth() )
        return false;

    bool bLocked = pFormat->IsModifyLocked();
    pFormat->LockModify();

    aTableMaxSz.SetWidth( 0 );

    SwTableLines& rLns = rTable.GetTabLines();
    for( auto pLn : rLns )
    {
        SwTwips nMaxLnWidth = 0;
        SwTableBoxes& rBoxes = pLn->GetTabBoxes();
        for( auto pBox : rBoxes )
            nMaxLnWidth += pBox->GetFrameFormat()->GetFrameSize().GetWidth();

        if( nMaxLnWidth > aTableMaxSz.GetWidth() )
            aTableMaxSz.SetWidth( nMaxLnWidth );
    }
    pFormat->SetFormatAttr( aTableMaxSz );
    if( !bLocked ) // Release the Lock if appropriate
        pFormat->UnlockModify();

    return true;
}

namespace {

class SplitTable_Para
{
    std::map<SwFrameFormat const*, SwFrameFormat*> m_aSrcDestMap;
    SwTableNode* m_pNewTableNode;
    SwTable& m_rOldTable;

public:
    SplitTable_Para(SwTableNode* pNew, SwTable& rOld)
        : m_pNewTableNode(pNew)
        , m_rOldTable(rOld)
    {}
    SwFrameFormat* GetDestFormat( SwFrameFormat* pSrcFormat ) const
    {
        auto it = m_aSrcDestMap.find(pSrcFormat);
        return it == m_aSrcDestMap.end() ? nullptr : it->second;
    }

    void InsertSrcDest( SwFrameFormat const * pSrcFormat, SwFrameFormat* pDestFormat )
            {
                m_aSrcDestMap[pSrcFormat] = pDestFormat;
            }

    void ChgBox( SwTableBox* pBox )
    {
        m_rOldTable.GetTabSortBoxes().erase(pBox);
        m_pNewTableNode->GetTable().GetTabSortBoxes().insert(pBox);
    }
};

}

static void lcl_SplitTable_CpyBox( SwTableBox* pBox, SplitTable_Para* pPara );

static void lcl_SplitTable_CpyLine( SwTableLine* pLn, SplitTable_Para* pPara )
{
    SwFrameFormat *pSrcFormat = pLn->GetFrameFormat();
    SwTableLineFormat* pDestFormat = static_cast<SwTableLineFormat*>( pPara->GetDestFormat( pSrcFormat ) );
    if( pDestFormat == nullptr )
    {
        pPara->InsertSrcDest( pSrcFormat, pLn->ClaimFrameFormat() );
    }
    else
        pLn->ChgFrameFormat( pDestFormat );

    for( auto& rpBox : pLn->GetTabBoxes() )
        lcl_SplitTable_CpyBox(rpBox, pPara );
}

static void lcl_SplitTable_CpyBox( SwTableBox* pBox, SplitTable_Para* pPara )
{
    SwFrameFormat *pSrcFormat = pBox->GetFrameFormat();
    SwTableBoxFormat* pDestFormat = static_cast<SwTableBoxFormat*>(pPara->GetDestFormat( pSrcFormat ));
    if( pDestFormat == nullptr )
    {
        pPara->InsertSrcDest( pSrcFormat, pBox->ClaimFrameFormat() );
    }
    else
        pBox->ChgFrameFormat( pDestFormat );

    if( pBox->GetSttNd() )
        pPara->ChgBox( pBox );
    else
        for( SwTableLine* pLine : pBox->GetTabLines() )
            lcl_SplitTable_CpyLine( pLine, pPara );
}

SwTableNode* SwNodes::SplitTable( SwNode& rPos, bool bAfter,
                                    bool bCalcNewSize )
{
    SwNode* pNd = &rPos;
    SwTableNode* pTNd = pNd->FindTableNode();
    if( !pTNd || pNd->IsTableNode() )
        return nullptr;

    SwNodeOffset nSttIdx = pNd->FindTableBoxStartNode()->GetIndex();

    // Find this Box/top-level line
    SwTable& rTable = pTNd->GetTable();
    SwTableBox* pBox = rTable.GetTableBox( nSttIdx );
    if( !pBox )
        return nullptr;

    SwTableLine* pLine = pBox->GetUpper();
    while( pLine->GetUpper() )
        pLine = pLine->GetUpper()->GetUpper();

    // pLine now contains the top-level line
    sal_uInt16 nLinePos = rTable.GetTabLines().GetPos( pLine );
    if( USHRT_MAX == nLinePos ||
        ( bAfter ? ++nLinePos >= rTable.GetTabLines().size() : !nLinePos ))
        return nullptr; // Not found or last Line!

    // Find the first Box of the succeeding Line
    SwTableLine* pNextLine = rTable.GetTabLines()[ nLinePos ];
    pBox = pNextLine->GetTabBoxes()[0];
    while( !pBox->GetSttNd() )
        pBox = pBox->GetTabLines()[0]->GetTabBoxes()[0];

    // Insert an EndNode and TableNode into the Nodes Array
    SwTableNode * pNewTableNd;
    {
        SwEndNode* pOldTableEndNd = pTNd->EndOfSectionNode()->GetEndNode();
        assert(pOldTableEndNd && "Where is the EndNode?");

        new SwEndNode( *pBox->GetSttNd(), *pTNd );
        pNewTableNd = new SwTableNode( *pBox->GetSttNd() );
        pNewTableNd->GetTable().SetTableModel( rTable.IsNewModel() );

        pOldTableEndNd->m_pStartOfSection = pNewTableNd;
        pNewTableNd->m_pEndOfSection = pOldTableEndNd;

        SwNode* pBoxNd = const_cast<SwStartNode*>(pBox->GetSttNd()->GetStartNode());
        do {
            OSL_ENSURE( pBoxNd->IsStartNode(), "This needs to be a StartNode!" );
            pBoxNd->m_pStartOfSection = pNewTableNd;
            pBoxNd = (*this)[ pBoxNd->EndOfSectionIndex() + 1 ];
        } while( pBoxNd != pOldTableEndNd );
    }

    {
        // Move the Lines
        SwTable& rNewTable = pNewTableNd->GetTable();
        rNewTable.GetTabLines().insert( rNewTable.GetTabLines().begin(),
                      rTable.GetTabLines().begin() + nLinePos, rTable.GetTabLines().end() );

        /* From the back (bottom right) to the front (top left) deregister all Boxes from the
           Chart Data Provider. The Modify event is triggered in the calling function.
         TL_CHART2: */
        SwChartDataProvider *pPCD = rTable.GetFrameFormat()->getIDocumentChartDataProviderAccess().GetChartDataProvider();
        if( pPCD )
        {
            for (SwTableLines::size_type k = nLinePos;  k < rTable.GetTabLines().size(); ++k)
            {
                const SwTableLines::size_type nLineIdx = (rTable.GetTabLines().size() - 1) - k + nLinePos;
                const SwTableBoxes::size_type nBoxCnt = rTable.GetTabLines()[ nLineIdx ]->GetTabBoxes().size();
                for (SwTableBoxes::size_type j = 0;  j < nBoxCnt;  ++j)
                {
                    const SwTableBoxes::size_type nIdx = nBoxCnt - 1 - j;
                    pPCD->DeleteBox( &rTable, *rTable.GetTabLines()[ nLineIdx ]->GetTabBoxes()[nIdx] );
                }
            }
        }

        // Delete
        sal_uInt16 nDeleted = rTable.GetTabLines().size() - nLinePos;
        rTable.GetTabLines().erase( rTable.GetTabLines().begin() + nLinePos, rTable.GetTabLines().end() );

        // Move the affected Boxes. Make the Formats unique and correct the StartNodes
        SplitTable_Para aPara( pNewTableNd, rTable );
        for( SwTableLine* pNewLine : rNewTable.GetTabLines() )
            lcl_SplitTable_CpyLine( pNewLine, &aPara );
        rTable.CleanUpBottomRowSpan( nDeleted );
    }

    {
        // Copy the Table FrameFormat
        SwFrameFormat* pOldTableFormat = rTable.GetFrameFormat();
        SwFrameFormat* pNewTableFormat = pOldTableFormat->GetDoc()->MakeTableFrameFormat(
                                pOldTableFormat->GetDoc()->GetUniqueTableName(),
                                pOldTableFormat->GetDoc()->GetDfltFrameFormat() );

        *pNewTableFormat = *pOldTableFormat;
        pNewTableNd->GetTable().RegisterToFormat( *pNewTableFormat );

        pNewTableNd->GetTable().SetTableStyleName(rTable.GetTableStyleName());

        // Calculate a new Size?
        // lcl_ChgTableSize: Only execute the second call if the first call was
        // successful, thus has an absolute Size
        if( bCalcNewSize && lcl_ChgTableSize( rTable ) )
            lcl_ChgTableSize( pNewTableNd->GetTable() );
    }

    // TL_CHART2: need to inform chart of probably changed cell names
    rTable.UpdateCharts();

    return pNewTableNd; // That's it!
}

/**
 * rPos needs to be in the Table that remains
 *
 * @param bWithPrev  merge the current Table with the preceding
 *                   or succeeding one
 */
bool SwDoc::MergeTable( const SwPosition& rPos, bool bWithPrev )
{
    SwTableNode* pTableNd = rPos.GetNode().FindTableNode(), *pDelTableNd;
    if( !pTableNd )
        return false;

    SwNodes& rNds = GetNodes();
    if( bWithPrev )
        pDelTableNd = rNds[ pTableNd->GetIndex() - 1 ]->FindTableNode();
    else
        pDelTableNd = rNds[ pTableNd->EndOfSectionIndex() + 1 ]->GetTableNode();
    if( !pDelTableNd )
        return false;

    if( dynamic_cast<const SwDDETable*>( &pTableNd->GetTable() ) !=  nullptr ||
        dynamic_cast<const SwDDETable*>( &pDelTableNd->GetTable() ) !=  nullptr)
        return false;

    // Delete HTML Layout
    pTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>());
    pDelTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>());

    // Both Tables are present; we can start
    SwUndoMergeTable* pUndo = nullptr;
    std::unique_ptr<SwHistory> pHistory;
    if (GetIDocumentUndoRedo().DoesUndo())
    {
        pUndo = new SwUndoMergeTable( *pTableNd, *pDelTableNd, bWithPrev );
        GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo));
        pHistory.reset(new SwHistory);
    }

    // Adapt all "TableFormulas"
    SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() );
    aMsgHint.m_aData.pDelTable = &pDelTableNd->GetTable();
    aMsgHint.m_eFlags = TBL_MERGETBL;
    aMsgHint.m_pHistory = pHistory.get();
    getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint );

    // The actual merge
    bool bRet = rNds.MergeTable( bWithPrev ? *pTableNd : *pDelTableNd, !bWithPrev );

    if( pHistory )
    {
        if( pHistory->Count() )
            pUndo->SaveFormula( *pHistory );
        pHistory.reset();
    }
    if( bRet )
    {
        if (SwFEShell* pFEShell = GetDocShell()->GetFEShell())
            pFEShell->UpdateTableStyleFormatting();

        getIDocumentState().SetModified();
        getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) );
    }
    return bRet;
}

bool SwNodes::MergeTable( SwNode& rPos, bool bWithPrev )
{
    SwTableNode* pDelTableNd = rPos.GetTableNode();
    OSL_ENSURE( pDelTableNd, "Where did the TableNode go?" );

    SwTableNode* pTableNd = (*this)[ rPos.GetIndex() - 1]->FindTableNode();
    OSL_ENSURE( pTableNd, "Where did the TableNode go?" );

    if( !pDelTableNd || !pTableNd )
        return false;

    pDelTableNd->DelFrames();

    SwTable& rDelTable = pDelTableNd->GetTable();
    SwTable& rTable = pTableNd->GetTable();

    // Find Lines for the Layout update
    FndBox_ aFndBox( nullptr, nullptr );
    aFndBox.SetTableLines( rTable );
    aFndBox.DelFrames( rTable );

    // TL_CHART2:
    // tell the charts about the table to be deleted and have them use their own data
    GetDoc().getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( &rDelTable );

    // Sync the TableFormat's Width
    {
        const SwFormatFrameSize& rTableSz = rTable.GetFrameFormat()->GetFrameSize();
        const SwFormatFrameSize& rDelTableSz = rDelTable.GetFrameFormat()->GetFrameSize();
        if( rTableSz != rDelTableSz )
        {
            // The needs correction
            if( bWithPrev )
                rDelTable.GetFrameFormat()->SetFormatAttr( rTableSz );
            else
                rTable.GetFrameFormat()->SetFormatAttr( rDelTableSz );
        }
    }

    if( !bWithPrev )
    {
        // Transfer all Attributes of the succeeding Table to the preceding one
        // We do this, because the succeeding one is deleted when deleting the Node
        rTable.SetRowsToRepeat( rDelTable.GetRowsToRepeat() );
        rTable.SetTableChgMode( rDelTable.GetTableChgMode() );

        rTable.GetFrameFormat()->LockModify();
        *rTable.GetFrameFormat() = *rDelTable.GetFrameFormat();
        // Also switch the Name
        rTable.GetFrameFormat()->SetFormatName( rDelTable.GetFrameFormat()->GetName() );
        rTable.GetFrameFormat()->UnlockModify();
    }

    // Move the Lines and Boxes
    SwTableLines::size_type nOldSize = rTable.GetTabLines().size();
    rTable.GetTabLines().insert( rTable.GetTabLines().begin() + nOldSize,
                               rDelTable.GetTabLines().begin(), rDelTable.GetTabLines().end() );
    rDelTable.GetTabLines().clear();

    rTable.GetTabSortBoxes().insert( rDelTable.GetTabSortBoxes() );
    rDelTable.GetTabSortBoxes().clear();

    // The preceding Table always remains, while the succeeding one is deleted
    SwEndNode* pTableEndNd = pDelTableNd->EndOfSectionNode();
    pTableNd->m_pEndOfSection = pTableEndNd;

    SwNodeIndex aIdx( *pDelTableNd, 1 );

    SwNode* pBoxNd = aIdx.GetNode().GetStartNode();
    do {
        OSL_ENSURE( pBoxNd->IsStartNode(), "This needs to be a StartNode!" );
        pBoxNd->m_pStartOfSection = pTableNd;
        pBoxNd = (*this)[ pBoxNd->EndOfSectionIndex() + 1 ];
    } while( pBoxNd != pTableEndNd );
    pBoxNd->m_pStartOfSection = pTableNd;

    aIdx -= SwNodeOffset(2);
    DelNodes( aIdx, SwNodeOffset(2) );

    // tweak the conditional styles at the first inserted Line
    const SwTableLine* pFirstLn = rTable.GetTabLines()[ nOldSize ];
    sw_LineSetHeadCondColl( pFirstLn );

    // Clean up the Borders
    if( nOldSize )
    {
        SwGCLineBorder aPara( rTable );
        aPara.nLinePos = --nOldSize;
        pFirstLn = rTable.GetTabLines()[ nOldSize ];
        sw_GC_Line_Border( pFirstLn, &aPara );
    }

    // Update Layout
    aFndBox.MakeFrames( rTable );

    return true;
}

namespace {

// Use the PtrArray's ForEach method
struct SetAFormatTabPara
{
    SwTableAutoFormat& rTableFormat;
    SwUndoTableAutoFormat* pUndo;
    sal_uInt16 nEndBox, nCurBox;
    sal_uInt8 nAFormatLine, nAFormatBox;
    bool bSingleRowTable;

    explicit SetAFormatTabPara( const SwTableAutoFormat& rNew )
        : rTableFormat( const_cast<SwTableAutoFormat&>(rNew) ), pUndo( nullptr ),
        nEndBox( 0 ), nCurBox( 0 ), nAFormatLine( 0 ), nAFormatBox( 0 ), bSingleRowTable(false)
    {}
};

}

// Forward declare so that the Lines and Boxes can use recursion
static bool lcl_SetAFormatBox(FndBox_ &, SetAFormatTabPara *pSetPara, bool bResetDirect);
static bool lcl_SetAFormatLine(FndLine_ &, SetAFormatTabPara *pPara, bool bResetDirect);

static bool lcl_SetAFormatLine(FndLine_ & rLine, SetAFormatTabPara *pPara, bool bResetDirect)
{
    for (auto const& it : rLine.GetBoxes())
    {
        lcl_SetAFormatBox(*it, pPara, bResetDirect);
    }
    return true;
}

static bool lcl_SetAFormatBox(FndBox_ & rBox, SetAFormatTabPara *pSetPara, bool bResetDirect)
{
    if (!rBox.GetUpper()->GetUpper()) // Box on first level?
    {
        if( !pSetPara->nCurBox )
            pSetPara->nAFormatBox = 0;
        else if( pSetPara->nCurBox == pSetPara->nEndBox )
            pSetPara->nAFormatBox = 3;
        else //Even column(1) or Odd column(2)
            pSetPara->nAFormatBox = static_cast<sal_uInt8>(1 + ((pSetPara->nCurBox-1) & 1));
    }

    if (rBox.GetBox()->GetSttNd())
    {
        SwTableBox* pSetBox = rBox.GetBox();
        if (!pSetBox->HasDirectFormatting() || bResetDirect)
        {
            if (bResetDirect)
                pSetBox->SetDirectFormatting(false);

            SwDoc* pDoc = pSetBox->GetFrameFormat()->GetDoc();
            SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1> aCharSet(pDoc->GetAttrPool());
            SfxItemSet aBoxSet(pDoc->GetAttrPool(), aTableBoxSetRange);
            sal_uInt8 nPos = pSetPara->nAFormatLine * 4 + pSetPara->nAFormatBox;
            const bool bSingleRowTable = pSetPara->bSingleRowTable;
            const bool bSingleColTable = pSetPara->nEndBox == 0;
            pSetPara->rTableFormat.UpdateToSet(nPos, bSingleRowTable, bSingleColTable, aCharSet, SwTableAutoFormatUpdateFlags::Char, nullptr);
            pSetPara->rTableFormat.UpdateToSet(nPos, bSingleRowTable, bSingleColTable, aBoxSet, SwTableAutoFormatUpdateFlags::Box, pDoc->GetNumberFormatter());

            if (aCharSet.Count())
            {
                SwNodeOffset nSttNd = pSetBox->GetSttIdx()+1;
                SwNodeOffset nEndNd = pSetBox->GetSttNd()->EndOfSectionIndex();
                for (; nSttNd < nEndNd; ++nSttNd)
                {
                    SwContentNode* pNd = pDoc->GetNodes()[ nSttNd ]->GetContentNode();
                    if (pNd)
                        pNd->SetAttr(aCharSet);
                }
            }

            if (aBoxSet.Count())
            {
                if (pSetPara->pUndo && SfxItemState::SET == aBoxSet.GetItemState(RES_BOXATR_FORMAT))
                    pSetPara->pUndo->SaveBoxContent( *pSetBox );

                pSetBox->ClaimFrameFormat()->SetFormatAttr(aBoxSet);
            }
        }
    }
    else
    {
        // Not sure how this situation can occur, but apparently we have some kind of table in table.
        // I am guessing at how to best handle singlerow in this situation.
        const bool bOrigSingleRowTable = pSetPara->bSingleRowTable;
        pSetPara->bSingleRowTable = rBox.GetLines().size() == 1;
        for (auto const& rpFndLine : rBox.GetLines())
        {
            lcl_SetAFormatLine(*rpFndLine, pSetPara, bResetDirect);
        }
        pSetPara->bSingleRowTable = bOrigSingleRowTable;
    }

    if (!rBox.GetUpper()->GetUpper()) // a BaseLine
        ++pSetPara->nCurBox;
    return true;
}

bool SwDoc::SetTableAutoFormat(const SwSelBoxes& rBoxes, const SwTableAutoFormat& rNew, bool bResetDirect, bool const isSetStyleName)
{
    OSL_ENSURE( !rBoxes.empty(), "No valid Box list" );
    SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode());
    if( !pTableNd )
        return false;

    // Find all Boxes/Lines
    FndBox_ aFndBox( nullptr, nullptr );
    {
        FndPara aPara( rBoxes, &aFndBox );
        ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara );
    }
    if( aFndBox.GetLines().empty() )
        return false;

    SwTable &table = pTableNd->GetTable();
    table.SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>());

    FndBox_* pFndBox = &aFndBox;
    while( 1 == pFndBox->GetLines().size() &&
            1 == pFndBox->GetLines().front()->GetBoxes().size())
    {
        pFndBox = pFndBox->GetLines().front()->GetBoxes()[0].get();
    }

    if( pFndBox->GetLines().empty() ) // One too far? (only one sel. Box)
        pFndBox = pFndBox->GetUpper()->GetUpper();

    // Disable Undo, but first store parameters
    SwUndoTableAutoFormat* pUndo = nullptr;
    bool const bUndo(GetIDocumentUndoRedo().DoesUndo());
    if (bUndo)
    {
        pUndo = new SwUndoTableAutoFormat( *pTableNd, rNew );
        GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo));
        GetIDocumentUndoRedo().DoUndo(false);
    }

    if (isSetStyleName)
    {   // tdf#98226 do this here where undo can record it
        pTableNd->GetTable().SetTableStyleName(rNew.GetName());
    }

    rNew.RestoreTableProperties(table);

    SetAFormatTabPara aPara( rNew );
    FndLines_t& rFLns = pFndBox->GetLines();
    aPara.bSingleRowTable = rFLns.size() == 1;

    for (FndLines_t::size_type n = 0; n < rFLns.size(); ++n)
    {
        FndLine_* pLine = rFLns[n].get();

        // Set Upper to 0 (thus simulate BaseLine)
        FndBox_* pSaveBox = pLine->GetUpper();
        pLine->SetUpper( nullptr );

        if( !n )
            aPara.nAFormatLine = 0;
        else if (static_cast<size_t>(n+1) == rFLns.size())
            aPara.nAFormatLine = 3;
        else
            aPara.nAFormatLine = static_cast<sal_uInt8>(1 + ((n-1) & 1 ));

        aPara.nAFormatBox = 0;
        aPara.nCurBox = 0;
        aPara.nEndBox = pLine->GetBoxes().size()-1;
        aPara.pUndo = pUndo;
        for (auto const& it : pLine->GetBoxes())
        {
            lcl_SetAFormatBox(*it, &aPara, bResetDirect);
        }

        pLine->SetUpper( pSaveBox );
    }

    if( pUndo )
    {
        GetIDocumentUndoRedo().DoUndo(bUndo);
    }

    getIDocumentState().SetModified();
    getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) );

    return true;
}

/**
 * Find out who has the Attributes
 */
bool SwDoc::GetTableAutoFormat( const SwSelBoxes& rBoxes, SwTableAutoFormat& rGet )
{
    OSL_ENSURE( !rBoxes.empty(), "No valid Box list" );
    SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode());
    if( !pTableNd )
        return false;

    // Find all Boxes/Lines
    FndBox_ aFndBox( nullptr, nullptr );
    {
        FndPara aPara( rBoxes, &aFndBox );
        ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara );
    }
    if( aFndBox.GetLines().empty() )
        return false;

    // Store table properties
    SwTable &table = pTableNd->GetTable();
    rGet.StoreTableProperties(table);

    FndBox_* pFndBox = &aFndBox;
    while( 1 == pFndBox->GetLines().size() &&
            1 == pFndBox->GetLines().front()->GetBoxes().size())
    {
        pFndBox = pFndBox->GetLines().front()->GetBoxes()[0].get();
    }

    if( pFndBox->GetLines().empty() ) // One too far? (only one sel. Box)
        pFndBox = pFndBox->GetUpper()->GetUpper();

    FndLines_t& rFLns = pFndBox->GetLines();

    sal_uInt16 aLnArr[4];
    aLnArr[0] = 0;
    aLnArr[1] = 1 < rFLns.size() ? 1 : 0;
    aLnArr[2] = 2 < rFLns.size() ? 2 : aLnArr[1];
    aLnArr[3] = rFLns.size() - 1;

    for( sal_uInt8 nLine = 0; nLine < 4; ++nLine )
    {
        FndLine_& rLine = *rFLns[ aLnArr[ nLine ] ];

        sal_uInt16 aBoxArr[4];
        aBoxArr[0] = 0;
        aBoxArr[1] = 1 < rLine.GetBoxes().size() ? 1 : 0;
        aBoxArr[2] = 2 < rLine.GetBoxes().size() ? 2 : aBoxArr[1];
        aBoxArr[3] = rLine.GetBoxes().size() - 1;

        for( sal_uInt8 nBox = 0; nBox < 4; ++nBox )
        {
            SwTableBox* pFBox = rLine.GetBoxes()[ aBoxArr[ nBox ] ]->GetBox();
            // Always apply to the first ones
            while( !pFBox->GetSttNd() )
                pFBox = pFBox->GetTabLines()[0]->GetTabBoxes()[0];

            sal_uInt8 nPos = nLine * 4 + nBox;
            SwNodeIndex aIdx( *pFBox->GetSttNd(), 1 );
            SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
            if( !pCNd )
                pCNd = GetNodes().GoNext( &aIdx );

            if( pCNd )
                rGet.UpdateFromSet( nPos, pCNd->GetSwAttrSet(),
                                    SwTableAutoFormatUpdateFlags::Char, nullptr );
            rGet.UpdateFromSet( nPos, pFBox->GetFrameFormat()->GetAttrSet(),
                                SwTableAutoFormatUpdateFlags::Box,
                                GetNumberFormatter() );
        }
    }

    return true;
}

SwTableAutoFormatTable& SwDoc::GetTableStyles()
{
    if (!m_pTableStyles)
    {
        m_pTableStyles.reset(new SwTableAutoFormatTable);
        m_pTableStyles->Load();
    }
    return *m_pTableStyles;
}

OUString SwDoc::GetUniqueTableName() const
{
    if( IsInMailMerge())
    {
        OUString newName = "MailMergeTable"
            + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US )
            + OUString::number( mpTableFrameFormatTable->size() + 1 );
        return newName;
    }

    const OUString aName(SwResId(STR_TABLE_DEFNAME));

    const size_t nFlagSize = ( mpTableFrameFormatTable->size() / 8 ) + 2;

    std::unique_ptr<sal_uInt8[]> pSetFlags( new sal_uInt8[ nFlagSize ] );
    memset( pSetFlags.get(), 0, nFlagSize );

    for( size_t n = 0; n < mpTableFrameFormatTable->size(); ++n )
    {
        const SwFrameFormat* pFormat = (*mpTableFrameFormatTable)[ n ];
        if( !pFormat->IsDefault() && IsUsed( *pFormat )  &&
            pFormat->GetName().startsWith( aName ) )
        {
            // Get number and set the Flag
            const sal_Int32 nNmLen = aName.getLength();
            size_t nNum = o3tl::toInt32(pFormat->GetName().subView( nNmLen ));
            if( nNum-- && nNum < mpTableFrameFormatTable->size() )
                pSetFlags[ nNum / 8 ] |= (0x01 << ( nNum & 0x07 ));
        }
    }

    // All numbers are flagged properly, thus calculate the right number
    size_t nNum = mpTableFrameFormatTable->size();
    for( size_t n = 0; n < nFlagSize; ++n )
    {
        auto nTmp = pSetFlags[ n ];
        if( nTmp != 0xFF )
        {
            // Calculate the number
            nNum = n * 8;
            while( nTmp & 1 )
            {
                ++nNum;
                nTmp >>= 1;
            }
            break;
        }
    }

    return aName + OUString::number( ++nNum );
}

SwTableFormat* SwDoc::FindTableFormatByName( const OUString& rName, bool bAll ) const
{
    const SwFormat* pRet = nullptr;
    if( bAll )
        pRet = mpTableFrameFormatTable->FindFormatByName( rName );
    else
    {
        auto [it, itEnd] = mpTableFrameFormatTable->findRangeByName(rName);
        // Only the ones set in the Doc
        for( ; it != itEnd; ++it )
        {
            const SwFrameFormat* pFormat = *it;
            if( !pFormat->IsDefault() && IsUsed( *pFormat ) &&
                pFormat->GetName() == rName )
            {
                pRet = pFormat;
                break;
            }
        }
    }
    return const_cast<SwTableFormat*>(static_cast<const SwTableFormat*>(pRet));
}

void SwDoc::SetColRowWidthHeight( SwTableBox& rCurrentBox, TableChgWidthHeightType eType,
                                    SwTwips nAbsDiff, SwTwips nRelDiff )
{
    SwTableNode* pTableNd = const_cast<SwTableNode*>(rCurrentBox.GetSttNd()->FindTableNode());
    std::unique_ptr<SwUndo> pUndo;

    SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() );
    aMsgHint.m_eFlags = TBL_BOXPTR;
    getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint );

    bool const bUndo(GetIDocumentUndoRedo().DoesUndo());
    bool bRet = false;
    switch( extractPosition(eType) )
    {
    case TableChgWidthHeightType::ColLeft:
    case TableChgWidthHeightType::ColRight:
    case TableChgWidthHeightType::CellLeft:
    case TableChgWidthHeightType::CellRight:
        {
             bRet = pTableNd->GetTable().SetColWidth( rCurrentBox,
                                eType, nAbsDiff, nRelDiff,
                                bUndo ? &pUndo : nullptr );
        }
        break;
    case TableChgWidthHeightType::RowBottom:
    case TableChgWidthHeightType::CellTop:
    case TableChgWidthHeightType::CellBottom:
        bRet = pTableNd->GetTable().SetRowHeight( rCurrentBox,
                            eType, nAbsDiff, nRelDiff,
                            bUndo ? &pUndo : nullptr );
        break;
    default: break;
    }

    GetIDocumentUndoRedo().DoUndo(bUndo); // SetColWidth can turn it off
    if( pUndo )
    {
        GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
    }

    if( bRet )
    {
        getIDocumentState().SetModified();
    }
}

bool SwDoc::IsNumberFormat( std::u16string_view aString, sal_uInt32& F_Index, double& fOutNumber )
{
    if( aString.size() > 308 ) // optimization matches svl:IsNumberFormat arbitrary value
        return false;

    // remove any comment anchor marks
    OUStringBuffer sStringBuffer(aString);
    sal_Int32 nCommentPosition = sStringBuffer.indexOf( CH_TXTATR_INWORD );
    while( nCommentPosition != -1 )
    {
        sStringBuffer.remove( nCommentPosition, 1 );
        nCommentPosition = sStringBuffer.indexOf( CH_TXTATR_INWORD, nCommentPosition );
    }

    return GetNumberFormatter()->IsNumberFormat( sStringBuffer.makeStringAndClear(), F_Index, fOutNumber );
}

void SwDoc::ChkBoxNumFormat( SwTableBox& rBox, bool bCallUpdate )
{
    // Optimization: If the Box says it's Text, it remains Text
    const SwTableBoxNumFormat* pNumFormatItem = rBox.GetFrameFormat()->GetItemIfSet( RES_BOXATR_FORMAT,
        false );
    if( pNumFormatItem && GetNumberFormatter()->IsTextFormat(pNumFormatItem->GetValue()) )
        return ;

    std::unique_ptr<SwUndoTableNumFormat> pUndo;

    bool bIsEmptyTextNd;
    bool bChgd = true;
    sal_uInt32 nFormatIdx;
    double fNumber;
    if( rBox.HasNumContent( fNumber, nFormatIdx, bIsEmptyTextNd ) )
    {
        if( !rBox.IsNumberChanged() )
            bChgd = false;
        else
        {
            if (GetIDocumentUndoRedo().DoesUndo())
            {
                GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_AUTOFMT, nullptr );
                pUndo.reset(new SwUndoTableNumFormat( rBox ));
                pUndo->SetNumFormat( nFormatIdx, fNumber );
            }

            SwTableBoxFormat* pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.GetFrameFormat());
            SfxItemSetFixed<RES_BOXATR_FORMAT, RES_BOXATR_VALUE> aBoxSet( GetAttrPool() );

            bool bLockModify = true;
            bool bSetNumberFormat = IsInsTableFormatNum();
            const bool bForceNumberFormat = IsInsTableFormatNum() && IsInsTableChangeNumFormat();

            // if the user forced a number format in this cell previously,
            // keep it, unless the user set that she wants the full number
            // format recognition
            if( pNumFormatItem && !bForceNumberFormat )
            {
                sal_uLong nOldNumFormat = pNumFormatItem->GetValue();
                SvNumberFormatter* pNumFormatr = GetNumberFormatter();

                SvNumFormatType nFormatType = pNumFormatr->GetType( nFormatIdx );
                if( nFormatType == pNumFormatr->GetType( nOldNumFormat ) || SvNumFormatType::NUMBER == nFormatType )
                {
                    // Current and specified NumFormat match
                    // -> keep old Format
                    nFormatIdx = nOldNumFormat;
                    bSetNumberFormat = true;
                }
                else
                {
                    // Current and specified NumFormat do not match
                    // -> insert as Text
                    bLockModify = bSetNumberFormat = false;
                }
            }

            if( bSetNumberFormat || bForceNumberFormat )
            {
                pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.ClaimFrameFormat());

                aBoxSet.Put( SwTableBoxValue( fNumber ));
                aBoxSet.Put( SwTableBoxNumFormat( nFormatIdx ));
            }

            // It's not enough to only reset the Formula.
            // Make sure that the Text is formatted accordingly
            if( !bSetNumberFormat && !bIsEmptyTextNd && pNumFormatItem )
            {
                // Just resetting Attributes is not enough
                // Make sure that the Text is formatted accordingly
                pBoxFormat->SetFormatAttr( *GetDfltAttr( RES_BOXATR_FORMAT ));
            }

            if( bLockModify ) pBoxFormat->LockModify();
            pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE );
            if( bLockModify ) pBoxFormat->UnlockModify();

            if( bSetNumberFormat )
                pBoxFormat->SetFormatAttr( aBoxSet );
        }
    }
    else
    {
        // It's not a number
        SwTableBoxFormat* pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.GetFrameFormat());
        if( SfxItemState::SET == pBoxFormat->GetItemState( RES_BOXATR_FORMAT, false ) ||
            SfxItemState::SET == pBoxFormat->GetItemState( RES_BOXATR_VALUE, false ) )
        {
            if (GetIDocumentUndoRedo().DoesUndo())
            {
                GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_AUTOFMT, nullptr );
                pUndo.reset(new SwUndoTableNumFormat( rBox ));
            }

            pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.ClaimFrameFormat());

            // Remove all number formats
            sal_uInt16 nWhich1 = RES_BOXATR_FORMULA;
            if( !bIsEmptyTextNd )
            {
                nWhich1 = RES_BOXATR_FORMAT;

                // Just resetting Attributes is not enough
                // Make sure that the Text is formatted accordingly
                pBoxFormat->SetFormatAttr( *GetDfltAttr( nWhich1 ));
            }
            pBoxFormat->ResetFormatAttr( nWhich1, RES_BOXATR_VALUE );
        }
        else
            bChgd = false;
    }

    if( !bChgd )
        return;

    if( pUndo )
    {
        pUndo->SetBox( rBox );
        GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
        GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
    }

    const SwTableNode* pTableNd = rBox.GetSttNd()->FindTableNode();
    if( bCallUpdate )
    {
        SwTableFormulaUpdate aTableUpdate( &pTableNd->GetTable() );
        getIDocumentFieldsAccess().UpdateTableFields( &aTableUpdate );

        // TL_CHART2: update charts (when cursor leaves cell and
        // automatic update is enabled)
        if (AUTOUPD_FIELD_AND_CHARTS == GetDocumentSettingManager().getFieldUpdateFlags(true))
            pTableNd->GetTable().UpdateCharts();
    }
    getIDocumentState().SetModified();
}

void SwDoc::SetTableBoxFormulaAttrs( SwTableBox& rBox, const SfxItemSet& rSet )
{
    if (GetIDocumentUndoRedo().DoesUndo())
    {
        GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoTableNumFormat>(rBox, &rSet) );
    }

    SwFrameFormat* pBoxFormat = rBox.ClaimFrameFormat();
    if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMULA ))
    {
        pBoxFormat->LockModify();
        pBoxFormat->ResetFormatAttr( RES_BOXATR_VALUE );
        pBoxFormat->UnlockModify();
    }
    else if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_VALUE ))
    {
        pBoxFormat->LockModify();
        pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMULA );
        pBoxFormat->UnlockModify();
    }
    pBoxFormat->SetFormatAttr( rSet );
    getIDocumentState().SetModified();
}

void SwDoc::ClearLineNumAttrs( SwPosition const & rPos )
{
    SwPaM aPam(rPos);
    aPam.Move(fnMoveBackward);
    SwContentNode *pNode = aPam.GetPointContentNode();
    if ( nullptr == pNode )
        return ;
    if( !pNode->IsTextNode() )
        return;

    SwTextNode * pTextNode = pNode->GetTextNode();
    if (!(pTextNode && pTextNode->IsNumbered()
        && pTextNode->GetText().isEmpty()))
        return;

    SfxItemSetFixed<RES_PARATR_BEGIN, RES_PARATR_END - 1>
        rSet( pTextNode->GetDoc().GetAttrPool() );
    pTextNode->SwContentNode::GetAttr( rSet );
    const SfxStringItem* pFormatItem = rSet.GetItemIfSet( RES_PARATR_NUMRULE, false );
    if ( !pFormatItem )
        return;

    SwUndoDelNum * pUndo;
    if( GetIDocumentUndoRedo().DoesUndo() )
    {
        GetIDocumentUndoRedo().ClearRedo();
        pUndo = new SwUndoDelNum( aPam );
        GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
    }
    else
        pUndo = nullptr;
    SwRegHistory aRegH( pUndo ? pUndo->GetHistory() : nullptr );
    aRegH.RegisterInModify( pTextNode , *pTextNode );
    if ( pUndo )
        pUndo->AddNode( *pTextNode );
    std::unique_ptr<SfxStringItem> pNewItem(pFormatItem->Clone());
    pNewItem->SetValue(OUString());
    rSet.Put( std::move(pNewItem) );
    pTextNode->SetAttr( rSet );
}

void SwDoc::ClearBoxNumAttrs( SwNode& rNode )
{
    SwStartNode* pSttNd = rNode.FindSttNodeByType( SwTableBoxStartNode );
    if( nullptr == pSttNd ||
        SwNodeOffset(2) != pSttNd->EndOfSectionIndex() - pSttNd->GetIndex())
        return;

    SwTableBox* pBox = pSttNd->FindTableNode()->GetTable().
                        GetTableBox( pSttNd->GetIndex() );

    const SfxItemSet& rSet = pBox->GetFrameFormat()->GetAttrSet();
    const SwTableBoxNumFormat* pFormatItem = rSet.GetItemIfSet( RES_BOXATR_FORMAT, false );
    if( !pFormatItem ||
        SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMULA, false ) ||
        SfxItemState::SET == rSet.GetItemState( RES_BOXATR_VALUE, false ))
        return;

    if (GetIDocumentUndoRedo().DoesUndo())
    {
        GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoTableNumFormat>(*pBox));
    }

    SwFrameFormat* pBoxFormat = pBox->ClaimFrameFormat();

    // Keep TextFormats!
    sal_uInt16 nWhich1 = RES_BOXATR_FORMAT;
    if( pFormatItem && GetNumberFormatter()->IsTextFormat(
            pFormatItem->GetValue() ))
        nWhich1 = RES_BOXATR_FORMULA;
    else
        // Just resetting Attributes is not enough
        // Make sure that the Text is formatted accordingly
        pBoxFormat->SetFormatAttr( *GetDfltAttr( RES_BOXATR_FORMAT ));

    pBoxFormat->ResetFormatAttr( nWhich1, RES_BOXATR_VALUE );
    getIDocumentState().SetModified();
}

/**
 * Copies a Table from the same or another Doc into itself
 * We create a new Table or an existing one is filled with the Content.
 * We either fill in the Content from a certain Box or a certain TableSelection
 *
 * This method is called by edglss.cxx/fecopy.cxx
 */
bool SwDoc::InsCopyOfTable( SwPosition& rInsPos, const SwSelBoxes& rBoxes,
                        const SwTable* pCpyTable, bool bCpyName, bool bCorrPos, const OUString& rStyleName )
{
    bool bRet;

    const SwTableNode* pSrcTableNd = pCpyTable
            ? pCpyTable->GetTableNode()
            : rBoxes[ 0 ]->GetSttNd()->FindTableNode();

    SwTableNode * pInsTableNd = rInsPos.GetNode().FindTableNode();

    bool const bUndo( GetIDocumentUndoRedo().DoesUndo() );
    if( !pCpyTable && !pInsTableNd )
    {
        std::unique_ptr<SwUndoCpyTable> pUndo;
        if (bUndo)
        {
            GetIDocumentUndoRedo().ClearRedo();
            pUndo.reset(new SwUndoCpyTable(*this));
        }

        {
            ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo());
            bRet = pSrcTableNd->GetTable().MakeCopy( *this, rInsPos, rBoxes,
                                                bCpyName, rStyleName );
        }

        if( pUndo && bRet )
        {
            pInsTableNd = GetNodes()[ rInsPos.GetNodeIndex() - 1 ]->FindTableNode();

            pUndo->SetTableSttIdx( pInsTableNd->GetIndex() );
            GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
        }
    }
    else
    {
        RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags();
        if( getIDocumentRedlineAccess().IsRedlineOn() )
            getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::On |
                                  RedlineFlags::ShowInsert |
                                  RedlineFlags::ShowDelete );

        std::unique_ptr<SwUndoTableCpyTable> pUndo;
        if (bUndo)
        {
            GetIDocumentUndoRedo().ClearRedo();
            pUndo.reset(new SwUndoTableCpyTable(*this));
            GetIDocumentUndoRedo().DoUndo(false);
        }

        rtl::Reference<SwDoc> xCpyDoc(&const_cast<SwDoc&>(pSrcTableNd->GetDoc()));
        bool bDelCpyDoc = xCpyDoc == this;

        if( bDelCpyDoc )
        {
            // Copy the Table into a temporary Doc
            xCpyDoc = new SwDoc;

            SwPosition aPos( xCpyDoc->GetNodes().GetEndOfContent() );
            if( !pSrcTableNd->GetTable().MakeCopy( *xCpyDoc, aPos, rBoxes, true ))
            {
                xCpyDoc.clear();

                if( pUndo )
                {
                    GetIDocumentUndoRedo().DoUndo(bUndo);
                }
                return false;
            }
            aPos.Adjust(SwNodeOffset(-1)); // Set to the Table's EndNode
            pSrcTableNd = aPos.GetNode().FindTableNode();
        }

        const SwStartNode* pSttNd = rInsPos.GetNode().FindTableBoxStartNode();

        rInsPos.nContent.Assign( nullptr, 0 );

        // no complex into complex, but copy into or from new model is welcome
        if( ( !pSrcTableNd->GetTable().IsTableComplex() || pInsTableNd->GetTable().IsNewModel() )
            && ( bDelCpyDoc || !rBoxes.empty() ) )
        {
            // Copy the Table "relatively"
            const SwSelBoxes* pBoxes;
            SwSelBoxes aBoxes;

            if( bDelCpyDoc )
            {
                SwTableBox* pBox = pInsTableNd->GetTable().GetTableBox(
                                        pSttNd->GetIndex() );
                OSL_ENSURE( pBox, "Box is not in this Table" );
                aBoxes.insert( pBox );
                pBoxes = &aBoxes;
            }
            else
                pBoxes = &rBoxes;

            // Copy Table to the selected Lines
            bRet = pInsTableNd->GetTable().InsTable( pSrcTableNd->GetTable(),
                                                        *pBoxes, pUndo.get() );
        }
        else
        {
            SwNodeIndex aNdIdx( *pSttNd, 1 );
            bRet = pInsTableNd->GetTable().InsTable( pSrcTableNd->GetTable(),
                                                    aNdIdx, pUndo.get() );
        }

        xCpyDoc.clear();

        if( pUndo )
        {
            // If the Table could not be copied, delete the Undo object
            GetIDocumentUndoRedo().DoUndo(bUndo);
            if( bRet || !pUndo->IsEmpty() )
            {
                GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
            }
        }

        if( bCorrPos )
        {
            rInsPos.Assign( *pSttNd );
            GetNodes().GoNext( &rInsPos );
        }
        getIDocumentRedlineAccess().SetRedlineFlags( eOld );
    }

    if( bRet )
    {
        getIDocumentState().SetModified();
        getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) );
    }
    return bRet;
}

bool SwDoc::UnProtectTableCells( SwTable& rTable )
{
    bool bChgd = false;
    std::unique_ptr<SwUndoAttrTable> pUndo;
    if (GetIDocumentUndoRedo().DoesUndo())
        pUndo.reset(new SwUndoAttrTable( *rTable.GetTableNode() ));

    SwTableSortBoxes& rSrtBox = rTable.GetTabSortBoxes();
    for (size_t i = rSrtBox.size(); i; )
    {
        SwFrameFormat *pBoxFormat = rSrtBox[ --i ]->GetFrameFormat();
        if( pBoxFormat->GetProtect().IsContentProtected() )
        {
            pBoxFormat->ResetFormatAttr( RES_PROTECT );
            bChgd = true;
        }
    }

    if( pUndo && bChgd )
        GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
    return bChgd;
}

void SwDoc::UnProtectCells( const OUString& rName )
{
    SwTableFormat* pFormat = FindTableFormatByName( rName );
    if( pFormat )
    {
        bool bChgd = UnProtectTableCells( *SwTable::FindTable( pFormat ) );
        if( bChgd )
            getIDocumentState().SetModified();
    }
}

bool SwDoc::UnProtectCells( const SwSelBoxes& rBoxes )
{
    bool bChgd = false;
    if( !rBoxes.empty() )
    {
        std::unique_ptr<SwUndoAttrTable> pUndo;
        if (GetIDocumentUndoRedo().DoesUndo())
            pUndo.reset(new SwUndoAttrTable( *rBoxes[0]->GetSttNd()->FindTableNode() ));

        std::map<SwFrameFormat*, SwTableBoxFormat*> aFormatsMap;
        for (size_t i = rBoxes.size(); i; )
        {
            SwTableBox* pBox = rBoxes[ --i ];
            SwFrameFormat* pBoxFormat = pBox->GetFrameFormat();
            if( pBoxFormat->GetProtect().IsContentProtected() )
            {
                std::map<SwFrameFormat*, SwTableBoxFormat*>::const_iterator const it =
                    aFormatsMap.find(pBoxFormat);
                if (aFormatsMap.end() != it)
                    pBox->ChgFrameFormat(it->second);
                else
                {
                    SwTableBoxFormat *const pNewBoxFormat(
                        static_cast<SwTableBoxFormat*>(pBox->ClaimFrameFormat()));
                    pNewBoxFormat->ResetFormatAttr( RES_PROTECT );
                    aFormatsMap.insert(std::make_pair(pBoxFormat, pNewBoxFormat));
                }
                bChgd = true;
            }
        }

        if( pUndo && bChgd )
            GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
    }
    return bChgd;
}

void SwDoc::UnProtectTables( const SwPaM& rPam )
{
    GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);

    bool bChgd = false, bHasSel = rPam.HasMark() ||
                                    rPam.GetNext() != &rPam;
    SwFrameFormats& rFormats = *GetTableFrameFormats();
    SwTable* pTable;
    const SwTableNode* pTableNd;
    for( auto n = rFormats.size(); n ; )
        if( nullptr != (pTable = SwTable::FindTable( rFormats[ --n ] )) &&
            nullptr != (pTableNd = pTable->GetTableNode() ) &&
            pTableNd->GetNodes().IsDocNodes() )
        {
            SwNodeOffset nTableIdx = pTableNd->GetIndex();

            // Check whether the Table is within the Selection
            if( bHasSel )
            {
                bool bFound = false;
                SwPaM* pTmp = const_cast<SwPaM*>(&rPam);
                do {
                    auto [pStt, pEnd] = pTmp->StartEnd(); // SwPosition*
                    bFound = pStt->GetNodeIndex() < nTableIdx &&
                            nTableIdx < pEnd->GetNodeIndex();

                } while( !bFound && &rPam != ( pTmp = pTmp->GetNext() ) );
                if( !bFound )
                    continue; // Continue searching
            }

            // Lift the protection
            bChgd |= UnProtectTableCells( *pTable );
        }

    GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
    if( bChgd )
        getIDocumentState().SetModified();
}

bool SwDoc::HasTableAnyProtection( const SwPosition* pPos,
                                 const OUString* pTableName,
                                 bool* pFullTableProtection )
{
    bool bHasProtection = false;
    SwTable* pTable = nullptr;
    if( pTableName )
        pTable = SwTable::FindTable( FindTableFormatByName( *pTableName ) );
    else if( pPos )
    {
        SwTableNode* pTableNd = pPos->GetNode().FindTableNode();
        if( pTableNd )
            pTable = &pTableNd->GetTable();
    }

    if( pTable )
    {
        SwTableSortBoxes& rSrtBox = pTable->GetTabSortBoxes();
        for (size_t i = rSrtBox.size(); i; )
        {
            SwFrameFormat *pBoxFormat = rSrtBox[ --i ]->GetFrameFormat();
            if( pBoxFormat->GetProtect().IsContentProtected() )
            {
                if( !bHasProtection )
                {
                    bHasProtection = true;
                    if( !pFullTableProtection )
                        break;
                    *pFullTableProtection = true;
                }
            }
            else if( bHasProtection && pFullTableProtection )
            {
                *pFullTableProtection = false;
                break;
            }
        }
    }
    return bHasProtection;
}

SwTableAutoFormat* SwDoc::MakeTableStyle(const OUString& rName, bool bBroadcast)
{
    SwTableAutoFormat aTableFormat(rName);
    GetTableStyles().AddAutoFormat(aTableFormat);
    SwTableAutoFormat* pTableFormat = GetTableStyles().FindAutoFormat(rName);

    getIDocumentState().SetModified();

    if (GetIDocumentUndoRedo().DoesUndo())
    {
        GetIDocumentUndoRedo().AppendUndo(
            std::make_unique<SwUndoTableStyleMake>(rName, *this));
    }

    if (bBroadcast)
        BroadcastStyleOperation(rName, SfxStyleFamily::Table, SfxHintId::StyleSheetCreated);

    return pTableFormat;
}

std::unique_ptr<SwTableAutoFormat> SwDoc::DelTableStyle(const OUString& rName, bool bBroadcast)
{
    if (bBroadcast)
        BroadcastStyleOperation(rName, SfxStyleFamily::Table, SfxHintId::StyleSheetErased);

    std::unique_ptr<SwTableAutoFormat> pReleasedFormat = GetTableStyles().ReleaseAutoFormat(rName);

    std::vector<SwTable*> vAffectedTables;
    if (pReleasedFormat)
    {
        size_t nTableCount = GetTableFrameFormatCount(true);
        for (size_t i=0; i < nTableCount; ++i)
        {
            SwFrameFormat* pFrameFormat = &GetTableFrameFormat(i, true);
            SwTable* pTable = SwTable::FindTable(pFrameFormat);
            if (pTable->GetTableStyleName() == pReleasedFormat->GetName())
            {
                pTable->SetTableStyleName("");
                vAffectedTables.push_back(pTable);
            }
        }

        getIDocumentState().SetModified();

        if (GetIDocumentUndoRedo().DoesUndo())
        {
            GetIDocumentUndoRedo().AppendUndo(
                std::make_unique<SwUndoTableStyleDelete>(std::move(pReleasedFormat), std::move(vAffectedTables), *this));
        }
    }

    return pReleasedFormat;
}

void SwDoc::ChgTableStyle(const OUString& rName, const SwTableAutoFormat& rNewFormat)
{
    SwTableAutoFormat* pFormat = GetTableStyles().FindAutoFormat(rName);
    if (!pFormat)
        return;

    SwTableAutoFormat aOldFormat = *pFormat;
    *pFormat = rNewFormat;
    pFormat->SetName(rName);

    size_t nTableCount = GetTableFrameFormatCount(true);
    for (size_t i=0; i < nTableCount; ++i)
    {
        SwFrameFormat* pFrameFormat = &GetTableFrameFormat(i, true);
        SwTable* pTable = SwTable::FindTable(pFrameFormat);
        if (pTable->GetTableStyleName() == rName)
            if (SwFEShell* pFEShell = GetDocShell()->GetFEShell())
                pFEShell->UpdateTableStyleFormatting(pTable->GetTableNode());
    }

    getIDocumentState().SetModified();

    if (GetIDocumentUndoRedo().DoesUndo())
    {
        GetIDocumentUndoRedo().AppendUndo(
            std::make_unique<SwUndoTableStyleUpdate>(*pFormat, aOldFormat, *this));
    }
}

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