/* -*- 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 <officecfg/Office/Writer.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, 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 );
        SwNodes::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_RemoveBreaksTable(SwTableNode & rNode, SwTableFormat *const pTableFormat)
{
    // delete old layout frames, new ones need to be created...
    rNode.DelFrames(nullptr);
 
    // remove PageBreaks/PageDesc/ColBreak
    SwFrameFormat & rFormat(*rNode.GetTable().GetFrameFormat());
 
    if (const SvxFormatBreakItem* pItem = rFormat.GetItemIfSet(RES_BREAK, false))
    {
        if (pTableFormat)
        {
            pTableFormat->SetFormatAttr(*pItem);
        }
        rFormat.ResetFormatAttr(RES_BREAK);
    }
 
    SwFormatPageDesc const*const pPageDescItem(rFormat.GetItemIfSet(RES_PAGEDESC, false));
    if (pPageDescItem && pPageDescItem->GetPageDesc())
    {
        if (pTableFormat)
        {
            pTableFormat->SetFormatAttr(*pPageDescItem);
        }
        rFormat.ResetFormatAttr(RES_PAGEDESC);
    }
}
 
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* pNode(&aNodeIndex.GetNode());
        while (pNode->IsSectionNode()) // could be ToX field in table
        {
            pNode = pNode->GetNodes()[pNode->GetIndex()+1];
        }
        if (pNode->IsTableNode())
        {
            lcl_RemoveBreaksTable(static_cast<SwTableNode&>(*pNode),
                    (0 == nLines) ? pTableFormat : nullptr);
        }
        else if (pNode->IsContentNode())
        {
            lcl_RemoveBreaks(static_cast<SwContentNode&>(*pNode),
                    (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 ));
    }
 
    const_cast<SwTable*>(&pTableNd->GetTable())->SwitchFormulasToExternalRepresentation();
 
    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
    for(sw::SpzFrameFormat* pFly: *GetDoc().GetSpzFrameFormats())
    {
        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, bool bInsertDummy )
{
    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());
 
        rTable.SwitchFormulasToInternalRepresentation();
        bRet = rTable.InsertCol(*this, rBoxes, nCnt, bBehind, bInsertDummy);
        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, bool bInsertDummy )
{
    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());
        rTable.SwitchFormulasToInternalRepresentation();
 
        bRet = rTable.InsertRow( this, rBoxes, nCnt, bBehind, bInsertDummy );
        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 = SwNodes::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 );
    }
 
    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());
        rTable.SwitchFormulasToInternalRepresentation();
 
        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())
            {
                if (officecfg::Office::Writer::Table::Change::ApplyTableAutoFormat::get())
                {
                    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());
        rTable.SwitchFormulasToInternalRepresentation();
 
        if (bVert)
            bRet = rTable.SplitCol(*this, rBoxes, nCnt);
        else
            bRet = rTable.SplitRow(*this, rBoxes, nCnt, bSameHeight);
 
        if (bRet)
        {
            if (SwFEShell* pFEShell = GetDocShell()->GetFEShell())
            {
                if (officecfg::Office::Writer::Table::Change::ApplyTableAutoFormat::get())
                {
                    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
        pTableNd->GetTable().SwitchFormulasToInternalRepresentation();
 
        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();
 
    assert(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)
    {
        m_pTable->dumpAsXml(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);
    rTable.GetFrameFormat()->CallSwClientNotify(sw::TableHeadingChange());
    getIDocumentState().SetModified();
}
 
void SwCollectTableLineBoxes::AddToUndoHistory( const SwContentNode& rNd )
{
    if( m_pHistory )
        m_pHistory->AddColl(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 )
{
    size_t nCount = m_aPositionArr.size();
    if (nCount == 0)
        return nullptr;
 
    std::vector<sal_uInt16>::size_type n;
    for (n = 0; n < nCount; ++n)
    {
        if( m_aPositionArr[ n ] == m_nWidth )
            break;
        else if( m_aPositionArr[ n ] > m_nWidth )
        {
            if( n )
                --n;
            break;
        }
    }
 
    if (n >= nCount)
        --n;
 
    m_nWidth = m_nWidth + o3tl::narrowing<sal_uInt16>(rBox.GetFrameFormat()->GetFrameSize().GetWidth());
    return m_Boxes[ n ];
}
 
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 = SwNodes::GoNext(&aIdx);
                aIdx = *pBox->GetSttNd();
                SwContentNode* pDNd = SwNodes::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
 
    SwHistory aHistory;
    {
        SwNodeOffset nSttIdx = pNd->FindTableBoxStartNode()->GetIndex();
        // Find top-level Line
        SwTableBox* pBox = rTable.GetTableBox(nSttIdx);
        sal_uInt16 nSplitLine = 0;
        if(pBox)
        {
            SwTableLine* pLine = pBox->GetUpper();
            while(pLine->GetUpper())
                pLine = pLine->GetUpper()->GetUpper();
 
            // pLine contains the top-level Line now
            nSplitLine = rTable.GetTabLines().GetPos(pLine);
        }
        rTable.Split(GetUniqueTableName(), nSplitLine, GetIDocumentUndoRedo().DoesUndo() ? &aHistory : nullptr);
    }
 
    // 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())
    {
        if (officecfg::Office::Writer::Table::Change::ApplyTableAutoFormat::get())
        {
            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"
    pTableNd->GetTable().Merge(pDelTableNd->GetTable(), pHistory.get());
 
    // 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())
        {
            if (officecfg::Office::Writer::Table::Change::ApplyTableAutoFormat::get())
            {
                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,
        OUString const*const pStyleNameToSet)
{
    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 (pStyleNameToSet)
    {   // tdf#98226 do this here where undo can record it
        pTableNd->GetTable().SetTableStyleName(*pStyleNameToSet);
    }
 
    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 = SwNodes::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"
            + DateTimeToOUString( DateTime( DateTime::SYSTEM ) )
            + 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 SwTableFormat* 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;
 
    pTableNd->GetTable().SwitchFormulasToInternalRepresentation();
    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( const OUString& aString, sal_uInt32& F_Index, double& fOutNumber )
{
    if( aString.getLength() > 308 ) // optimization matches svl:IsNumberFormat arbitrary value
        return false;
 
    // remove any comment anchor marks
    return GetNumberFormatter()->IsNumberFormat(
        aString.replaceAll(OUStringChar(CH_TXTATR_INWORD), u""), 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 = 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 = 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 = 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 = 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 )
    {
        getIDocumentFieldsAccess().UpdateTableFields(&pTableNd->GetTable());
 
        // 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 );
            SwNodes::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(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;
    sw::TableFrameFormats& 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)
{
    SwTableAutoFormat aTableFormat(rName);
    GetTableStyles().AddAutoFormat(aTableFormat);
    SwTableAutoFormat* pTableFormat = GetTableStyles().FindAutoFormat(rName);
 
    getIDocumentState().SetModified();
 
    if (GetIDocumentUndoRedo().DoesUndo())
    {
        GetIDocumentUndoRedo().AppendUndo(
            std::make_unique<SwUndoTableStyleMake>(rName, *this));
    }
 
    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(u""_ustr);
                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: */

V530 The return value of function 'Assign' is required to be utilized.

V530 The return value of function 'InsertText' is required to be utilized.

V530 The return value of function 'Assign' is required to be utilized.

V595 The 'pContentNd' pointer was utilized before it was verified against nullptr. Check lines: 402, 410.

V1028 Possible overflow. Consider casting operands, not the result.

V560 A part of conditional expression is always true: pFormatItem.

V1007 The value from the potentially uninitialized optional 'aBoxFormatArr2' is used. Probably it is a mistake.

V1007 The value from the potentially uninitialized optional 'aBoxFormatArr2' is used. Probably it is a mistake.

V1007 The value from the potentially uninitialized optional 'aBoxFormatArr2' is used. Probably it is a mistake.

V1007 The value from the potentially uninitialized optional 'aBoxFormatArr2' is used. Probably it is a mistake.

V1007 The value from the potentially uninitialized optional 'oNode2Layout' is used. Probably it is a mistake.

V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.