/* -*- 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 <DocumentRedlineManager.hxx>
#include <frmfmt.hxx>
#include <rootfrm.hxx>
#include <txtfrm.hxx>
#include <txtfld.hxx>
#include <doc.hxx>
#include <docsh.hxx>
#include <wrtsh.hxx>
#include <fmtfld.hxx>
#include <frmtool.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentState.hxx>
#include <redline.hxx>
#include <UndoRedline.hxx>
#include <docary.hxx>
#include <ndtxt.hxx>
#include <unocrsr.hxx>
#include <ftnidx.hxx>
#include <authfld.hxx>
#include <strings.hrc>
#include <swmodule.hxx>
#include <osl/diagnose.h>
#include <editeng/prntitem.hxx>
#include <comphelper/lok.hxx>
#include <svl/itemiter.hxx>
 
using namespace com::sun::star;
 
#ifdef DBG_UTIL
 
    #define ERROR_PREFIX "redline table corrupted: "
 
    namespace
    {
        // helper function for lcl_CheckRedline
        // 1. make sure that pPos->nContent points into pPos->nNode
        // 2. check that position is valid and doesn't point after text
        void lcl_CheckPosition( const SwPosition* pPos )
        {
            assert(dynamic_cast<SwContentIndexReg*>(&pPos->GetNode())
                    == pPos->GetContentNode());
 
            SwTextNode* pTextNode = pPos->GetNode().GetTextNode();
            if( pTextNode == nullptr )
            {
                assert(pPos->GetContentIndex() == 0);
            }
            else
            {
                assert(pPos->GetContentIndex() >= 0 && pPos->GetContentIndex() <= pTextNode->Len());
            }
        }
 
        void lcl_CheckPam( const SwPaM* pPam )
        {
            assert(pPam);
            lcl_CheckPosition( pPam->GetPoint() );
            lcl_CheckPosition( pPam->GetMark() );
        }
 
        // check validity of the redline table. Checks redline bounds, and make
        // sure the redlines are sorted and non-overlapping.
        void lcl_CheckRedline( const IDocumentRedlineAccess& redlineAccess )
        {
            const SwRedlineTable& rTable = redlineAccess.GetRedlineTable();
 
            // verify valid redline positions
            for(SwRangeRedline* i : rTable)
                lcl_CheckPam( i );
 
            for(SwRangeRedline* j : rTable)
            {
                // check for empty redlines
                // note: these can destroy sorting in SwTextNode::Update()
                // if there's another one without mark on the same pos.
                OSL_ENSURE( ( *(j->GetPoint()) != *(j->GetMark()) ) ||
                            ( j->GetContentIdx() != nullptr ),
                            ERROR_PREFIX "empty redline" );
            }
 
            // verify proper redline sorting
            for( size_t n = 1; n < rTable.size(); ++n )
            {
                const SwRangeRedline* pPrev = rTable[ n-1 ];
                const SwRangeRedline* pCurrent = rTable[ n ];
 
                // check redline sorting
                SAL_WARN_IF( *pPrev->Start() > *pCurrent->Start(), "sw",
                             ERROR_PREFIX "not sorted correctly" );
 
                // check for overlapping redlines
                SAL_WARN_IF( *pPrev->End() > *pCurrent->Start(), "sw",
                             ERROR_PREFIX "overlapping redlines" );
            }
 
            assert(std::is_sorted(rTable.begin(), rTable.end(), CompareSwRedlineTable()));
        }
    }
 
    #define CHECK_REDLINE( pDoc ) lcl_CheckRedline( pDoc );
 
#else
 
    #define CHECK_REDLINE( pDoc )
 
#endif
 
namespace sw {
 
static void UpdateFieldsForRedline(IDocumentFieldsAccess & rIDFA)
{
    auto const pAuthType(static_cast<SwAuthorityFieldType*>(rIDFA.GetFieldType(
        SwFieldIds::TableOfAuthorities, OUString(), false)));
    if (pAuthType) // created on demand...
    {
        pAuthType->DelSequenceArray();
    }
    rIDFA.GetFieldType(SwFieldIds::RefPageGet, OUString(), false)->UpdateFields();
    rIDFA.GetSysFieldType(SwFieldIds::Chapter)->UpdateFields();
    rIDFA.UpdateExpFields(nullptr, false);
    rIDFA.UpdateRefFields();
}
 
void UpdateFramesForAddDeleteRedline(SwDoc & rDoc, SwPaM const& rPam)
{
    if (rDoc.IsClipBoard())
    {
        return;
    }
    // no need to call UpdateFootnoteNums for FTNNUM_PAGE:
    // the AppendFootnote/RemoveFootnote will do it by itself!
    rDoc.GetFootnoteIdxs().UpdateFootnote(rPam.Start()->GetNode());
    SwPosition currentStart(*rPam.Start());
    SwTextNode * pStartNode(rPam.Start()->GetNode().GetTextNode());
    while (!pStartNode)
    {
        // note: branch only taken for redlines, not fieldmarks
        SwStartNode *const pTableOrSectionNode(
            currentStart.GetNode().IsTableNode()
                ? static_cast<SwStartNode*>(currentStart.GetNode().GetTableNode())
                : static_cast<SwStartNode*>(currentStart.GetNode().GetSectionNode()));
        if ( !pTableOrSectionNode )
        {
            SAL_WARN("sw.core", "UpdateFramesForAddDeleteRedline:: known pathology (or ChangesInRedline mode)");
            return;
        }
        for (SwNodeOffset j = pTableOrSectionNode->GetIndex(); j <= pTableOrSectionNode->EndOfSectionIndex(); ++j)
        {
            pTableOrSectionNode->GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::Hidden);
        }
        for (SwRootFrame const*const pLayout : rDoc.GetAllLayouts())
        {
            if (pLayout->HasMergedParas())
            {
                if (pTableOrSectionNode->IsTableNode())
                {
                    static_cast<SwTableNode*>(pTableOrSectionNode)->DelFrames(pLayout);
                }
                else
                {
                    static_cast<SwSectionNode*>(pTableOrSectionNode)->DelFrames(pLayout);
                }
            }
        }
        currentStart.Assign( pTableOrSectionNode->EndOfSectionIndex() + 1 );
        pStartNode = currentStart.GetNode().GetTextNode();
    }
    if (currentStart < *rPam.End())
    {
        SwTextNode * pNode(pStartNode);
        do
        {
            // deleted text node: remove it from "hidden" list
            // to update numbering in Show Changes mode
            SwPosition aPos( *pNode, pNode->Len() );
            if ( pNode->GetNumRule() && aPos < *rPam.End() )
                pNode->RemoveFromListRLHidden();
 
            std::vector<SwTextFrame*> frames;
            SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pNode);
            for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
            {
                if (pFrame->getRootFrame()->HasMergedParas())
                {
                    frames.push_back(pFrame);
                }
                // set anchored objects as deleted
                pFrame->SetDrawObjsAsDeleted(true);
            }
            if (frames.empty())
            {
                auto const layouts(rDoc.GetAllLayouts());
                assert(std::none_of(layouts.begin(), layouts.end(),
                    [](SwRootFrame const*const pLayout) { return pLayout->IsHideRedlines(); }));
                (void) layouts;
                break;
            }
            auto eMode(sw::FrameMode::Existing);
            SwTextNode * pLast(pNode);
            for (SwTextFrame * pFrame : frames)
            {
                SwTextNode & rFirstNode(pFrame->GetMergedPara()
                    ? *pFrame->GetMergedPara()->pFirstNode
                    : *pNode);
                assert(pNode == pStartNode
                        ? rFirstNode.GetIndex() <= pNode->GetIndex()
                        : &rFirstNode == pNode);
                // clear old one first to avoid DelFrames confusing updates & asserts...
                pFrame->SetMergedPara(nullptr);
                pFrame->SetMergedPara(sw::CheckParaRedlineMerge(
                    *pFrame, rFirstNode, eMode));
                eMode = sw::FrameMode::New; // Existing is not idempotent!
                // the first node of the new redline is not necessarily the first
                // node of the merged frame, there could be another redline nearby
                sw::AddRemoveFlysAnchoredToFrameStartingAtNode(*pFrame, *pNode, nullptr);
                // if redline is split across table and table cell is empty, there's no redline in the cell and so no merged para
                if (pFrame->GetMergedPara())
                {
                    pLast = const_cast<SwTextNode*>(pFrame->GetMergedPara()->pLastNode);
                }
            }
            SwNodeIndex tmp(*pLast);
            // skip over hidden sections!
            pNode = static_cast<SwTextNode*>(SwNodes::GoNextSection(&tmp, /*bSkipHidden=*/true, /*bSkipProtect=*/false));
        }
        while (pNode && pNode->GetIndex() <= rPam.End()->GetNodeIndex());
    }
    // fields last - SwGetRefField::UpdateField requires up-to-date frames
    UpdateFieldsForRedline(rDoc.getIDocumentFieldsAccess()); // after footnotes
 
    // update SwPostItMgr / notes in the margin
    rDoc.GetDocShell()->Broadcast(
            SwFormatFieldHint(nullptr, SwFormatFieldHintWhich::REMOVED) );
}
 
void UpdateFramesForRemoveDeleteRedline(SwDoc & rDoc, SwPaM const& rPam)
{
    // tdf#147006 fieldmark command may be empty => do not call AppendAllObjs()
    if (rDoc.IsClipBoard() || *rPam.GetPoint() == *rPam.GetMark())
    {
        return;
    }
    bool isAppendObjsCalled(false);
    rDoc.GetFootnoteIdxs().UpdateFootnote(rPam.Start()->GetNode());
    SwPosition currentStart(*rPam.Start());
    SwTextNode * pStartNode(rPam.Start()->GetNode().GetTextNode());
    while (!pStartNode)
    {
        // note: branch only taken for redlines, not fieldmarks
        SwStartNode *const pTableOrSectionNode(
            currentStart.GetNode().IsTableNode()
                ? static_cast<SwStartNode*>(currentStart.GetNode().GetTableNode())
                : static_cast<SwStartNode*>(currentStart.GetNode().GetSectionNode()));
        assert(pTableOrSectionNode); // known pathology
        for (SwNodeOffset j = pTableOrSectionNode->GetIndex(); j <= pTableOrSectionNode->EndOfSectionIndex(); ++j)
        {
            pTableOrSectionNode->GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::None);
        }
        if (rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->HasMergedParas())
        {
            // note: this will also create frames for all currently hidden flys
            // because it calls AppendAllObjs
            ::MakeFrames(&rDoc, currentStart.GetNode(), *pTableOrSectionNode->EndOfSectionNode());
            isAppendObjsCalled = true;
        }
        currentStart.Assign( pTableOrSectionNode->EndOfSectionIndex() + 1 );
        pStartNode = currentStart.GetNode().GetTextNode();
    }
    if (currentStart < *rPam.End())
    {
        SwTextNode * pNode(pStartNode);
        do
        {
            // undeleted text node: add it to the "hidden" list
            // to update numbering in Show Changes mode
            SwPosition aPos( *pNode, pNode->Len() );
            if ( pNode->GetNumRule() && aPos < *rPam.End() )
                pNode->AddToListRLHidden();
 
            std::vector<SwTextFrame*> frames;
            SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pNode);
            for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
            {
                if (pFrame->getRootFrame()->HasMergedParas())
                {
                    frames.push_back(pFrame);
                }
                // set anchored objects as not deleted
                pFrame->SetDrawObjsAsDeleted(false);
            }
            if (frames.empty())
            {
                // in SwUndoSaveSection::SaveSection(), DelFrames() preceded this call
                if (!pNode->FindTableBoxStartNode() && !pNode->FindFlyStartNode())
                {
                    auto const layouts(rDoc.GetAllLayouts());
                    assert(std::none_of(layouts.begin(), layouts.end(),
                        [](SwRootFrame const*const pLayout) { return pLayout->IsHideRedlines(); }));
                    (void) layouts;
                }
                isAppendObjsCalled = true; // skip that!
                break;
            }
 
            // no nodes can be unmerged by this - skip MakeFrames() etc.
            if (rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode())
            {
                break; // continue with AppendAllObjs()
            }
 
            // first, call CheckParaRedlineMerge on the first paragraph,
            // to init flag on new merge range (if any) + 1st node post the merge
            auto eMode(sw::FrameMode::Existing);
            SwTextNode * pLast(pNode);
            for (SwTextFrame * pFrame : frames)
            {
                if (auto const pMergedPara = pFrame->GetMergedPara())
                {
                    pLast = const_cast<SwTextNode*>(pMergedPara->pLastNode);
                    assert(pNode == pStartNode
                        ? pMergedPara->pFirstNode->GetIndex() <= pNode->GetIndex()
                        : pMergedPara->pFirstNode == pNode);
                    // clear old one first to avoid DelFrames confusing updates & asserts...
                    SwTextNode & rFirstNode(*pMergedPara->pFirstNode);
                    pFrame->SetMergedPara(nullptr);
                    pFrame->SetMergedPara(sw::CheckParaRedlineMerge(
                        *pFrame, rFirstNode, eMode));
                    eMode = sw::FrameMode::New; // Existing is not idempotent!
                    // update pNode so MakeFrames starts on 2nd node
                    pNode = &rFirstNode;
                }
            }
            if (pLast != pNode)
            {
                // now start node until end of merge + 1 has proper flags; MakeFrames
                // should pick up from the next node in need of frames by checking flags
                SwNodeIndex const start(*pNode, +1);
                SwNodeIndex const end(*pLast, +1); // end is exclusive
                // note: this will also create frames for all currently hidden flys
                // both on first and non-first nodes because it calls AppendAllObjs
                ::MakeFrames(&rDoc, start.GetNode(), end.GetNode());
                isAppendObjsCalled = true;
                // re-use this to move flys that are now on the wrong frame, with end
                // of redline as "second" node; the nodes between start and end should
                // be complete with MakeFrames already
                sw::MoveMergedFlysAndFootnotes(frames, *pNode, *pLast, false);
            }
            SwNodeIndex tmp(*pLast);
            // skip over hidden sections!
            pNode = static_cast<SwTextNode*>(SwNodes::GoNextSection(&tmp, /*bSkipHidden=*/true, /*bSkipProtect=*/false));
        }
        while (pNode && pNode->GetIndex() <= rPam.End()->GetNodeIndex());
    }
 
    if (!isAppendObjsCalled)
    {   // recreate flys in the one node the hard way...
        for (auto const& pLayout : rDoc.GetAllLayouts())
        {
            if (pLayout->HasMergedParas())
            {
                AppendAllObjs(rDoc.GetSpzFrameFormats(), pLayout);
                break;
            }
        }
    }
    // fields last - SwGetRefField::UpdateField requires up-to-date frames
    UpdateFieldsForRedline(rDoc.getIDocumentFieldsAccess()); // after footnotes
 
    const SwTextNode *pTextNode = rPam.GetPointNode().GetTextNode();
    SwTextAttr* pTextAttr = pTextNode ? pTextNode->GetFieldTextAttrAt(rPam.GetPoint()->GetContentIndex() - 1, ::sw::GetTextAttrMode::Default) : nullptr;
    SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pTextAttr));
    if (pTextField && comphelper::LibreOfficeKit::isActive() )
        rDoc.GetDocShell()->Broadcast(
            SwFormatFieldHint(&pTextField->GetFormatField(), SwFormatFieldHintWhich::INSERTED));
    else
        rDoc.GetDocShell()->Broadcast(
            SwFormatFieldHint(nullptr, SwFormatFieldHintWhich::INSERTED) );
}
 
} // namespace sw
 
namespace
{
    bool IsPrevPos( const SwPosition & rPos1, const SwPosition & rPos2 )
    {
        const SwContentNode* pCNd;
        if( 0 != rPos2.GetContentIndex() )
            return false;
        if( rPos2.GetNodeIndex() - 1 != rPos1.GetNodeIndex() )
            return false;
        pCNd = rPos1.GetNode().GetContentNode();
        return pCNd && rPos1.GetContentIndex() == pCNd->Len();
    }
 
    // copy style or return with SwRedlineExtra_FormatColl with reject data of the upcoming copy
    SwRedlineExtraData_FormatColl* lcl_CopyStyle( const SwPosition & rFrom, const SwPosition & rTo, bool bCopy = true )
    {
        SwTextNode* pToNode = rTo.GetNode().GetTextNode();
        SwTextNode* pFromNode = rFrom.GetNode().GetTextNode();
        if (pToNode != nullptr && pFromNode != nullptr && pToNode != pFromNode)
        {
            const SwPaM aPam(*pToNode);
            SwDoc& rDoc = aPam.GetDoc();
            // using Undo, copy paragraph style
            SwTextFormatColl* pFromColl = pFromNode->GetTextColl();
            SwTextFormatColl* pToColl = pToNode->GetTextColl();
            if (bCopy && pFromColl != pToColl)
                rDoc.SetTextFormatColl(aPam, pFromColl);
 
            // using Undo, remove direct paragraph formatting of the "To" paragraph,
            // and apply here direct paragraph formatting of the "From" paragraph
            SfxItemSetFixed<
                    RES_PARATR_BEGIN, RES_PARATR_END - 3, // skip RSID and GRABBAG
                    RES_PARATR_LIST_BEGIN, RES_UL_SPACE,  // skip PAGEDESC and BREAK
                    RES_CNTNT, RES_FRMATR_END - 1>
                 aTmp(rDoc.GetAttrPool());
            SfxItemSet aTmp2(aTmp);
 
            pToNode->GetParaAttr(aTmp, 0, 0);
            pFromNode->GetParaAttr(aTmp2, 0, 0);
 
            bool bSameSet = aTmp == aTmp2;
 
            if (!bSameSet)
            {
                for (SfxItemIter aIter(aTmp); !aIter.IsAtEnd(); aIter.NextItem())
                {
                    const sal_uInt16 nWhich(aIter.GetCurWhich());
                    if( SfxItemState::SET == aTmp.GetItemState( nWhich, false ) &&
                        SfxItemState::SET != aTmp2.GetItemState( nWhich, false ) )
                            aTmp2.Put( aTmp.GetPool()->GetUserOrPoolDefaultItem(nWhich) );
                }
            }
 
            if (bCopy && !bSameSet)
                rDoc.getIDocumentContentOperations().InsertItemSet(aPam, aTmp2);
            else if (!bCopy && (!bSameSet || pFromColl != pToColl))
                return new SwRedlineExtraData_FormatColl( pFromColl->GetName(), USHRT_MAX, &aTmp2 );
        }
        return nullptr;
    }
 
    // delete the empty tracked table row (i.e. if it's last tracked deletion was accepted)
    void lcl_DeleteTrackedTableRow ( const SwPosition* pPos )
    {
        const SwTableBox* pBox = pPos->GetNode().GetTableBox();
        if ( !pBox )
            return;
 
        // tracked column deletion
 
        const SvxPrintItem *pHasBoxTextChangesOnlyProp =
                pBox->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT);
        // empty table cell with property "HasTextChangesOnly" = false
        if ( pHasBoxTextChangesOnlyProp && !pHasBoxTextChangesOnlyProp->GetValue() )
        {
            SwCursor aCursor( *pPos, nullptr );
            if ( pBox->IsEmpty() )
            {
                // tdf#155747 remove table cursor
                pPos->GetDoc().GetDocShell()->GetWrtShell()->EnterStdMode();
                // TODO check the other cells of the column
                // before removing the column
                pPos->GetDoc().DeleteCol( aCursor );
                return;
            }
            else
            {
                SvxPrintItem aHasTextChangesOnly(RES_PRINT, false);
                pPos->GetDoc().SetBoxAttr( aCursor, aHasTextChangesOnly );
            }
        }
 
        // tracked row deletion
 
        const SwTableLine* pLine = pBox->GetUpper();
        const SvxPrintItem *pHasTextChangesOnlyProp =
                pLine->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT);
        // empty table row with property "HasTextChangesOnly" = false
        if ( pHasTextChangesOnlyProp && !pHasTextChangesOnlyProp->GetValue() )
        {
            if ( pLine->IsEmpty() )
            {
                SwCursor aCursor( *pPos, nullptr );
                pPos->GetDoc().DeleteRow( aCursor );
            }
            else
            {
                // update property "HasTextChangesOnly"
                SwRedlineTable::size_type nPos = 0;
                (void)pLine->UpdateTextChangesOnly(nPos);
            }
        }
    }
 
    // at rejection of a deletion in a table, remove the tracking of the table row
    // (also at accepting the last redline insertion of a tracked table row insertion)
    void lcl_RemoveTrackingOfTableRow( const SwPosition* pPos, bool bRejectDeletion )
    {
        const SwTableBox* pBox = pPos->GetNode().GetTableBox();
        if ( !pBox )
            return;
 
        // tracked column deletion
 
        const SvxPrintItem *pHasBoxTextChangesOnlyProp =
                pBox->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT);
        // table cell property "HasTextChangesOnly" is set and its value is false
        if ( pHasBoxTextChangesOnlyProp && !pHasBoxTextChangesOnlyProp->GetValue() )
        {
            SvxPrintItem aUnsetTracking(RES_PRINT, true);
            SwCursor aCursor( *pPos, nullptr );
            pPos->GetDoc().SetBoxAttr( aCursor, aUnsetTracking );
        }
 
        // tracked row deletion
 
        const SwTableLine* pLine = pBox->GetUpper();
        const SvxPrintItem *pHasTextChangesOnlyProp =
                pLine->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT);
        // table row property "HasTextChangesOnly" is set and its value is false
        if ( pHasTextChangesOnlyProp && !pHasTextChangesOnlyProp->GetValue() )
        {
            bool bNoMoreInsertion = false;
            if ( !bRejectDeletion )
            {
                SwRedlineTable::size_type nPos = 0;
                SwRedlineTable::size_type nInsert = pLine->UpdateTextChangesOnly(nPos, /*bUpdateProperty=*/false);
 
                if ( SwRedlineTable::npos == nInsert )
                    bNoMoreInsertion = true;
            }
            if ( bRejectDeletion || bNoMoreInsertion )
            {
                SvxPrintItem aUnsetTracking(RES_PRINT, true);
                SwCursor aCursor( *pPos, nullptr );
                pPos->GetDoc().SetRowNotTracked( aCursor, aUnsetTracking );
            }
        }
    }
 
    bool lcl_AcceptRedline( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos,
                            bool bCallDelete,
                            const SwPosition* pSttRng = nullptr,
                            const SwPosition* pEndRng = nullptr )
    {
        bool bRet = true;
        SwRangeRedline* pRedl = rArr[ rPos ];
        SwPosition *pRStt = nullptr, *pREnd = nullptr;
        SwComparePosition eCmp = SwComparePosition::Outside;
        if( pSttRng && pEndRng )
        {
            pRStt = pRedl->Start();
            pREnd = pRedl->End();
            eCmp = ComparePosition( *pSttRng, *pEndRng, *pRStt, *pREnd );
        }
 
        pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove);
 
        switch( pRedl->GetType() )
        {
        case RedlineType::Insert:
        case RedlineType::Format:
            {
                bool bCheck = false, bReplace = false;
                switch( eCmp )
                {
                case SwComparePosition::Inside:
                    if( *pSttRng == *pRStt )
                        pRedl->SetStart( *pEndRng, pRStt );
                    else
                    {
                        if( *pEndRng != *pREnd )
                        {
                            // split up
                            SwRangeRedline* pNew = new SwRangeRedline( *pRedl );
                            pNew->SetStart( *pEndRng );
                            rArr.Insert( pNew ); ++rPos;
                        }
                        pRedl->SetEnd( *pSttRng, pREnd );
                        bCheck = true;
                    }
                    break;
 
                case SwComparePosition::OverlapBefore:
                    pRedl->SetStart( *pEndRng, pRStt );
                    bReplace = true;
                    break;
 
                case SwComparePosition::OverlapBehind:
                    pRedl->SetEnd( *pSttRng, pREnd );
                    bCheck = true;
                    break;
 
                case SwComparePosition::Outside:
                case SwComparePosition::Equal:
                    {
                        bool bInsert = RedlineType::Insert == pRedl->GetType();
                        SwPosition aPos(pRedl->Start()->GetNode());
                        rArr.DeleteAndDestroy( rPos-- );
 
                        // remove tracking of the table row, if needed
                        if ( bInsert )
                            lcl_RemoveTrackingOfTableRow( &aPos, /*bRejectDelete=*/false );
                    }
                    break;
 
                default:
                    bRet = false;
                }
 
                if( bReplace || ( bCheck && !pRedl->HasValidRange() ))
                {
                    // re-insert
                    rArr.Remove( pRedl );
                    rArr.Insert( pRedl );
                }
            }
            break;
        case RedlineType::Delete:
            {
                SwDoc& rDoc = pRedl->GetDoc();
                const SwPosition *pDelStt = nullptr, *pDelEnd = nullptr;
                bool bDelRedl = false;
                switch( eCmp )
                {
                case SwComparePosition::Inside:
                    if( bCallDelete )
                    {
                        pDelStt = pSttRng;
                        pDelEnd = pEndRng;
                    }
                    break;
 
                case SwComparePosition::OverlapBefore:
                    if( bCallDelete )
                    {
                        pDelStt = pRStt;
                        pDelEnd = pEndRng;
                    }
                    break;
                case SwComparePosition::OverlapBehind:
                    if( bCallDelete )
                    {
                        pDelStt = pREnd;
                        pDelEnd = pSttRng;
                    }
                    break;
 
                case SwComparePosition::Outside:
                case SwComparePosition::Equal:
                    {
                        rArr.Remove( rPos-- );
                        bDelRedl = true;
                        if( bCallDelete )
                        {
                            pDelStt = pRedl->Start();
                            pDelEnd = pRedl->End();
                        }
                    }
                    break;
                default:
                    bRet = false;
                }
 
                if( pDelStt && pDelEnd )
                {
                    SwPaM aPam( *pDelStt, *pDelEnd );
                    SwContentNode* pCSttNd = pDelStt->GetNode().GetContentNode();
                    SwContentNode* pCEndNd = pDelEnd->GetNode().GetContentNode();
                    pRStt = pRedl->Start();
                    pREnd = pRedl->End();
 
                    // keep style of the empty paragraph after deletion of wholly paragraphs
                    if( pCSttNd && pCEndNd && pRStt && pREnd && pRStt->GetContentIndex() == 0 )
                        lcl_CopyStyle(*pREnd, *pRStt);
 
                    if( bDelRedl )
                        delete pRedl;
 
                    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
                    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));
 
                    if( pCSttNd && pCEndNd )
                    {
                        rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam );
                        lcl_DeleteTrackedTableRow( aPam.End() );
                    }
                    else if (pCSttNd && !pCEndNd)
                        {
                            aPam.GetBound().nContent.Assign( nullptr, 0 );
                            aPam.GetBound( false ).nContent.Assign( nullptr, 0 );
                            rDoc.getIDocumentContentOperations().DelFullPara( aPam );
                        }
                    else
                    {
                        rDoc.getIDocumentContentOperations().DeleteRange(aPam);
                    }
                    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
                }
                else if( bDelRedl )
                    delete pRedl;
            }
            break;
 
        case RedlineType::FmtColl:
        case RedlineType::ParagraphFormat:
            rArr.DeleteAndDestroy( rPos-- );
            break;
 
        default:
            bRet = false;
        }
        return bRet;
    }
 
    bool lcl_RejectRedline( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos,
                            bool bCallDelete,
                            const SwPosition* pSttRng = nullptr,
                            const SwPosition* pEndRng = nullptr )
    {
        bool bRet = true;
        SwRangeRedline* pRedl = rArr[ rPos ];
        SwDoc& rDoc = pRedl->GetDoc();
        SwPosition *pRStt = nullptr, *pREnd = nullptr;
        SwComparePosition eCmp = SwComparePosition::Outside;
        if( pSttRng && pEndRng )
        {
            pRStt = pRedl->Start();
            pREnd = pRedl->End();
            eCmp = ComparePosition( *pSttRng, *pEndRng, *pRStt, *pREnd );
        }
 
        pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove);
 
        switch( pRedl->GetType() )
        {
        case RedlineType::Insert:
            {
                const SwPosition *pDelStt = nullptr, *pDelEnd = nullptr;
                bool bDelRedl = false;
                switch( eCmp )
                {
                case SwComparePosition::Inside:
                    if( bCallDelete )
                    {
                        pDelStt = pSttRng;
                        pDelEnd = pEndRng;
                    }
                    break;
 
                case SwComparePosition::OverlapBefore:
                    if( bCallDelete )
                    {
                        pDelStt = pRStt;
                        pDelEnd = pEndRng;
                    }
                    break;
                case SwComparePosition::OverlapBehind:
                    if( bCallDelete )
                    {
                        pDelStt = pREnd;
                        pDelEnd = pSttRng;
                    }
                    break;
                case SwComparePosition::Outside:
                case SwComparePosition::Equal:
                    {
                        // delete the range again
                        rArr.Remove( rPos-- );
                        bDelRedl = true;
                        if( bCallDelete )
                        {
                            pDelStt = pRedl->Start();
                            pDelEnd = pRedl->End();
                        }
                    }
                    break;
 
                default:
                    bRet = false;
                }
                if( pDelStt && pDelEnd )
                {
                    SwPaM aPam( *pDelStt, *pDelEnd );
 
                    SwContentNode* pCSttNd = pDelStt->GetNode().GetContentNode();
                    SwContentNode* pCEndNd = pDelEnd->GetNode().GetContentNode();
 
                    if( bDelRedl )
                        delete pRedl;
 
                    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
                    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));
 
                    if( pCSttNd && pCEndNd )
                    {
                        rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam );
                        lcl_DeleteTrackedTableRow( aPam.End() );
                    }
                    else if (pCSttNd && !pCEndNd)
                        {
                            aPam.GetBound().nContent.Assign( nullptr, 0 );
                            aPam.GetBound( false ).nContent.Assign( nullptr, 0 );
                            if (aPam.End()->GetNode().IsStartNode())
                            {   // end node will be deleted too! see nNodeDiff+1
                                aPam.End()->Adjust(SwNodeOffset(-1));
                            }
                            assert(!aPam.End()->GetNode().IsStartNode());
                            rDoc.getIDocumentContentOperations().DelFullPara( aPam );
                        }
                    else
                    {
                        rDoc.getIDocumentContentOperations().DeleteRange(aPam);
                    }
                    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
                }
                else if( bDelRedl )
                    delete pRedl;
            }
            break;
        case RedlineType::Delete:
            {
                SwRangeRedline* pNew = nullptr;
                bool bCheck = false, bReplace = false;
                SwPaM const updatePaM(pSttRng ? *pSttRng : *pRedl->Start(),
                                      pEndRng ? *pEndRng : *pRedl->End());
 
                if( pRedl->GetExtraData() )
                    pRedl->GetExtraData()->Reject( *pRedl );
 
                // remove tracking of the table row, if needed
                lcl_RemoveTrackingOfTableRow( updatePaM.End(), /*bRejectDelete=*/true );
 
                switch( eCmp )
                {
                case SwComparePosition::Inside:
                    {
                        if( 1 < pRedl->GetStackCount() )
                        {
                            pNew = new SwRangeRedline( *pRedl );
                            pNew->PopData();
                        }
                        if( *pSttRng == *pRStt )
                        {
                            pRedl->SetStart( *pEndRng, pRStt );
                            bReplace = true;
                            if( pNew )
                                pNew->SetEnd( *pEndRng );
                        }
                        else
                        {
                            if( *pEndRng != *pREnd )
                            {
                                // split up
                                SwRangeRedline* pCpy = new SwRangeRedline( *pRedl );
                                pCpy->SetStart( *pEndRng );
                                rArr.Insert( pCpy ); ++rPos;
                                if( pNew )
                                    pNew->SetEnd( *pEndRng );
                            }
 
                            pRedl->SetEnd( *pSttRng, pREnd );
                            bCheck = true;
                            if( pNew )
                                pNew->SetStart( *pSttRng );
                        }
                    }
                    break;
 
                case SwComparePosition::OverlapBefore:
                    if( 1 < pRedl->GetStackCount() )
                    {
                        pNew = new SwRangeRedline( *pRedl );
                        pNew->PopData();
                    }
                    pRedl->SetStart( *pEndRng, pRStt );
                    bReplace = true;
                    if( pNew )
                        pNew->SetEnd( *pEndRng );
                    break;
 
                case SwComparePosition::OverlapBehind:
                    if( 1 < pRedl->GetStackCount() )
                    {
                        pNew = new SwRangeRedline( *pRedl );
                        pNew->PopData();
                    }
                    pRedl->SetEnd( *pSttRng, pREnd );
                    bCheck = true;
                    if( pNew )
                        pNew->SetStart( *pSttRng );
                    break;
 
                case SwComparePosition::Outside:
                case SwComparePosition::Equal:
                    if( !pRedl->PopData() )
                        // deleting the RedlineObject is enough
                        rArr.DeleteAndDestroy( rPos-- );
                    break;
 
                default:
                    bRet = false;
                }
 
                if( pNew )
                {
                    rArr.Insert( pNew ); ++rPos;
                }
 
                if( bReplace || ( bCheck && !pRedl->HasValidRange() ))
                {
                    // re-insert
                    rArr.Remove( pRedl );
                    rArr.Insert( pRedl );
                }
 
                sw::UpdateFramesForRemoveDeleteRedline(rDoc, updatePaM);
            }
            break;
 
        case RedlineType::Format:
        case RedlineType::FmtColl:
        case RedlineType::ParagraphFormat:
            {
                // tdf#52391 instead of hidden acception at the requested
                // rejection, remove direct text formatting to get the potential
                // original state of the text (FIXME if the original text
                // has already contained direct text formatting: unfortunately
                // ODF 1.2 doesn't support rejection of format-only changes)
                if ( pRedl->GetType() == RedlineType::Format )
                {
                    SwPaM aPam( *(pRedl->Start()), *(pRedl->End()) );
                    rDoc.ResetAttrs(aPam);
                }
                else if ( pRedl->GetType() == RedlineType::ParagraphFormat )
                {
                    // handle paragraph formatting changes
                    // (range is only a full paragraph or a part of it)
                    const SwPosition* pStt = pRedl->Start();
                    SwTextNode* pTNd = pStt->GetNode().GetTextNode();
                    if( pTNd )
                    {
                        // expand range to the whole paragraph
                        // and reset only the paragraph attributes
                        SwPaM aPam( *pTNd, pTNd->GetText().getLength() );
                        o3tl::sorted_vector<sal_uInt16> aResetAttrsArray;
 
                        static constexpr std::pair<sal_uInt16, sal_uInt16> aResetableSetRange[] = {
                            { RES_PARATR_BEGIN, RES_PARATR_END - 1 },
                            { RES_PARATR_LIST_BEGIN, RES_FRMATR_END - 1 },
                        };
 
                        for (const auto& [nBegin, nEnd] : aResetableSetRange)
                        {
                            for (sal_uInt16 i = nBegin; i <= nEnd; ++i)
                                aResetAttrsArray.insert( i );
                        }
 
                        rDoc.ResetAttrs(aPam, false, aResetAttrsArray);
 
                        // remove numbering
                        if ( pTNd->GetNumRule() )
                            rDoc.DelNumRules(aPam);
                    }
                }
 
                if( pRedl->GetExtraData() )
                    pRedl->GetExtraData()->Reject( *pRedl );
 
                rArr.DeleteAndDestroy( rPos-- );
            }
            break;
 
        default:
            bRet = false;
        }
        return bRet;
    }
 
    bool lcl_AcceptInnerInsertRedline(SwRedlineTable& rArr, SwRedlineTable::size_type& rPos,
                                      int nDepth)
    {
        SwRangeRedline* pRedl = rArr[rPos];
        SwDoc& rDoc = pRedl->GetDoc();
        SwPaM const updatePaM(*pRedl->Start(), *pRedl->End());
 
        pRedl->PopAllDataAfter(nDepth);
        sw::UpdateFramesForRemoveDeleteRedline(rDoc, updatePaM);
        return true;
    }
 
    typedef bool (*Fn_AcceptReject)( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos,
                            bool bCallDelete,
                            const SwPosition* pSttRng,
                            const SwPosition* pEndRng);
 
 
    int lcl_AcceptRejectRedl( Fn_AcceptReject fn_AcceptReject,
                                SwRedlineTable& rArr, bool bCallDelete,
                                const SwPaM& rPam)
    {
        SwRedlineTable::size_type n = 0;
        int nCount = 0;
 
        const SwPosition* pStt = rPam.Start(),
                        * pEnd = rPam.End();
        const SwRangeRedline* pFnd = rArr.FindAtPosition( *pStt, n );
        if( pFnd &&     // Is new a part of it?
            ( *pFnd->Start() != *pStt || *pFnd->End() > *pEnd ))
        {
            // Only revoke the partial selection
            if( (*fn_AcceptReject)( rArr, n, bCallDelete, pStt, pEnd ))
                nCount++;
            ++n;
        }
 
        // tdf#119824 first we will accept only overlapping paragraph format changes
        // in the first loop to avoid potential content changes during Redo
        bool bHasParagraphFormatChange = false;
        for( int m = 0 ; m < 2 && !bHasParagraphFormatChange; ++m )
        {
            for(SwRedlineTable::size_type o = n ; o < rArr.size(); ++o )
            {
                SwRangeRedline* pTmp = rArr[ o ];
                if( pTmp->HasMark() && pTmp->IsVisible() )
                {
                    if( *pTmp->End() <= *pEnd )
                    {
                        if( (m > 0 || RedlineType::ParagraphFormat == pTmp->GetType()) &&
                            (*fn_AcceptReject)( rArr, o, bCallDelete, nullptr, nullptr ))
                        {
                            bHasParagraphFormatChange = true;
                            nCount++;
                        }
                    }
                    else
                    {
                        if( *pTmp->Start() < *pEnd )
                        {
                            // Only revoke the partial selection
                            if( (m > 0 || RedlineType::ParagraphFormat == pTmp->GetType()) &&
                                (*fn_AcceptReject)( rArr, o, bCallDelete, pStt, pEnd ))
                            {
                                bHasParagraphFormatChange = true;
                                nCount++;
                            }
                        }
                        break;
                    }
                }
            }
        }
        return nCount;
    }
 
    void lcl_AdjustRedlineRange( SwPaM& rPam )
    {
        // The Selection is only in the ContentSection. If there are Redlines
        // to Non-ContentNodes before or after that, then the Selections
        // expand to them.
        auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
        SwDoc& rDoc = rPam.GetDoc();
        if( !pStt->GetContentIndex() &&
            !rDoc.GetNodes()[ pStt->GetNodeIndex() - 1 ]->IsContentNode() )
        {
            const SwRangeRedline* pRedl = rDoc.getIDocumentRedlineAccess().GetRedline( *pStt, nullptr );
            if( pRedl )
            {
                const SwPosition* pRStt = pRedl->Start();
                if( !pRStt->GetContentIndex() && pRStt->GetNodeIndex() ==
                    pStt->GetNodeIndex() - 1 )
                    *pStt = *pRStt;
            }
        }
        if( pEnd->GetNode().IsContentNode() &&
            !rDoc.GetNodes()[ pEnd->GetNodeIndex() + 1 ]->IsContentNode() &&
            pEnd->GetContentIndex() == pEnd->GetNode().GetContentNode()->Len()    )
        {
            const SwRangeRedline* pRedl = rDoc.getIDocumentRedlineAccess().GetRedline( *pEnd, nullptr );
            if( pRedl )
            {
                const SwPosition* pREnd = pRedl->End();
                if( !pREnd->GetContentIndex() && pREnd->GetNodeIndex() ==
                    pEnd->GetNodeIndex() + 1 )
                    *pEnd = *pREnd;
            }
        }
    }
 
    /// in case some text is deleted, ensure that the not-yet-inserted
    /// SwRangeRedline has its positions corrected not to point to deleted node
    class TemporaryRedlineUpdater
    {
    private:
        SwRangeRedline & m_rRedline;
        std::shared_ptr<SwUnoCursor> m_pCursor;
    public:
        TemporaryRedlineUpdater(SwDoc & rDoc, SwRangeRedline & rRedline)
            : m_rRedline(rRedline)
            , m_pCursor(rDoc.CreateUnoCursor(*rRedline.GetPoint(), false))
        {
            if (m_rRedline.HasMark())
            {
                m_pCursor->SetMark();
                *m_pCursor->GetMark() = *m_rRedline.GetMark();
                m_rRedline.GetMark()->Assign(rDoc.GetNodes().GetEndOfContent());
            }
            m_rRedline.GetPoint()->Assign(rDoc.GetNodes().GetEndOfContent());
        }
        ~TemporaryRedlineUpdater()
        {
            static_cast<SwPaM&>(m_rRedline) = *m_pCursor;
        }
    };
}
 
namespace sw
{
 
DocumentRedlineManager::DocumentRedlineManager(SwDoc& i_rSwdoc)
    : m_rDoc(i_rSwdoc)
    , meRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)
    , mbIsRedlineMove(false)
    , mnAutoFormatRedlnCommentNo(0)
{
}
 
RedlineFlags DocumentRedlineManager::GetRedlineFlags() const
{
    return meRedlineFlags;
}
 
void DocumentRedlineManager::SetRedlineFlags( RedlineFlags eMode )
{
    if( meRedlineFlags == eMode )
        return;
 
    if( (RedlineFlags::ShowMask & meRedlineFlags) != (RedlineFlags::ShowMask & eMode)
        || !(RedlineFlags::ShowMask & eMode) )
    {
        bool bSaveInXMLImportFlag = m_rDoc.IsInXMLImport();
        m_rDoc.SetInXMLImport( false );
        // and then hide/display everything
        void (SwRangeRedline::*pFnc)(sal_uInt16, size_t, bool); // Allow compiler warn if use of
                                                          // uninitialized ptr is possible
 
        RedlineFlags eShowMode = RedlineFlags::ShowMask & eMode;
        if (eShowMode == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete))
            pFnc = &SwRangeRedline::Show;
        else if (eShowMode == RedlineFlags::ShowInsert)
            pFnc = &SwRangeRedline::Hide;
        else if (eShowMode == RedlineFlags::ShowDelete)
            pFnc = &SwRangeRedline::ShowOriginal;
        else
        {
            pFnc = &SwRangeRedline::Hide;
            eMode |= RedlineFlags::ShowInsert;
        }
 
        CheckAnchoredFlyConsistency(m_rDoc);
        CHECK_REDLINE( *this )
 
        o3tl::sorted_vector<SwRootFrame *> hiddenLayouts;
        if (eShowMode == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete))
        {
            // sw_redlinehide: the problem here is that MoveFromSection
            // creates the frames wrongly (non-merged), because its own
            // SwRangeRedline has wrong positions until after the nodes
            // are all moved, so fix things up by force by re-creating
            // all merged frames from scratch.
            o3tl::sorted_vector<SwRootFrame *> const layouts(m_rDoc.GetAllLayouts());
            for (SwRootFrame *const pLayout : layouts)
            {
                if (pLayout->IsHideRedlines())
                {
                    pLayout->SetHideRedlines(false);
                    hiddenLayouts.insert(pLayout);
                }
            }
        }
 
        for (sal_uInt16 nLoop = 1; nLoop <= 2; ++nLoop)
            for (size_t i = 0; i < maRedlineTable.size(); )
            {
                SwRangeRedline *const pRedline = maRedlineTable[i];
                (pRedline->*pFnc)(nLoop, i, false);
                // a previous redline may have been deleted
                if (i < maRedlineTable.size() && maRedlineTable[i] == pRedline)
                    ++i;
            }
 
        //SwRangeRedline::MoveFromSection routinely changes
        //the keys that mpRedlineTable is sorted by
        maRedlineTable.Resort();
 
        CheckAnchoredFlyConsistency(m_rDoc);
        CHECK_REDLINE( *this )
 
        for (SwRootFrame *const pLayout : hiddenLayouts)
        {
            pLayout->SetHideRedlines(true);
        }
 
        m_rDoc.SetInXMLImport( bSaveInXMLImportFlag );
    }
    meRedlineFlags = eMode;
    m_rDoc.getIDocumentState().SetModified();
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
bool DocumentRedlineManager::IsRedlineOn() const
{
    return IDocumentRedlineAccess::IsRedlineOn(meRedlineFlags);
}
 
bool DocumentRedlineManager::IsIgnoreRedline() const
{
    return bool(RedlineFlags::Ignore & meRedlineFlags);
}
 
void DocumentRedlineManager::SetRedlineFlags_intern(RedlineFlags eMode)
{
    meRedlineFlags = eMode;
}
 
const SwRedlineTable& DocumentRedlineManager::GetRedlineTable() const
{
    return maRedlineTable;
}
 
SwRedlineTable& DocumentRedlineManager::GetRedlineTable()
{
    return maRedlineTable;
}
 
const SwExtraRedlineTable& DocumentRedlineManager::GetExtraRedlineTable() const
{
    return maExtraRedlineTable;
}
 
SwExtraRedlineTable& DocumentRedlineManager::GetExtraRedlineTable()
{
    return maExtraRedlineTable;
}
 
bool DocumentRedlineManager::IsInRedlines(const SwNode & rNode) const
{
    if (&rNode.GetNodes() != &m_rDoc.GetNodes())
        return false;
 
    SwPosition aPos(rNode);
    SwNode & rEndOfRedlines = m_rDoc.GetNodes().GetEndOfRedlines();
    SwPaM aPam(SwPosition(*rEndOfRedlines.StartOfSectionNode()),
               SwPosition(rEndOfRedlines));
 
    return aPam.ContainsPosition(aPos);
}
 
bool DocumentRedlineManager::IsRedlineMove() const
{
    return mbIsRedlineMove;
}
 
void DocumentRedlineManager::SetRedlineMove(bool bFlag)
{
    mbIsRedlineMove = bFlag;
}
 
/*
Text means Text not "polluted" by Redlines.
 
Behaviour of Insert-Redline:
    - in the Text                       - insert Redline Object
    - in InsertRedline (own)            - ignore, existing is extended
    - in InsertRedline (others)         - split up InsertRedline and
                                          insert Redline Object
    - in DeleteRedline                  - split up DeleteRedline or
                                          move at the end/beginning
 
Behaviour of Delete-Redline:
    - in the Text                       - insert Redline Object
    - in DeleteRedline (own/others)     - ignore
    - in InsertRedline (own)            - ignore, but delete character
    - in InsertRedline (others)         - split up InsertRedline and
                                          insert Redline Object
    - Text and own Insert overlap       - delete Text in the own Insert,
                                          extend in the other Text
                                          (up to the Insert!)
    - Text and other Insert overlap     - insert Redline Object, the
                                          other Insert is overlapped by
                                          the Delete
*/
IDocumentRedlineAccess::AppendResult
DocumentRedlineManager::AppendRedline(SwRangeRedline* pNewRedl, bool const bCallDelete,
                                      sal_uInt32 nMoveIDToDelete)
{
    CHECK_REDLINE( *this )
 
    if (!IsRedlineOn() || IsShowOriginal(meRedlineFlags))
    {
        if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() )
        {
            RedlineFlags eOld = meRedlineFlags;
            // Set to NONE, so that the Delete::Redo merges the Redline data correctly!
            // The ShowMode needs to be retained!
            meRedlineFlags = eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore);
            m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl );
            meRedlineFlags = eOld;
        }
        delete pNewRedl;
        pNewRedl = nullptr;
        CHECK_REDLINE( *this )
        return AppendResult::IGNORED;
    }
 
    // Collect MoveID's of the redlines we delete.
    // If there is only 1, then we should use its ID. (continuing the move)
    std::set<sal_uInt32> deletedMoveIDs;
 
    bool bMerged = false;
 
    pNewRedl->InvalidateRange(SwRangeRedline::Invalidation::Add);
 
    if( m_rDoc.IsAutoFormatRedline() )
    {
        pNewRedl->SetAutoFormat();
        if( moAutoFormatRedlnComment && !moAutoFormatRedlnComment->isEmpty() )
        {
            pNewRedl->SetComment( *moAutoFormatRedlnComment );
            pNewRedl->SetSeqNo( mnAutoFormatRedlnCommentNo );
        }
    }
 
    auto [pStt, pEnd] = pNewRedl->StartEnd(); // SwPosition*
    {
        SwTextNode* pTextNode = pStt->GetNode().GetTextNode();
        if( pTextNode == nullptr )
        {
            if( pStt->GetContentIndex() > 0 )
            {
                OSL_ENSURE( false, "Redline start: non-text-node with content" );
                pStt->SetContent( 0 );
            }
        }
        else
        {
            if( pStt->GetContentIndex() > pTextNode->Len() )
            {
                OSL_ENSURE( false, "Redline start: index after text" );
                pStt->SetContent( pTextNode->Len() );
            }
        }
        pTextNode = pEnd->GetNode().GetTextNode();
        if( pTextNode == nullptr )
        {
            if( pEnd->GetContentIndex() > 0 )
            {
                OSL_ENSURE( false, "Redline end: non-text-node with content" );
                pEnd->SetContent(0);
            }
        }
        else
        {
            if( pEnd->GetContentIndex() > pTextNode->Len() )
            {
                OSL_ENSURE( false, "Redline end: index after text" );
                pEnd->SetContent( pTextNode->Len() );
            }
        }
    }
    if( ( *pStt == *pEnd ) &&
        ( pNewRedl->GetContentIdx() == nullptr ) )
    {   // Do not insert empty redlines
        delete pNewRedl;
        return AppendResult::IGNORED;
    }
    bool bCompress = false;
    SwRedlineTable::size_type n = 0;
    // look up the first Redline for the starting position
    if( !GetRedline( *pStt, &n ) && n > 0 )
        --n;
    const SwRedlineTable::size_type nStartPos = n;
    bool bDec = false;
 
    for( ; pNewRedl && n < maRedlineTable.size(); bDec ? n : ++n )
    {
        bDec = false;
 
        SwRangeRedline* pRedl = maRedlineTable[ n ];
        auto [pRStt, pREnd] = pRedl->StartEnd();
 
        // #i8518# remove empty redlines while we're at it
        if( ( *pRStt == *pREnd ) &&
            ( pRedl->GetContentIdx() == nullptr ) )
        {
            maRedlineTable.DeleteAndDestroy(n);
            continue;
        }
 
        SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRStt, *pREnd );
 
        if ( SwComparePosition::Before == eCmpPos && !IsPrevPos( *pEnd, *pRStt ))
            break;
 
        switch( pNewRedl->GetType() )
        {
        case RedlineType::Insert:
            switch( pRedl->GetType() )
            {
            case RedlineType::Insert:
                if( pRedl->IsOwnRedline( *pNewRedl ) &&
                    // don't join inserted characters with moved text
                    !pRedl->IsMoved() )
                {
                    bool bDelete = false;
                    bool bMaybeNotify = false;
 
                    // Merge if applicable?
                    if( (( SwComparePosition::Behind == eCmpPos &&
                           IsPrevPos( *pREnd, *pStt ) ) ||
                         ( SwComparePosition::CollideStart == eCmpPos ) ||
                         ( SwComparePosition::OverlapBehind == eCmpPos ) ) &&
                        pRedl->CanCombine( *pNewRedl ) &&
                        ( n+1 >= maRedlineTable.size() ||
                         ( *maRedlineTable[ n+1 ]->Start() >= *pEnd &&
                         *maRedlineTable[ n+1 ]->Start() != *pREnd ) ) )
                    {
                        pRedl->SetEnd( *pEnd, pREnd );
                        if( !pRedl->HasValidRange() )
                        {
                            // re-insert
                            maRedlineTable.Remove( n );
                            maRedlineTable.Insert( pRedl );
                        }
 
                        bMerged = true;
                        bDelete = true;
                    }
                    else if( (( SwComparePosition::Before == eCmpPos &&
                                IsPrevPos( *pEnd, *pRStt ) ) ||
                               ( SwComparePosition::CollideEnd == eCmpPos ) ||
                              ( SwComparePosition::OverlapBefore == eCmpPos ) ) &&
                        pRedl->CanCombine( *pNewRedl ) &&
                        ( !n ||
                         *maRedlineTable[ n-1 ]->End() != *pRStt ))
                    {
                        pRedl->SetStart( *pStt, pRStt );
                        // re-insert
                        maRedlineTable.Remove( n );
                        maRedlineTable.Insert( pRedl );
 
                        bMerged = true;
                        bDelete = true;
                    }
                    else if ( SwComparePosition::Outside == eCmpPos )
                    {
                        // own insert-over-insert redlines:
                        // just scrap the inside ones
                        maRedlineTable.DeleteAndDestroy( n );
                        bDec = true;
                    }
                    else if( SwComparePosition::OverlapBehind == eCmpPos )
                    {
                        *pStt = *pREnd;
                        if( ( *pStt == *pEnd ) &&
                            ( pNewRedl->GetContentIdx() == nullptr ) )
                            bDelete = bMaybeNotify = true;
                    }
                    else if( SwComparePosition::OverlapBefore == eCmpPos )
                    {
                        *pEnd = *pRStt;
                        if( ( *pStt == *pEnd ) &&
                            ( pNewRedl->GetContentIdx() == nullptr ) )
                            bDelete = bMaybeNotify = true;
                    }
                    else if( SwComparePosition::Inside == eCmpPos )
                    {
                        bDelete = bMaybeNotify = true;
                        bMerged = true;
                    }
                    else if( SwComparePosition::Equal == eCmpPos )
                        bDelete = bMaybeNotify = true;
 
                    if( bDelete )
                    {
                        delete pNewRedl;
                        pNewRedl = nullptr;
                        bCompress = true;
 
                        if (bMaybeNotify)
                            MaybeNotifyRedlineModification(*pRedl, m_rDoc);
 
                        // set IsMoved checking nearby redlines
                        if (n < maRedlineTable.size()) // in case above 're-insert' failed
                            maRedlineTable.isMoved(n);
                    }
                }
                else if( SwComparePosition::Inside == eCmpPos )
                {
                    // split up
                    if( *pEnd != *pREnd )
                    {
                        SwRangeRedline* pCpy = new SwRangeRedline( *pRedl );
                        pCpy->SetStart( *pEnd );
                        maRedlineTable.Insert( pCpy );
                    }
                    pRedl->SetEnd( *pStt, pREnd );
                    if( ( *pStt == *pRStt ) &&
                        ( pRedl->GetContentIdx() == nullptr ) )
                    {
                        maRedlineTable.DeleteAndDestroy( n );
                        bDec = true;
                    }
                    else if( !pRedl->HasValidRange() )
                    {
                        // re-insert
                        maRedlineTable.Remove( n );
                        maRedlineTable.Insert( pRedl );
                    }
                }
                else if ( SwComparePosition::Outside == eCmpPos )
                {
                    // handle overlapping redlines in broken documents
 
                    // split up the new redline, since it covers the
                    // existing redline. Insert the first part, and
                    // progress with the remainder as usual
                    SwRangeRedline* pSplit = new SwRangeRedline( *pNewRedl );
                    pSplit->SetEnd( *pRStt );
                    pNewRedl->SetStart( *pREnd );
                    maRedlineTable.Insert( pSplit );
                    if( *pStt == *pEnd && pNewRedl->GetContentIdx() == nullptr )
                    {
                        delete pNewRedl;
                        pNewRedl = nullptr;
                        bCompress = true;
                    }
                }
                else if ( SwComparePosition::OverlapBehind == eCmpPos )
                {
                    // handle overlapping redlines in broken documents
                    pNewRedl->SetStart( *pREnd );
                }
                else if ( SwComparePosition::OverlapBefore == eCmpPos )
                {
                    // handle overlapping redlines in broken documents
                    *pEnd = *pRStt;
                    if( ( *pStt == *pEnd ) &&
                        ( pNewRedl->GetContentIdx() == nullptr ) )
                    {
                        delete pNewRedl;
                        pNewRedl = nullptr;
                        bCompress = true;
 
                        MaybeNotifyRedlineModification(*pRedl, m_rDoc);
                    }
                }
                break;
            case RedlineType::Delete:
                if( SwComparePosition::Inside == eCmpPos )
                {
                    // split up
                    if( *pEnd != *pREnd )
                    {
                        SwRangeRedline* pCpy = new SwRangeRedline( *pRedl );
                        pCpy->SetStart( *pEnd );
                        maRedlineTable.Insert( pCpy );
                    }
                    pRedl->SetEnd( *pStt, pREnd );
                    if( ( *pStt == *pRStt ) &&
                        ( pRedl->GetContentIdx() == nullptr ) )
                    {
                        maRedlineTable.DeleteAndDestroy( n );
                        bDec = true;
                    }
                    else if( !pRedl->HasValidRange() )
                    {
                        // re-insert
                        maRedlineTable.Remove( n );
                        maRedlineTable.Insert( pRedl, n );
                    }
                }
                else if ( SwComparePosition::Outside == eCmpPos )
                {
                    // handle overlapping redlines in broken documents
 
                    // split up the new redline, since it covers the
                    // existing redline. Insert the first part, and
                    // progress with the remainder as usual
                    SwRangeRedline* pSplit = new SwRangeRedline( *pNewRedl );
                    pSplit->SetEnd( *pRStt );
                    pNewRedl->SetStart( *pREnd );
                    maRedlineTable.Insert( pSplit );
                    if( *pStt == *pEnd && pNewRedl->GetContentIdx() == nullptr )
                    {
                        delete pNewRedl;
                        pNewRedl = nullptr;
                        bCompress = true;
                    }
                }
                else if ( SwComparePosition::Equal == eCmpPos )
                {
                    // handle identical redlines in broken documents
                    // delete old (delete) redline
                    maRedlineTable.DeleteAndDestroy( n );
                    bDec = true;
                }
                else if ( SwComparePosition::OverlapBehind == eCmpPos )
                {   // Another workaround for broken redlines
                    pNewRedl->SetStart( *pREnd );
                }
                break;
            case RedlineType::Format:
                switch( eCmpPos )
                {
                case SwComparePosition::OverlapBefore:
                    pRedl->SetStart( *pEnd, pRStt );
                    // re-insert
                    maRedlineTable.Remove( n );
                    maRedlineTable.Insert( pRedl, n );
                    bDec = true;
                    break;
 
                case SwComparePosition::OverlapBehind:
                    pRedl->SetEnd( *pStt, pREnd );
                    if( *pStt == *pRStt && pRedl->GetContentIdx() == nullptr )
                    {
                        maRedlineTable.DeleteAndDestroy( n );
                        bDec = true;
                    }
                    break;
 
                case SwComparePosition::Equal:
                case SwComparePosition::Outside:
                    // Overlaps the current one completely or has the
                    // same dimension, delete the old one
                    maRedlineTable.DeleteAndDestroy( n );
                    bDec = true;
                    break;
 
                case SwComparePosition::Inside:
                    // Overlaps the current one completely,
                    // split or shorten the new one
                    if( *pEnd != *pREnd )
                    {
                        if( *pEnd != *pRStt )
                        {
                            SwRangeRedline* pNew = new SwRangeRedline( *pRedl );
                            pNew->SetStart( *pEnd );
                            pRedl->SetEnd( *pStt, pREnd );
                            if( *pStt == *pRStt && pRedl->GetContentIdx() == nullptr )
                                maRedlineTable.DeleteAndDestroy( n );
                            AppendRedline( pNew, bCallDelete );
                            n = 0;      // re-initialize
                            bDec = true;
                        }
                    }
                    else
                        pRedl->SetEnd( *pStt, pREnd );
                    break;
                default:
                    break;
                }
                break;
            default:
                break;
            }
            break;
 
        case RedlineType::Delete:
            switch( pRedl->GetType() )
            {
            case RedlineType::Delete:
                switch( eCmpPos )
                {
                case SwComparePosition::Outside:
                    {
                        // Overlaps the current one completely,
                        // split the new one
                        if (*pEnd == *pREnd)
                        {
                            pNewRedl->SetEnd(*pRStt, pEnd);
                        }
                        else if (*pStt == *pRStt)
                        {
                            pNewRedl->SetStart(*pREnd, pStt);
                        }
                        else
                        {
                            SwRangeRedline* pNew = new SwRangeRedline( *pNewRedl );
                            pNew->SetStart( *pREnd );
                            pNewRedl->SetEnd( *pRStt, pEnd );
                            AppendRedline( pNew, bCallDelete );
                            n = 0;      // re-initialize
                            bDec = true;
                        }
                    }
                    break;
 
                case SwComparePosition::Inside:
                case SwComparePosition::Equal:
                    delete pNewRedl;
                    pNewRedl = nullptr;
                    bCompress = true;
 
                    MaybeNotifyRedlineModification(*pRedl, m_rDoc);
                    break;
 
                case SwComparePosition::OverlapBefore:
                case SwComparePosition::OverlapBehind:
                    if( pRedl->IsOwnRedline( *pNewRedl ) &&
                        pRedl->CanCombine( *pNewRedl ))
                    {
                        // If that's the case we can merge it, meaning
                        // the new one covers this well
                        if( SwComparePosition::OverlapBehind == eCmpPos )
                            pNewRedl->SetStart( *pRStt, pStt );
                        else
                            pNewRedl->SetEnd( *pREnd, pEnd );
                        maRedlineTable.DeleteAndDestroy( n );
                        bDec = true;
                    }
                    else if( SwComparePosition::OverlapBehind == eCmpPos )
                        pNewRedl->SetStart( *pREnd, pStt );
                    else
                        pNewRedl->SetEnd( *pRStt, pEnd );
                    break;
 
                case SwComparePosition::CollideEnd:
                    if (pRStt->GetContentIndex() != 0
                        && pRStt->GetNode() != pREnd->GetNode())
                    {   // tdf#147466 HACK: don't combine in this case to avoid the tdf#119571 code from *undeleting* section nodes
                        break;
                    }
                    [[fallthrough]];
                case SwComparePosition::CollideStart:
                    if( pRedl->IsOwnRedline( *pNewRedl ) &&
                        pRedl->CanCombine( *pNewRedl ) )
                    {
                        if( IsHideChanges( meRedlineFlags ))
                        {
                            // Before we can merge, we make it visible!
                            // We insert temporarily so that pNew is
                            // also dealt with when moving the indices.
                            maRedlineTable.Insert(pNewRedl);
                            pRedl->Show(0, maRedlineTable.GetPos(pRedl));
                            maRedlineTable.Remove( pNewRedl );
                            pRStt = pRedl->Start();
                            pREnd = pRedl->End();
                        }
 
                        // If that's the case we can merge it, meaning
                        // the new one covers this well
                        if( SwComparePosition::CollideStart == eCmpPos )
                            pNewRedl->SetStart( *pRStt, pStt );
                        else
                            pNewRedl->SetEnd( *pREnd, pEnd );
 
                        // delete current (below), and restart process with
                        // previous
                        SwRedlineTable::size_type nToBeDeleted = n;
                        bDec = true;
 
                        if( *(pNewRedl->Start()) <= *pREnd )
                        {
                            // Whoooah, we just extended the new 'redline'
                            // beyond previous redlines, so better start
                            // again. Of course this is not supposed to
                            // happen, and in an ideal world it doesn't,
                            // but unfortunately this code is buggy and
                            // totally rotten so it does happen and we
                            // better fix it.
                            n = 0;
                        }
 
                        maRedlineTable.DeleteAndDestroy( nToBeDeleted );
                    }
                    break;
                default:
                    break;
                }
                break;
 
            case RedlineType::Insert:
            {
                // b62341295: Do not throw away redlines
                // even if they are not allowed to be combined
                RedlineFlags eOld = meRedlineFlags;
                if( !( eOld & RedlineFlags::DontCombineRedlines ) &&
                    pRedl->IsOwnRedline( *pNewRedl ) &&
                    // tdf#116084 tdf#121176 don't combine anonymized deletion
                    // and anonymized insertion, i.e. with the same dummy timestamp
                    !pRedl->GetRedlineData(0).IsAnonymized() )
                {
                    // Collect MoveID's of the redlines we delete.
                    if (nMoveIDToDelete > 1 && maRedlineTable[n]->GetMoved() > 0
                        && (eCmpPos == SwComparePosition::Equal
                            || eCmpPos == SwComparePosition::Inside
                            || eCmpPos == SwComparePosition::Outside
                            || eCmpPos == SwComparePosition::OverlapBefore
                            || eCmpPos == SwComparePosition::OverlapBehind))
                    {
                        deletedMoveIDs.insert(maRedlineTable[n]->GetMoved());
                    }
 
                    // Set to NONE, so that the Delete::Redo merges the Redline data correctly!
                    // The ShowMode needs to be retained!
                    meRedlineFlags = eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore);
                    switch( eCmpPos )
                    {
                    case SwComparePosition::Equal:
                        bCompress = true;
                        maRedlineTable.DeleteAndDestroy( n );
                        bDec = true;
                        [[fallthrough]];
 
                    case SwComparePosition::Inside:
                        if( bCallDelete )
                        {
                            // DeleteAndJoin does not yield the
                            // desired result if there is no paragraph to
                            // join with, i.e. at the end of the document.
                            // For this case, we completely delete the
                            // paragraphs (if, of course, we also start on
                            // a paragraph boundary).
                            if( (pStt->GetContentIndex() == 0) &&
                                pEnd->GetNode().IsEndNode() )
                            {
                                pEnd->Adjust(SwNodeOffset(-1));
                                m_rDoc.getIDocumentContentOperations().DelFullPara( *pNewRedl );
                            }
                            else
                                m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl );
 
                            bCompress = true;
                        }
                        delete pNewRedl;
                        pNewRedl = nullptr;
 
                        // No need to call MaybeNotifyRedlineModification, because a notification
                        // was already sent in DocumentRedlineManager::DeleteRedline
                        break;
 
                    case SwComparePosition::Outside:
                        {
                            maRedlineTable.Remove( n );
                            bDec = true;
                            if( bCallDelete )
                            {
                                TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl);
                                m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pRedl );
                                n = 0;      // re-initialize
                            }
                            delete pRedl;
                        }
                        break;
 
                    case SwComparePosition::OverlapBefore:
                        {
                            SwPaM aPam( *pRStt, *pEnd );
 
                            if( *pEnd == *pREnd )
                                maRedlineTable.DeleteAndDestroy( n );
                            else
                            {
                                pRedl->SetStart( *pEnd, pRStt );
                                // re-insert
                                maRedlineTable.Remove( n );
                                maRedlineTable.Insert( pRedl, n );
                            }
 
                            if( bCallDelete )
                            {
                                TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl);
                                m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam );
                                n = 0;      // re-initialize
                            }
                            bDec = true;
                        }
                        break;
 
                    case SwComparePosition::OverlapBehind:
                        {
                            SwPaM aPam( *pStt, *pREnd );
 
                            if( *pStt == *pRStt )
                            {
                                maRedlineTable.DeleteAndDestroy( n );
                                bDec = true;
                            }
                            else
                                pRedl->SetEnd( *pStt, pREnd );
 
                            if( bCallDelete )
                            {
                                TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl);
                                m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam );
                                n = 0;      // re-initialize
                                bDec = true;
                            }
                        }
                        break;
                    default:
                        break;
                    }
 
                    meRedlineFlags = eOld;
                }
                else
                {
                    // it may be necessary to split the existing redline in
                    // two. In this case, pRedl will be changed to cover
                    // only part of its former range, and pNew will cover
                    // the remainder.
                    SwRangeRedline* pNew = nullptr;
 
                    switch( eCmpPos )
                    {
                    case SwComparePosition::Equal:
                        {
                            pRedl->PushData( *pNewRedl );
                            delete pNewRedl;
                            pNewRedl = nullptr;
                            if( IsHideChanges( meRedlineFlags ))
                            {
                                pRedl->Hide(0, maRedlineTable.GetPos(pRedl));
                            }
                            bCompress = true;
 
                            // set IsMoved checking nearby redlines
                            SwRedlineTable::size_type nRIdx = maRedlineTable.GetPos(pRedl);
                            if (nRIdx < maRedlineTable.size()) // in case above 're-insert' failed
                                maRedlineTable.isMoved(nRIdx);
 
                        }
                        break;
 
                    case SwComparePosition::Inside:
                        {
                            if( *pRStt == *pStt )
                            {
                                // #i97421#
                                // redline w/out extent loops
                                if (*pStt != *pEnd)
                                {
                                    pNewRedl->PushData( *pRedl, false );
                                    pRedl->SetStart( *pEnd, pRStt );
                                    // re-insert
                                    maRedlineTable.Remove( n );
                                    maRedlineTable.Insert( pRedl, n );
                                    bDec = true;
                                }
                            }
                            else
                            {
                                pNewRedl->PushData( *pRedl, false );
                                if( *pREnd != *pEnd )
                                {
                                    pNew = new SwRangeRedline( *pRedl );
                                    pNew->SetStart( *pEnd );
                                }
                                pRedl->SetEnd( *pStt, pREnd );
                                if( !pRedl->HasValidRange() )
                                {
                                    // re-insert
                                    maRedlineTable.Remove( n );
                                    maRedlineTable.Insert( pRedl, n );
                                }
                            }
                        }
                        break;
 
                    case SwComparePosition::Outside:
                        {
                            pRedl->PushData( *pNewRedl );
                            if( *pEnd == *pREnd )
                            {
                                pNewRedl->SetEnd( *pRStt, pEnd );
                            }
                            else if (*pStt == *pRStt)
                            {
                                pNewRedl->SetStart(*pREnd, pStt);
                            }
                            else
                            {
                                pNew = new SwRangeRedline( *pNewRedl );
                                pNew->SetEnd( *pRStt );
                                pNewRedl->SetStart( *pREnd, pStt );
                            }
                            bCompress = true;
                        }
                        break;
 
                    case SwComparePosition::OverlapBefore:
                        {
                            if( *pEnd == *pREnd )
                            {
                                pRedl->PushData( *pNewRedl );
                                pNewRedl->SetEnd( *pRStt, pEnd );
                                if( IsHideChanges( meRedlineFlags ))
                                {
                                    maRedlineTable.Insert(pNewRedl);
                                    pRedl->Hide(0, maRedlineTable.GetPos(pRedl));
                                    maRedlineTable.Remove( pNewRedl );
                                }
                            }
                            else
                            {
                                pNew = new SwRangeRedline( *pRedl );
                                pNew->PushData( *pNewRedl );
                                pNew->SetEnd( *pEnd );
                                pNewRedl->SetEnd( *pRStt, pEnd );
                                pRedl->SetStart( *pNew->End(), pRStt ) ;
                                // re-insert
                                maRedlineTable.Remove( n );
                                maRedlineTable.Insert( pRedl );
                                bDec = true;
                            }
                        }
                        break;
 
                    case SwComparePosition::OverlapBehind:
                        {
                            if( *pStt == *pRStt )
                            {
                                pRedl->PushData( *pNewRedl );
                                pNewRedl->SetStart( *pREnd, pStt );
                                if( IsHideChanges( meRedlineFlags ))
                                {
                                    maRedlineTable.Insert( pNewRedl );
                                    pRedl->Hide(0, maRedlineTable.GetPos(pRedl));
                                    maRedlineTable.Remove( pNewRedl );
                                }
                            }
                            else
                            {
                                pNew = new SwRangeRedline( *pRedl );
                                pNew->PushData( *pNewRedl );
                                pNew->SetStart( *pStt );
                                pNewRedl->SetStart( *pREnd, pStt );
                                pRedl->SetEnd( *pNew->Start(), pREnd );
                                if( !pRedl->HasValidRange() )
                                {
                                    // re-insert
                                    maRedlineTable.Remove( n );
                                    maRedlineTable.Insert( pRedl );
                                }
                            }
                        }
                        break;
                    default:
                        break;
                    }
 
                    // insert the pNew part (if it exists)
                    if( pNew )
                    {
                        maRedlineTable.Insert( pNew );
 
                        // pNew must be deleted if Insert() wasn't
                        // successful. But that can't happen, since pNew is
                        // part of the original pRedl redline.
                        // OSL_ENSURE( bRet, "Can't insert existing redline?" );
 
                        // restart (now with pRedl being split up)
                        n = 0;
                        bDec = true;
                    }
                }
            }
            break;
 
            case RedlineType::Format:
                switch( eCmpPos )
                {
                case SwComparePosition::OverlapBefore:
                    pRedl->SetStart( *pEnd, pRStt );
                    // re-insert
                    maRedlineTable.Remove( n );
                    maRedlineTable.Insert( pRedl, n );
                    bDec = true;
                    break;
 
                case SwComparePosition::OverlapBehind:
                    pRedl->SetEnd( *pStt, pREnd );
                    break;
 
                case SwComparePosition::Equal:
                case SwComparePosition::Outside:
                    // Overlaps the current one completely or has the
                    // same dimension, delete the old one
                    maRedlineTable.DeleteAndDestroy( n );
                    bDec = true;
                    break;
 
                case SwComparePosition::Inside:
                    // Overlaps the current one completely,
                    // split or shorten the new one
                    if( *pEnd != *pREnd )
                    {
                        if( *pEnd != *pRStt )
                        {
                            SwRangeRedline* pNew = new SwRangeRedline( *pRedl );
                            pNew->SetStart( *pEnd );
                            pRedl->SetEnd( *pStt, pREnd );
                            if( ( *pStt == *pRStt ) &&
                                ( pRedl->GetContentIdx() == nullptr ) )
                                maRedlineTable.DeleteAndDestroy( n );
                            AppendRedline( pNew, bCallDelete );
                            n = 0;      // re-initialize
                            bDec = true;
                        }
                    }
                    else
                        pRedl->SetEnd( *pStt, pREnd );
                    break;
                default:
                    break;
                }
                break;
            default:
                break;
            }
            break;
 
        case RedlineType::Format:
            switch( pRedl->GetType() )
            {
            case RedlineType::Insert:
            case RedlineType::Delete:
                switch( eCmpPos )
                {
                case SwComparePosition::OverlapBefore:
                    pNewRedl->SetEnd( *pRStt, pEnd );
                    break;
 
                case SwComparePosition::OverlapBehind:
                    pNewRedl->SetStart( *pREnd, pStt );
                    break;
 
                case SwComparePosition::Equal:
                case SwComparePosition::Inside:
                    delete pNewRedl;
                    pNewRedl = nullptr;
 
                    MaybeNotifyRedlineModification(*pRedl, m_rDoc);
                    break;
 
                case SwComparePosition::Outside:
                    // Overlaps the current one completely,
                    // split or shorten the new one
                    if (*pEnd == *pREnd)
                    {
                        pNewRedl->SetEnd(*pRStt, pEnd);
                    }
                    else if (*pStt == *pRStt)
                    {
                        pNewRedl->SetStart(*pREnd, pStt);
                    }
                    else
                    {
                        SwRangeRedline* pNew = new SwRangeRedline( *pNewRedl );
                        pNew->SetStart( *pREnd );
                        pNewRedl->SetEnd( *pRStt, pEnd );
                        AppendRedline( pNew, bCallDelete );
                        n = 0;      // re-initialize
                        bDec = true;
                    }
                    break;
                default:
                    break;
                }
                break;
            case RedlineType::Format:
                switch( eCmpPos )
                {
                case SwComparePosition::Outside:
                case SwComparePosition::Equal:
                    {
                        // Overlaps the current one completely or has the
                        // same dimension, delete the old one
                        maRedlineTable.DeleteAndDestroy( n );
                        bDec = true;
                    }
                    break;
 
                case SwComparePosition::Inside:
                    if( pRedl->IsOwnRedline( *pNewRedl ) &&
                        pRedl->CanCombine( *pNewRedl ))
                    {
                        // own one can be ignored completely
                        delete pNewRedl;
                        pNewRedl = nullptr;
 
                        MaybeNotifyRedlineModification(*pRedl, m_rDoc);
                    }
                    else if( *pREnd == *pEnd )
                        // or else only shorten the current one
                        pRedl->SetEnd( *pStt, pREnd );
                    else if( *pRStt == *pStt )
                    {
                        // or else only shorten the current one
                        pRedl->SetStart( *pEnd, pRStt );
                        // re-insert
                        maRedlineTable.Remove( n );
                        maRedlineTable.Insert( pRedl, n );
                        bDec = true;
                    }
                    else
                    {
                        // If it lies completely within the current one
                        // we need to split it
                        SwRangeRedline* pNew = new SwRangeRedline( *pRedl );
                        pNew->SetStart( *pEnd );
                        pRedl->SetEnd( *pStt, pREnd );
                        AppendRedline( pNew, bCallDelete );
                        n = 0;      // re-initialize
                        bDec = true;
                    }
                    break;
 
                case SwComparePosition::OverlapBefore:
                case SwComparePosition::OverlapBehind:
                    if( pRedl->IsOwnRedline( *pNewRedl ) &&
                        pRedl->CanCombine( *pNewRedl ))
                    {
                        // If that's the case we can merge it, meaning
                        // the new one covers this well
                        if( SwComparePosition::OverlapBehind == eCmpPos )
                            pNewRedl->SetStart( *pRStt, pStt );
                        else
                            pNewRedl->SetEnd( *pREnd, pEnd );
                        maRedlineTable.DeleteAndDestroy( n );
                        bDec = false;
                    }
                    else if( SwComparePosition::OverlapBehind == eCmpPos )
                        pNewRedl->SetStart( *pREnd, pStt );
                    else
                        pNewRedl->SetEnd( *pRStt, pEnd );
                    break;
 
                case SwComparePosition::CollideEnd:
                    if( pRedl->IsOwnRedline( *pNewRedl ) &&
                        pRedl->CanCombine( *pNewRedl ) &&
                        (n == 0 || *maRedlineTable[ n-1 ]->End() < *pStt))
                    {
                        // If that's the case we can merge it, meaning
                        // the new one covers this well
                        pNewRedl->SetEnd( *pREnd, pEnd );
                        maRedlineTable.DeleteAndDestroy( n );
                        bDec = true;
                    }
                    break;
                case SwComparePosition::CollideStart:
                    if( pRedl->IsOwnRedline( *pNewRedl ) &&
                        pRedl->CanCombine( *pNewRedl ) &&
                        (n+1 >= maRedlineTable.size() ||
                         (*maRedlineTable[ n+1 ]->Start() >= *pEnd &&
                          *maRedlineTable[ n+1 ]->Start() != *pREnd)))
                    {
                        // If that's the case we can merge it, meaning
                        // the new one covers this well
                        pNewRedl->SetStart( *pRStt, pStt );
                        maRedlineTable.DeleteAndDestroy( n );
                        bDec = true;
                    }
                    break;
                default:
                    break;
                }
                break;
            default:
                break;
            }
            break;
 
        case RedlineType::FmtColl:
            // How should we behave here?
            // insert as is
            break;
        default:
            break;
        }
    }
 
    if( pNewRedl )
    {
        if( ( *pStt == *pEnd ) &&
            ( pNewRedl->GetContentIdx() == nullptr ) )
        {   // Do not insert empty redlines
            delete pNewRedl;
            pNewRedl = nullptr;
        }
        else
        {
            if ( bCallDelete && RedlineType::Delete == pNewRedl->GetType() )
            {
                if ( pStt->GetContentIndex() != 0 )
                {
                    // tdf#119571 update the style of the joined paragraph
                    // after a partially deleted paragraph to show its correct style
                    // in "Show changes" mode, too. All removed paragraphs
                    // get the style of the first (partially deleted) paragraph
                    // to avoid text insertion with bad style in the deleted
                    // area later (except paragraphs of the removed tables).
 
                    SwContentNode* pDelNd = pStt->GetNode().GetContentNode();
                    // start copying the style of the first paragraph from the end of the range
                    SwContentNode* pTextNd = pEnd->GetNode().GetContentNode();
                    SwNodeIndex aIdx( pEnd->GetNode() );
                    bool bFirst = true;
 
                    while (pTextNd != nullptr && pDelNd->GetIndex() < pTextNd->GetIndex())
                    {
                        if( pTextNd->IsTextNode() )
                        {
                            SwPosition aPos(aIdx);
 
                            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
                            {
                                bCompress = true;
 
                                // split redline to store ExtraData per paragraphs
                                SwRangeRedline* pPar = new SwRangeRedline( *pNewRedl );
                                pPar->SetStart( aPos );
                                pNewRedl->SetEnd( aPos );
 
                                // get extradata for reset formatting of the modified paragraph
                                SwRedlineExtraData_FormatColl* pExtraData = lcl_CopyStyle(aPos, *pStt, false);
                                if (pExtraData)
                                {
                                    std::unique_ptr<SwRedlineExtraData_FormatColl> xRedlineExtraData;
                                    if (!bFirst)
                                        pExtraData->SetFormatAll(false);
                                    xRedlineExtraData.reset(pExtraData);
                                    pPar->SetExtraData( xRedlineExtraData.get() );
                                }
 
                                // skip empty redlines without ExtraData
                                // FIXME: maybe checking pExtraData is redundant here
                                if ( pExtraData || *pPar->Start() != *pPar->End() )
                                    maRedlineTable.Insert( pPar );
                                else
                                    delete pPar;
                            }
 
                            // modify paragraph formatting
                            lcl_CopyStyle(*pStt, aPos);
                        }
 
                        if (bFirst)
                            bFirst = false;
 
                        // Jump to the previous paragraph and if needed, skip paragraphs of
                        // the removed table(s) in the range to avoid leaving empty tables
                        // because of the non-continuous redline range over the table.
                        // FIXME: this is not enough for tables with inner redlines, where
                        // tracked deletion of the text containing such a table leaves an
                        // empty table at the place of the table (a problem inherited from OOo).
                        pTextNd = nullptr;
                        while( --aIdx > *pDelNd && !aIdx.GetNode().IsContentNode() )
                        {
                            // possible table end
                            if( aIdx.GetNode().IsEndNode() && aIdx.GetNode().FindTableNode() )
                            {
                                SwNodeIndex aIdx2 = aIdx;
                                // search table start and skip table paragraphs
                                while ( pDelNd->GetIndex() < aIdx2.GetIndex() )
                                {
                                    SwTableNode* pTable = aIdx2.GetNode().GetTableNode();
                                    if( pTable &&
                                        pTable->EndOfSectionNode()->GetIndex() == aIdx.GetIndex() )
                                    {
                                       aIdx = aIdx2;
                                       break;
                                    }
                                    --aIdx2;
                                }
                            }
                        }
 
                        if (aIdx.GetNode().IsContentNode())
                            pTextNd = aIdx.GetNode().GetContentNode();
                    }
                }
 
                // delete tables of the deletion explicitly, to avoid
                // remaining empty tables after accepting the rejection
                // and visible empty tables in Hide Changes mode
                // (this was the case, if tables have already contained
                // other tracked changes)
                // FIXME: because of recursive nature of AppendRedline,
                // this doesn't work for selections with multiple tables
                if ( m_rDoc.GetIDocumentUndoRedo().DoesUndo() )
                {
                    SwNodeIndex aSttIdx( pStt->GetNode() );
                    SwNodeIndex aEndIdx( pEnd->GetNode() );
                    while ( aSttIdx < aEndIdx )
                    {
                        if ( aSttIdx.GetNode().IsTableNode() )
                        {
                            SvxPrintItem aHasTextChangesOnly(RES_PRINT, false);
                            SwCursor aCursor( SwPosition(aSttIdx), nullptr );
                            m_rDoc.SetRowNotTracked( aCursor, aHasTextChangesOnly, /*bAll=*/true );
                        }
                        ++aSttIdx;
                    }
                }
            }
            bool const ret = maRedlineTable.Insert( pNewRedl );
            assert(ret || !pNewRedl);
            if (ret && !pNewRedl)
            {
                bMerged = true; // treat InsertWithValidRanges as "merge"
            }
        }
    }
 
    // If we deleted moved redlines, and there was only 1 MoveID, then we should use that
    // We overwrite those that was given right now, so it cannot be deeper under other redline
    if (nMoveIDToDelete > 1 && deletedMoveIDs.size() == 1)
    {
        sal_uInt32 nNewMoveID = *(deletedMoveIDs.begin());
        if (nNewMoveID > 1)     // MoveID==1 is for old, unrecognised moves, leave them alone
        {
            for (n = 0; n < maRedlineTable.size(); ++n)
            {
                if (maRedlineTable[n]->GetMoved() == nMoveIDToDelete)
                {
                    maRedlineTable[n]->SetMoved(nNewMoveID);
                }
            }
        }
    }
 
    if( bCompress )
        CompressRedlines(nStartPos);
 
    CHECK_REDLINE( *this )
 
    return (nullptr != pNewRedl)
        ? AppendResult::APPENDED
        : (bMerged ? AppendResult::MERGED : AppendResult::IGNORED);
}
 
bool DocumentRedlineManager::AppendTableRowRedline( SwTableRowRedline* pNewRedl )
{
    // #TODO - equivalent for 'SwTableRowRedline'
    /*
    CHECK_REDLINE( this )
    */
 
    if (IsRedlineOn() && !IsShowOriginal(meRedlineFlags))
    {
        // #TODO - equivalent for 'SwTableRowRedline'
        /*
        pNewRedl->InvalidateRange();
        */
 
        // Make equivalent of 'AppendRedline' checks inside here too
 
        maExtraRedlineTable.Insert( pNewRedl );
    }
    else
    {
        // TO DO - equivalent for 'SwTableRowRedline'
        /*
        if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() )
        {
            RedlineFlags eOld = meRedlineFlags;
            // Set to NONE, so that the Delete::Redo merges the Redline data correctly!
            // The ShowMode needs to be retained!
            meRedlineFlags = eOld & ~(RedlineFlags::On | RedlineFlags::Ignore);
            DeleteAndJoin( *pNewRedl );
            meRedlineFlags = eOld;
        }
        delete pNewRedl, pNewRedl = 0;
        */
    }
    // #TODO - equivalent for 'SwTableRowRedline'
    /*
    CHECK_REDLINE( this )
    */
 
    return nullptr != pNewRedl;
}
 
bool DocumentRedlineManager::AppendTableCellRedline( SwTableCellRedline* pNewRedl )
{
    // #TODO - equivalent for 'SwTableCellRedline'
    /*
    CHECK_REDLINE( this )
    */
 
    if (IsRedlineOn() && !IsShowOriginal(meRedlineFlags))
    {
        // #TODO - equivalent for 'SwTableCellRedline'
        /*
        pNewRedl->InvalidateRange();
        */
 
        // Make equivalent of 'AppendRedline' checks inside here too
 
        maExtraRedlineTable.Insert( pNewRedl );
    }
    else
    {
        // TO DO - equivalent for 'SwTableCellRedline'
        /*
        if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() )
        {
            RedlineFlags eOld = meRedlineFlags;
            // Set to NONE, so that the Delete::Redo merges the Redline data correctly!
            // The ShowMode needs to be retained!
            meRedlineFlags = eOld & ~(RedlineFlags::On | RedlineFlags::Ignore);
            DeleteAndJoin( *pNewRedl );
            meRedlineFlags = eOld;
        }
        delete pNewRedl, pNewRedl = 0;
        */
    }
    // #TODO - equivalent for 'SwTableCellRedline'
    /*
    CHECK_REDLINE( this )
    */
 
    return nullptr != pNewRedl;
}
 
void DocumentRedlineManager::CompressRedlines(size_t nStartIndex)
{
    CHECK_REDLINE( *this )
 
    void (SwRangeRedline::*pFnc)(sal_uInt16, size_t, bool) = nullptr;
    RedlineFlags eShow = RedlineFlags::ShowMask & meRedlineFlags;
    if( eShow == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete))
        pFnc = &SwRangeRedline::Show;
    else if (eShow == RedlineFlags::ShowInsert)
        pFnc = &SwRangeRedline::Hide;
 
    // Try to merge identical ones
    if (nStartIndex == 0)
        nStartIndex = 1;
    for( SwRedlineTable::size_type n = nStartIndex; n < maRedlineTable.size(); ++n )
    {
        SwRangeRedline* pPrev = maRedlineTable[ n-1 ],
                    * pCur = maRedlineTable[ n ];
        auto [pPrevStt,pPrevEnd] = pPrev->StartEnd();
        auto [pCurStt, pCurEnd] = pCur->StartEnd();
 
        if( *pPrevEnd == *pCurStt && pPrev->CanCombine( *pCur ) &&
            pPrevStt->GetNode().StartOfSectionNode() ==
            pCurEnd->GetNode().StartOfSectionNode() &&
            !pCurEnd->GetNode().StartOfSectionNode()->IsTableNode() )
        {
            // we then can merge them
            SwRedlineTable::size_type nPrevIndex = n-1;
            pPrev->Show(0, nPrevIndex);
            pCur->Show(0, n);
 
            pPrev->SetEnd( *pCur->End() );
            maRedlineTable.DeleteAndDestroy( n );
            --n;
            if( pFnc )
                (pPrev->*pFnc)(0, nPrevIndex, false);
        }
    }
    CHECK_REDLINE( *this )
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
bool DocumentRedlineManager::SplitRedline( const SwPaM& rRange )
{
    if (maRedlineTable.empty())
        return false;
    auto [pStt, pEnd] = rRange.StartEnd(); // SwPosition*
    // tdf#144208 this happens a lot during load of some DOCX files.
    if (*pEnd > maRedlineTable.GetMaxEndPos())
        return false;
    bool bChg = false;
    SwRedlineTable::size_type n = 0;
    //FIXME overlapping problem GetRedline( *pStt, &n );
    for ( ; n < maRedlineTable.size(); ++n)
    {
        SwRangeRedline * pRedline = maRedlineTable[ n ];
        auto [pRedlineStart, pRedlineEnd] = pRedline->StartEnd();
        if (*pRedlineStart <= *pStt && *pEnd <= *pRedlineEnd)
        {
            bChg = true;
            int nn = 0;
            if (*pStt == *pRedlineStart)
                nn += 1;
            if (*pEnd == *pRedlineEnd)
                nn += 2;
 
            SwRangeRedline* pNew = nullptr;
            switch( nn )
            {
            case 0:
                pNew = new SwRangeRedline( *pRedline );
                pRedline->SetEnd( *pStt, pRedlineEnd );
                pNew->SetStart( *pEnd );
                break;
 
            case 1:
                *pRedlineStart = *pEnd;
                break;
 
            case 2:
                *pRedlineEnd = *pStt;
                break;
 
            case 3:
                pRedline->InvalidateRange(SwRangeRedline::Invalidation::Remove);
                maRedlineTable.DeleteAndDestroy( n-- );
                pRedline = nullptr;
                break;
            }
            if (pRedline && !pRedline->HasValidRange())
            {
                // re-insert
                maRedlineTable.Remove( n );
                maRedlineTable.Insert( pRedline, n );
            }
            if( pNew )
                maRedlineTable.Insert( pNew, n );
        }
        else if (*pEnd < *pRedlineStart)
            break;
    }
    return bChg;
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
bool DocumentRedlineManager::DeleteRedline( const SwPaM& rRange, bool bSaveInUndo,
                            RedlineType nDelType )
{
    if( !rRange.HasMark() || *rRange.GetMark() == *rRange.GetPoint() )
        return false;
 
    bool bChg = false;
 
    if (bSaveInUndo && m_rDoc.GetIDocumentUndoRedo().DoesUndo())
    {
        std::unique_ptr<SwUndoRedline> pUndo(new SwUndoRedline( SwUndoId::REDLINE, rRange ));
        if( pUndo->GetRedlSaveCount() )
        {
            m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
        }
    }
 
    auto [pStt, pEnd] = rRange.StartEnd(); // SwPosition*
    SwRedlineTable::size_type n = 0;
    GetRedline( *pStt, &n );
    while (n < maRedlineTable.size())
    {
        SwRangeRedline* pRedl = maRedlineTable[ n ];
        if( RedlineType::Any != nDelType && nDelType != pRedl->GetType() )
        {
            ++n;
            continue;
        }
 
        auto [pRStt, pREnd] = pRedl->StartEnd(); // SwPosition*
        switch( ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ) )
        {
        case SwComparePosition::Equal:
        case SwComparePosition::Outside:
            pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove);
            maRedlineTable.DeleteAndDestroy( n );
            bChg = true;
            break;
 
        case SwComparePosition::OverlapBefore:
                pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove);
                pRedl->SetStart( *pEnd, pRStt );
                pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add);
                // re-insert
                maRedlineTable.Remove( n );
                maRedlineTable.Insert( pRedl );
            break;
 
        case SwComparePosition::OverlapBehind:
                pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove);
                pRedl->SetEnd( *pStt, pREnd );
                pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add);
                if( !pRedl->HasValidRange() )
                {
                    // re-insert
                    maRedlineTable.Remove( n );
                    maRedlineTable.Insert( pRedl );
                }
                else
                    ++n;
            break;
 
        case SwComparePosition::Inside:
            {
                // this one needs to be split
                pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove);
                if( *pRStt == *pStt )
                {
                    pRedl->SetStart( *pEnd, pRStt );
                    pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add);
                    // re-insert
                    maRedlineTable.Remove( n );
                    maRedlineTable.Insert( pRedl );
                }
                else
                {
                    SwRangeRedline* pCpy;
                    if( *pREnd != *pEnd )
                    {
                        pCpy = new SwRangeRedline( *pRedl );
                        pCpy->SetStart( *pEnd );
                        pCpy->InvalidateRange(SwRangeRedline::Invalidation::Add);
                    }
                    else
                        pCpy = nullptr;
                    pRedl->SetEnd( *pStt, pREnd );
                    pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add);
                    if( !pRedl->HasValidRange() )
                    {
                        // re-insert
                        maRedlineTable.Remove( n );
                        maRedlineTable.Insert( pRedl );
                    }
                    else
                        ++n;
                    if( pCpy )
                        maRedlineTable.Insert( pCpy );
                }
            }
            break;
 
        case SwComparePosition::CollideEnd:
            // remove (not hidden) empty redlines created for fixing tdf#119571
            // (Note: hidden redlines are all empty, i.e. start and end are equal.)
            if ( pRedl->HasMark() && *pRedl->GetMark() == *pRedl->GetPoint() )
            {
                pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove);
                maRedlineTable.DeleteAndDestroy( n );
                bChg = true;
                break;
            }
            [[fallthrough]];
 
        case SwComparePosition::Before:
            n = maRedlineTable.size() + 1;
            break;
        default:
            ++n;
            break;
        }
    }
 
    if( bChg )
        m_rDoc.getIDocumentState().SetModified();
 
    return bChg;
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
bool DocumentRedlineManager::DeleteRedline( const SwStartNode& rNode, bool bSaveInUndo,
                            RedlineType nDelType )
{
    SwPaM aTemp(*rNode.EndOfSectionNode(), rNode);
    return DeleteRedline(aTemp, bSaveInUndo, nDelType);
}
 
SwRedlineTable::size_type DocumentRedlineManager::GetRedlinePos( const SwNode& rNd, RedlineType nType ) const
{
    const SwNodeOffset nNdIdx = rNd.GetIndex();
    // if the table only contains good (i.e. non-overlapping) data, we can do a binary search
    if (!maRedlineTable.HasOverlappingElements())
    {
        // binary search to the first redline with end >= the needle
        auto it = std::lower_bound(maRedlineTable.begin(), maRedlineTable.end(), rNd,
            [&nNdIdx](const SwRangeRedline* lhs, const SwNode& /*rhs*/)
            {
                return lhs->End()->GetNodeIndex() < nNdIdx;
            });
        for( ; it != maRedlineTable.end(); ++it)
        {
            const SwRangeRedline* pTmp = *it;
            auto [pStart, pEnd] = pTmp->StartEnd(); // SwPosition*
            SwNodeOffset nStart = pStart->GetNodeIndex(),
                         nEnd = pEnd->GetNodeIndex();
 
            if( ( RedlineType::Any == nType || nType == pTmp->GetType()) &&
                nStart <= nNdIdx && nNdIdx <= nEnd )
                return std::distance(maRedlineTable.begin(), it);
 
            if( nStart > nNdIdx )
                break;
        }
    }
    else
    {
        for( auto it = maRedlineTable.begin(), itEnd = maRedlineTable.end(); it != itEnd; ++it )
        {
            const SwRangeRedline* pTmp = *it;
            SwNodeOffset nPt = pTmp->GetPoint()->GetNodeIndex(),
                  nMk = pTmp->GetMark()->GetNodeIndex();
            if( nPt < nMk )
                std::swap( nMk, nPt );
 
            if( ( RedlineType::Any == nType || nType == pTmp->GetType()) &&
                nMk <= nNdIdx && nNdIdx <= nPt )
                return std::distance(maRedlineTable.begin(), it);
 
            if( nMk > nNdIdx )
                break;
        }
    }
    return SwRedlineTable::npos;
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
SwRedlineTable::size_type
DocumentRedlineManager::GetRedlineEndPos(SwRedlineTable::size_type nStartPos, const SwNode& rNd,
                                         RedlineType nType) const
{
    //if the start is already invalid
    if (nStartPos >= maRedlineTable.size())
        return nStartPos;
 
    const SwNodeOffset nNdIdx = rNd.GetIndex();
    SwRedlineTable::size_type nEndPos = nStartPos;
    SwRedlineTable::size_type nEndPosTry = nEndPos + 1;
 
    while (nEndPosTry < maRedlineTable.size()
           && maRedlineTable[nEndPosTry]->Start()->GetNodeIndex() <= nNdIdx)
    {
        if (RedlineType::Any == nType || nType == maRedlineTable[nEndPosTry]->GetType())
        {
            nEndPos = nEndPosTry;
        }
        nEndPosTry++;
    }
    return nEndPos;
}
 
void DocumentRedlineManager::UpdateRedlineContentNode(SwRedlineTable::size_type nStartPos,
                                                      SwRedlineTable::size_type nEndPos) const
{
    for (SwRedlineTable::size_type n = nStartPos; n <= nEndPos; ++n)
    {
        //just in case we got wrong input
        if (n >= maRedlineTable.size())
            return;
 
        SwPosition* pStart = maRedlineTable[n]->Start();
        SwPosition* pEnd = maRedlineTable[n]->End();
        SwContentNode* pCont = pStart->GetNode().GetContentNode();
        if (pCont)
        {
            pStart->nContent.Assign(pCont, pStart->nContent.GetIndex());
        }
        pCont = pEnd->GetNode().GetContentNode();
        if (pCont)
        {
            pEnd->nContent.Assign(pCont, pEnd->nContent.GetIndex());
        }
    }
}
 
bool DocumentRedlineManager::HasRedline( const SwPaM& rPam, RedlineType nType, bool bStartOrEndInRange ) const
{
    SwPosition currentStart(*rPam.Start());
    SwPosition currentEnd(*rPam.End());
    const SwNode& rEndNode(currentEnd.GetNode());
 
    for( SwRedlineTable::size_type n = GetRedlinePos( rPam.Start()->GetNode(), nType );
                    n < maRedlineTable.size(); ++n )
    {
        const SwRangeRedline* pTmp = maRedlineTable[ n ];
 
        if ( pTmp->Start()->GetNode() > rEndNode )
            break;
 
        if( RedlineType::Any != nType && nType != pTmp->GetType() )
            continue;
 
        // redline over the range
        if ( currentStart < *pTmp->End() && *pTmp->Start() <= currentEnd &&
             // starting or ending within the range
             ( !bStartOrEndInRange ||
                 ( currentStart < *pTmp->Start() || *pTmp->End() < currentEnd ) ) )
        {
            return true;
        }
    }
    return false;
}
 
const SwRangeRedline* DocumentRedlineManager::GetRedline( const SwPosition& rPos,
                                    SwRedlineTable::size_type* pFndPos ) const
{
    SwRedlineTable::size_type nO = maRedlineTable.size(), nM, nU = 0;
    if( nO > 0 )
    {
        nO--;
        while( nU <= nO )
        {
            nM = nU + ( nO - nU ) / 2;
            const SwRangeRedline* pRedl = maRedlineTable[ nM ];
            auto [pStt, pEnd] = pRedl->StartEnd();
            if( pEnd == pStt
                    ? *pStt == rPos
                    : ( *pStt <= rPos && rPos < *pEnd ) )
            {
                while( nM && rPos == *maRedlineTable[ nM - 1 ]->End() &&
                    rPos == *maRedlineTable[ nM - 1 ]->Start() )
                {
                    --nM;
                    pRedl = maRedlineTable[ nM ];
                }
                // if there are format and insert changes in the same position
                // show insert change first.
                // since the redlines are sorted by position, only check the redline
                // before and after the current redline
                if( RedlineType::Format == pRedl->GetType() )
                {
                    if( nM && rPos >= *maRedlineTable[ nM - 1 ]->Start() &&
                        rPos <= *maRedlineTable[ nM - 1 ]->End() &&
                        ( RedlineType::Insert == maRedlineTable[ nM - 1 ]->GetType() ) )
                    {
                        --nM;
                        pRedl = maRedlineTable[ nM ];
                    }
                    else if( ( nM + 1 ) <= nO && rPos >= *maRedlineTable[ nM + 1 ]->Start() &&
                        rPos <= *maRedlineTable[ nM + 1 ]->End() &&
                        ( RedlineType::Insert == maRedlineTable[ nM + 1 ]->GetType() ) )
                    {
                        ++nM;
                        pRedl = maRedlineTable[ nM ];
                    }
                }
 
                if( pFndPos )
                    *pFndPos = nM;
                return pRedl;
            }
            else if( *pEnd <= rPos )
                nU = nM + 1;
            else if( nM == 0 )
            {
                if( pFndPos )
                    *pFndPos = nU;
                return nullptr;
            }
            else
                nO = nM - 1;
        }
    }
    if( pFndPos )
        *pFndPos = nU;
    return nullptr;
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
bool DocumentRedlineManager::AcceptRedlineRange(SwRedlineTable::size_type nPosOrigin,
                                                SwRedlineTable::size_type& nPosStart,
                                                SwRedlineTable::size_type& nPosEnd,
                                                bool bCallDelete)
{
    bool bRet = false;
 
    SwRangeRedline* pTmp = maRedlineTable[nPosOrigin];
    SwRedlineTable::size_type nRdlIdx = nPosEnd + 1;
    SwRedlineData aOrigData = pTmp->GetRedlineData(0);
 
    SwNodeOffset nPamStartNI = maRedlineTable[nPosStart]->Start()->GetNodeIndex();
    sal_Int32 nPamStartCI = maRedlineTable[nPosStart]->Start()->GetContentIndex();
    SwNodeOffset nPamEndtNI = maRedlineTable[nPosEnd]->End()->GetNodeIndex();
    sal_Int32 nPamEndCI = maRedlineTable[nPosEnd]->End()->GetContentIndex();
    do
    {
        nRdlIdx--;
        pTmp = maRedlineTable[nRdlIdx];
        if (pTmp->Start()->GetNodeIndex() < nPamStartNI
            || (pTmp->Start()->GetNodeIndex() == nPamStartNI
                && pTmp->Start()->GetContentIndex() < nPamStartCI))
            break;
 
        if (pTmp->End()->GetNodeIndex() > nPamEndtNI
            || (pTmp->End()->GetNodeIndex() == nPamEndtNI
                && pTmp->End()->GetContentIndex() > nPamEndCI))
        {
        }
        else if (pTmp->GetRedlineData(0).CanCombineForAcceptReject(aOrigData))
        {
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(
                    std::make_unique<SwUndoAcceptRedline>(*pTmp));
            }
            nPamEndtNI = pTmp->Start()->GetNodeIndex();
            nPamEndCI = pTmp->Start()->GetContentIndex();
            bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
            nRdlIdx++; //we will decrease it in the loop anyway.
        }
        else if (aOrigData.GetType() == RedlineType::Insert
                 && pTmp->GetType() == RedlineType::Delete && pTmp->GetStackCount() > 1
                 && pTmp->GetType(1) == RedlineType::Insert
                 && pTmp->GetRedlineData(1).CanCombineForAcceptReject(aOrigData))
        {
            // The Insert redline we want to accept has a deletion redline too
            // we should leave the deletion redline, and only accept the inner insert.
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(
                    std::make_unique<SwUndoAcceptRedline>(*pTmp, 1));
            }
            nPamEndtNI = pTmp->Start()->GetNodeIndex();
            nPamEndCI = pTmp->Start()->GetContentIndex();
            bRet |= lcl_AcceptInnerInsertRedline(maRedlineTable, nRdlIdx, 1);
            nRdlIdx++; //we will decrease it in the loop anyway.
        }
    } while (nRdlIdx > 0);
    return bRet;
}
 
bool DocumentRedlineManager::AcceptMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete)
{
    assert(nMovedID > 1);   // 0, and 1 is reserved
    bool bRet = false;
    SwRedlineTable::size_type nRdlIdx = maRedlineTable.size();
 
    while (nRdlIdx > 0)
    {
        nRdlIdx--;
        SwRangeRedline* pTmp = maRedlineTable[nRdlIdx];
        if (pTmp->GetMoved(0) == nMovedID
            || (pTmp->GetStackCount() > 1 && pTmp->GetMoved(1) == nMovedID))
        {
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(
                    std::make_unique<SwUndoAcceptRedline>(*pTmp));
            }
 
            if (pTmp->GetMoved(0) == nMovedID)
                bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
            else
                bRet |= lcl_AcceptInnerInsertRedline(maRedlineTable, nRdlIdx, 1);
 
            nRdlIdx++; //we will decrease it in the loop anyway.
        }
    }
    return bRet;
}
 
bool DocumentRedlineManager::AcceptRedline(SwRedlineTable::size_type nPos, bool bCallDelete,
                                           bool bRange)
{
    bool bRet = false;
 
    // Switch to visible in any case
    if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
        (RedlineFlags::ShowMask & meRedlineFlags) )
      SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags );
 
    SwRangeRedline* pTmp = maRedlineTable[ nPos ];
    bool bAnonym = pTmp->GetRedlineData(0).IsAnonymized();
 
    pTmp->Show(0, maRedlineTable.GetPos(pTmp), /*bForced=*/true);
    pTmp->Show(1, maRedlineTable.GetPos(pTmp), /*bForced=*/true);
    if( pTmp->HasMark() && pTmp->IsVisible() )
    {
        if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
        {
            SwRewriter aRewriter;
 
            aRewriter.AddRule(UndoArg1, pTmp->GetDescr());
            m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::ACCEPT_REDLINE, &aRewriter);
        }
 
        int nLoopCnt = 2;
        sal_uInt16 nSeqNo = pTmp->GetSeqNo();
 
        if (bRange && !nSeqNo && !bAnonym
            && !pTmp->Start()->GetNode().StartOfSectionNode()->IsTableNode())
        {
            sal_uInt32 nMovedID = pTmp->GetMoved(0);
            if (nMovedID > 1)
            {
                // Accept all redlineData with this unique move id
                bRet |= AcceptMovedRedlines(nMovedID, bCallDelete);
            }
            else
            {
                SwRedlineTable::size_type nPosStart = nPos;
                SwRedlineTable::size_type nPosEnd = nPos;
 
                maRedlineTable.getConnectedArea(nPos, nPosStart, nPosEnd, true);
 
                // Accept redlines between pPamStart-pPamEnd.
                // but only those that can be combined with the selected.
                bRet |= AcceptRedlineRange(nPos, nPosStart, nPosEnd, bCallDelete);
            }
        }
        else do {
 
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(
                    std::make_unique<SwUndoAcceptRedline>(*pTmp) );
            }
 
            bRet |= lcl_AcceptRedline( maRedlineTable, nPos, bCallDelete );
 
            if( nSeqNo )
            {
                if( SwRedlineTable::npos == nPos )
                    nPos = 0;
                SwRedlineTable::size_type nFndPos = 2 == nLoopCnt
                                    ? maRedlineTable.FindNextSeqNo( nSeqNo, nPos )
                                    : maRedlineTable.FindPrevSeqNo( nSeqNo, nPos );
                if( SwRedlineTable::npos != nFndPos || ( 0 != ( --nLoopCnt ) &&
                    SwRedlineTable::npos != ( nFndPos =
                        maRedlineTable.FindPrevSeqNo( nSeqNo, nPos ))) )
                {
                    nPos = nFndPos;
                    pTmp = maRedlineTable[ nPos ];
                }
                else
                    nLoopCnt = 0;
            }
            else
                nLoopCnt = 0;
 
        } while (nLoopCnt);
 
        if( bRet )
        {
            CompressRedlines();
            m_rDoc.getIDocumentState().SetModified();
        }
 
        if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
        {
            m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr);
        }
    }
    return bRet;
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
bool DocumentRedlineManager::AcceptRedline( const SwPaM& rPam, bool bCallDelete, sal_Int8 nDepth )
{
    // Switch to visible in any case
    if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
        (RedlineFlags::ShowMask & meRedlineFlags) )
      SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags );
 
    // The Selection is only in the ContentSection. If there are Redlines
    // to Non-ContentNodes before or after that, then the Selections
    // expand to them.
    std::shared_ptr<SwUnoCursor> const pPam(m_rDoc.CreateUnoCursor(*rPam.GetPoint(), false));
    if (rPam.HasMark())
    {
        pPam->SetMark();
        *pPam->GetMark() = *rPam.GetMark();
    }
    lcl_AdjustRedlineRange(*pPam);
 
    if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
    {
        m_rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::ACCEPT_REDLINE, nullptr );
        m_rDoc.GetIDocumentUndoRedo().AppendUndo(
            std::make_unique<SwUndoAcceptRedline>(*pPam, nDepth));
    }
 
    int nRet = 0;
    if (nDepth == 0)
    {
        nRet = lcl_AcceptRejectRedl(lcl_AcceptRedline, maRedlineTable, bCallDelete, *pPam);
    }
    else
    {
        // For now it is called only if it is an Insert redline in a delete redline.
        SwRedlineTable::size_type nRdlIdx = 0;
        maRedlineTable.FindAtPosition(*rPam.Start(), nRdlIdx);
        if (lcl_AcceptInnerInsertRedline(maRedlineTable, nRdlIdx, 1))
            nRet = 1;
    }
    if( nRet > 0 )
    {
        CompressRedlines();
        m_rDoc.getIDocumentState().SetModified();
    }
    if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
    {
        OUString aTmpStr;
 
        {
            SwRewriter aRewriter;
            aRewriter.AddRule(UndoArg1, OUString::number(nRet));
            aTmpStr = aRewriter.Apply(SwResId(STR_N_REDLINES));
        }
 
        SwRewriter aRewriter;
        aRewriter.AddRule(UndoArg1, aTmpStr);
 
        m_rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::ACCEPT_REDLINE, &aRewriter );
    }
    return nRet != 0;
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
void DocumentRedlineManager::AcceptRedlineParagraphFormatting( const SwPaM &rPam )
{
    auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
 
    const SwNodeOffset nSttIdx = pStt->GetNodeIndex();
    const SwNodeOffset nEndIdx = pEnd->GetNodeIndex();
 
    for( SwRedlineTable::size_type n = 0; n < maRedlineTable.size() ; ++n )
    {
        const SwRangeRedline* pTmp = maRedlineTable[ n ];
        SwNodeOffset nPt = pTmp->GetPoint()->GetNodeIndex(),
              nMk = pTmp->GetMark()->GetNodeIndex();
        if( nPt < nMk )
            std::swap( nMk, nPt );
 
        if( RedlineType::ParagraphFormat == pTmp->GetType() &&
            ( (nSttIdx <= nMk && nMk <= nEndIdx) || (nSttIdx <= nPt && nPt <= nEndIdx) ) )
                AcceptRedline( n, false );
 
        if( nMk > nEndIdx )
            break;
    }
}
 
bool DocumentRedlineManager::RejectRedlineRange(SwRedlineTable::size_type nPosOrigin,
                                                SwRedlineTable::size_type& nPosStart,
                                                SwRedlineTable::size_type& nPosEnd,
                                                bool bCallDelete)
{
    bool bRet = false;
 
    SwRangeRedline* pTmp = maRedlineTable[nPosOrigin];
    SwRedlineTable::size_type nRdlIdx = nPosEnd + 1;
    SwRedlineData aOrigData = pTmp->GetRedlineData(0);
 
    SwNodeOffset nPamStartNI = maRedlineTable[nPosStart]->Start()->GetNodeIndex();
    sal_Int32 nPamStartCI = maRedlineTable[nPosStart]->Start()->GetContentIndex();
    SwNodeOffset nPamEndtNI = maRedlineTable[nPosEnd]->End()->GetNodeIndex();
    sal_Int32 nPamEndCI = maRedlineTable[nPosEnd]->End()->GetContentIndex();
    do
    {
        nRdlIdx--;
        pTmp = maRedlineTable[nRdlIdx];
        if (pTmp->Start()->GetNodeIndex() < nPamStartNI
            || (pTmp->Start()->GetNodeIndex() == nPamStartNI
                && pTmp->Start()->GetContentIndex() < nPamStartCI))
            break;
 
        if (pTmp->End()->GetNodeIndex() > nPamEndtNI
            || (pTmp->End()->GetNodeIndex() == nPamEndtNI
                && pTmp->End()->GetContentIndex() > nPamEndCI))
        {
        }
        else if (pTmp->GetRedlineData(0).CanCombineForAcceptReject(aOrigData))
        {
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                std::unique_ptr<SwUndoRejectRedline> pUndoRdl
                    = std::make_unique<SwUndoRejectRedline>(*pTmp);
#if OSL_DEBUG_LEVEL > 0
                pUndoRdl->SetRedlineCountDontCheck(true);
#endif
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl));
            }
            nPamEndtNI = pTmp->Start()->GetNodeIndex();
            nPamEndCI = pTmp->Start()->GetContentIndex();
            bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, bCallDelete);
            nRdlIdx++; //we will decrease it in the loop anyway.
        }
        else if (aOrigData.GetType() == RedlineType::Insert
                 && pTmp->GetType() == RedlineType::Delete && pTmp->GetStackCount() > 1
                 && pTmp->GetType(1) == RedlineType::Insert
                 && pTmp->GetRedlineData(1).CanCombineForAcceptReject(aOrigData))
        {
            // The Insert redline we want to reject has a deletion redline too
            // without the insert, the delete is meaningless
            // so we rather just accept the deletion redline
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                std::unique_ptr<SwUndoRejectRedline> pUndoRdl
                    = std::make_unique<SwUndoRejectRedline>(*pTmp, 1);
#if OSL_DEBUG_LEVEL > 0
                pUndoRdl->SetRedlineCountDontCheck(true);
#endif
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl));
            }
            nPamEndtNI = pTmp->Start()->GetNodeIndex();
            nPamEndCI = pTmp->Start()->GetContentIndex();
            bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
            nRdlIdx++; //we will decrease it in the loop anyway.
        }
 
    } while (nRdlIdx > 0);
    return bRet;
}
 
bool DocumentRedlineManager::RejectMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete)
{
    assert(nMovedID > 1); // 0, and 1 is reserved
    bool bRet = false;
    SwRedlineTable::size_type nRdlIdx = maRedlineTable.size();
 
    while (nRdlIdx > 0)
    {
        nRdlIdx--;
        SwRangeRedline* pTmp = maRedlineTable[nRdlIdx];
        if (pTmp->GetMoved(0) == nMovedID
            || (pTmp->GetStackCount() > 1 && pTmp->GetMoved(1) == nMovedID))
        {
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                std::unique_ptr<SwUndoRejectRedline> pUndoRdl
                    = std::make_unique<SwUndoRejectRedline>(*pTmp);
#if OSL_DEBUG_LEVEL > 0
                pUndoRdl->SetRedlineCountDontCheck(true);
#endif
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl));
            }
 
            if (pTmp->GetMoved(0) == nMovedID)
                bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, bCallDelete);
            else
                bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
 
            nRdlIdx++; //we will decrease it in the loop anyway.
        }
    }
    return bRet;
}
 
bool DocumentRedlineManager::RejectRedline(SwRedlineTable::size_type nPos,
                                           bool bCallDelete, bool bRange)
{
    bool bRet = false;
 
    // Switch to visible in any case
    if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
        (RedlineFlags::ShowMask & meRedlineFlags) )
      SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags );
 
    SwRangeRedline* pTmp = maRedlineTable[ nPos ];
    bool bAnonym = pTmp->GetRedlineData(0).IsAnonymized();
 
    pTmp->Show(0, maRedlineTable.GetPos(pTmp), /*bForced=*/true);
    pTmp->Show(1, maRedlineTable.GetPos(pTmp), /*bForced=*/true);
    if( pTmp->HasMark() && pTmp->IsVisible() )
    {
        if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
        {
            SwRewriter aRewriter;
 
            aRewriter.AddRule(UndoArg1, pTmp->GetDescr());
            m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::REJECT_REDLINE, &aRewriter);
        }
 
        int nLoopCnt = 2;
        sal_uInt16 nSeqNo = pTmp->GetSeqNo();
 
        if (bRange && !nSeqNo && !bAnonym
            && !pTmp->Start()->GetNode().StartOfSectionNode()->IsTableNode())
        {
            sal_uInt32 nMovedID = pTmp->GetMoved(0);
            if (nMovedID > 1)
            {
                // Reject all redlineData with this unique move id
                bRet |= RejectMovedRedlines(nMovedID, bCallDelete);
            }
            else
            {
                SwRedlineTable::size_type nPosStart = nPos;
                SwRedlineTable::size_type nPosEnd = nPos;
                maRedlineTable.getConnectedArea(nPos, nPosStart, nPosEnd, true);
 
                // Reject items between pPamStart-pPamEnd
                // but only those that can be combined with the selected.
 
                bRet |= RejectRedlineRange(nPos, nPosStart, nPosEnd, bCallDelete);
            }
        }
        else do {
 
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(
                    std::make_unique<SwUndoRejectRedline>( *pTmp ) );
            }
 
            bRet |= lcl_RejectRedline( maRedlineTable, nPos, bCallDelete );
 
            if( nSeqNo )
            {
                if( SwRedlineTable::npos == nPos )
                    nPos = 0;
                SwRedlineTable::size_type nFndPos = 2 == nLoopCnt
                                    ? maRedlineTable.FindNextSeqNo( nSeqNo, nPos )
                                    : maRedlineTable.FindPrevSeqNo( nSeqNo, nPos );
                if( SwRedlineTable::npos != nFndPos || ( 0 != ( --nLoopCnt ) &&
                    SwRedlineTable::npos != ( nFndPos =
                            maRedlineTable.FindPrevSeqNo( nSeqNo, nPos ))) )
                {
                    nPos = nFndPos;
                    pTmp = maRedlineTable[ nPos ];
                }
                else
                    nLoopCnt = 0;
            }
            else
                nLoopCnt = 0;
 
        } while (nLoopCnt);
 
        if( bRet )
        {
            CompressRedlines();
            m_rDoc.getIDocumentState().SetModified();
        }
 
        if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
        {
            m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr);
        }
    }
    return bRet;
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
bool DocumentRedlineManager::RejectRedline( const SwPaM& rPam, bool bCallDelete, sal_Int8 nDepth )
{
    // Switch to visible in any case
    if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
        (RedlineFlags::ShowMask & meRedlineFlags) )
      SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags );
 
    // The Selection is only in the ContentSection. If there are Redlines
    // to Non-ContentNodes before or after that, then the Selections
    // expand to them.
    SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() );
    lcl_AdjustRedlineRange( aPam );
 
    if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
    {
        m_rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::REJECT_REDLINE, nullptr );
        m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoRejectRedline>(aPam, nDepth) );
    }
 
    int nRet = 0;
    if (nDepth == 0)
    {
        nRet = lcl_AcceptRejectRedl(lcl_RejectRedline, maRedlineTable, bCallDelete, aPam);
    }
    else
    {
        // For now it is called only if it is an Insert redline in a delete redline.
        SwRedlineTable::size_type nRdlIdx = 0;
        maRedlineTable.FindAtPosition(*rPam.Start(), nRdlIdx);
        if (lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete))
            nRet = 1;
    }
 
    if( nRet > 0 )
    {
        CompressRedlines();
        m_rDoc.getIDocumentState().SetModified();
    }
    if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
    {
        OUString aTmpStr;
 
        {
            SwRewriter aRewriter;
            aRewriter.AddRule(UndoArg1, OUString::number(nRet));
            aTmpStr = aRewriter.Apply(SwResId(STR_N_REDLINES));
        }
 
        SwRewriter aRewriter;
        aRewriter.AddRule(UndoArg1, aTmpStr);
 
        m_rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::REJECT_REDLINE, &aRewriter );
    }
 
    return nRet != 0;
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
void DocumentRedlineManager::AcceptAllRedline(bool bAccept)
{
    bool bSuccess = true;
    OUString sUndoStr;
    IDocumentUndoRedo& rUndoMgr = m_rDoc.GetIDocumentUndoRedo();
 
    if (maRedlineTable.size() > 1)
    {
        {
            SwRewriter aRewriter;
            aRewriter.AddRule(UndoArg1, OUString::number(maRedlineTable.size()));
            sUndoStr = aRewriter.Apply(SwResId(STR_N_REDLINES));
        }
 
        SwRewriter aRewriter;
        aRewriter.AddRule(UndoArg1, sUndoStr);
        rUndoMgr.StartUndo(bAccept ? SwUndoId::ACCEPT_REDLINE : SwUndoId::REJECT_REDLINE, &aRewriter);
    }
 
    while (!maRedlineTable.empty() && bSuccess)
    {
        if (bAccept)
            bSuccess = AcceptRedline(maRedlineTable.size() - 1, true);
        else
            bSuccess = RejectRedline(maRedlineTable.size() - 1, true);
    }
 
    if (!sUndoStr.isEmpty())
    {
        rUndoMgr.EndUndo(SwUndoId::EMPTY, nullptr);
    }
}
 
const SwRangeRedline* DocumentRedlineManager::SelNextRedline( SwPaM& rPam ) const
{
    rPam.DeleteMark();
    rPam.SetMark();
 
    SwPosition& rSttPos = *rPam.GetPoint();
    SwPosition aSavePos( rSttPos );
    bool bRestart;
 
    // If the starting position points to the last valid ContentNode,
    // we take the next Redline in any case.
    SwRedlineTable::size_type n = 0;
    const SwRangeRedline* pFnd = GetRedlineTable().FindAtPosition( rSttPos, n );
    if( pFnd )
    {
        const SwPosition* pEnd = pFnd->End();
        if( !pEnd->GetNode().IsContentNode() )
        {
            SwNodeIndex aTmp( pEnd->GetNode() );
            SwContentNode* pCNd = SwNodes::GoPrevSection( &aTmp );
            if( !pCNd || ( aTmp == rSttPos.GetNode() &&
                pCNd->Len() == rSttPos.GetContentIndex() ))
                pFnd = nullptr;
        }
        if( pFnd )
            rSttPos = *pFnd->End();
    }
 
    do {
        bRestart = false;
 
        for( ; !pFnd && n < maRedlineTable.size(); ++n )
        {
            pFnd = maRedlineTable[ n ];
            if( pFnd->HasMark() && pFnd->IsVisible() )
            {
                *rPam.GetMark() = *pFnd->Start();
                rSttPos = *pFnd->End();
                break;
            }
            else
                pFnd = nullptr;
        }
 
        if( pFnd )
        {
            // Merge all of the same type and author that are
            // consecutive into one Selection.
            const SwPosition* pPrevEnd = pFnd->End();
            while( ++n < maRedlineTable.size() )
            {
                const SwRangeRedline* pTmp = maRedlineTable[ n ];
                if( pTmp->HasMark() && pTmp->IsVisible() )
                {
                    const SwPosition *pRStt;
                    if( pFnd->GetType() != pTmp->GetType() ||
                        pFnd->GetAuthor() != pTmp->GetAuthor() )
                        break;
                    pRStt = pTmp->Start();
                    if( *pPrevEnd == *pRStt || IsPrevPos( *pPrevEnd, *pRStt ) )
                    {
                        pPrevEnd = pTmp->End();
                        rSttPos = *pPrevEnd;
                    }
                    else
                        break;
                }
            }
        }
 
        if( pFnd )
        {
            const SwRangeRedline* pSaveFnd = pFnd;
 
            SwContentNode* pCNd;
            SwPosition* pPos = rPam.GetMark();
            if( !pPos->GetNode().IsContentNode() )
            {
                pCNd = SwNodes::GoNextSection(pPos);
                if( pCNd )
                {
                    if( pPos->GetNode() <= rPam.GetPoint()->GetNode() )
                        pPos->Assign( *pCNd, 0 );
                    else
                        pFnd = nullptr;
                }
            }
 
            if( pFnd )
            {
                pPos = rPam.GetPoint();
                if( !pPos->GetNode().IsContentNode() )
                {
                    pCNd = SwNodes::GoPrevSection( pPos );
                    if( pCNd )
                    {
                        if( pPos->GetNode() >= rPam.GetMark()->GetNode() )
                            pPos->Assign( *pCNd, pCNd->Len() );
                        else
                            pFnd = nullptr;
                    }
                }
            }
 
            if( !pFnd || *rPam.GetMark() == *rPam.GetPoint() )
            {
                if( n < maRedlineTable.size() )
                {
                    bRestart = true;
                    *rPam.GetPoint() = *pSaveFnd->End();
                }
                else
                {
                    rPam.DeleteMark();
                    *rPam.GetPoint() = aSavePos;
                }
                pFnd = nullptr;
            }
        }
    } while( bRestart );
 
    return pFnd;
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
const SwRangeRedline* DocumentRedlineManager::SelPrevRedline( SwPaM& rPam ) const
{
    rPam.DeleteMark();
    rPam.SetMark();
 
    SwPosition& rSttPos = *rPam.GetPoint();
    SwPosition aSavePos( rSttPos );
    bool bRestart;
 
    // If the starting position points to the last valid ContentNode,
    // we take the previous Redline in any case.
    SwRedlineTable::size_type n = 0;
    const SwRangeRedline* pFnd = GetRedlineTable().FindAtPosition( rSttPos, n, false );
    if( pFnd )
    {
        const SwPosition* pStt = pFnd->Start();
        if( !pStt->GetNode().IsContentNode() )
        {
            SwNodeIndex aTmp( pStt->GetNode() );
            SwContentNode* pCNd = SwNodes::GoNextSection(&aTmp);
            if( !pCNd || ( aTmp == rSttPos.GetNode() &&
                !rSttPos.GetContentIndex() ))
                pFnd = nullptr;
        }
        if( pFnd )
            rSttPos = *pFnd->Start();
    }
 
    do {
        bRestart = false;
 
        while( !pFnd && 0 < n )
        {
            pFnd = maRedlineTable[ --n ];
            if( pFnd->HasMark() && pFnd->IsVisible() )
            {
                *rPam.GetMark() = *pFnd->End();
                rSttPos = *pFnd->Start();
            }
            else
                pFnd = nullptr;
        }
 
        if( pFnd )
        {
            // Merge all of the same type and author that are
            // consecutive into one Selection.
            const SwPosition* pNextStt = pFnd->Start();
            while( 0 < n )
            {
                const SwRangeRedline* pTmp = maRedlineTable[ --n ];
                if( pTmp->HasMark() && pTmp->IsVisible() )
                {
                    const SwPosition *pREnd;
                    if( pFnd->GetType() == pTmp->GetType() &&
                        pFnd->GetAuthor() == pTmp->GetAuthor() &&
                        ( *pNextStt == *( pREnd = pTmp->End() ) ||
                          IsPrevPos( *pREnd, *pNextStt )) )
                    {
                        pNextStt = pTmp->Start();
                        rSttPos = *pNextStt;
                    }
                    else
                    {
                        ++n;
                        break;
                    }
                }
            }
        }
 
        if( pFnd )
        {
            const SwRangeRedline* pSaveFnd = pFnd;
 
            SwContentNode* pCNd;
            SwPosition* pPos = rPam.GetMark();
            if( !pPos->GetNode().IsContentNode() )
            {
                pCNd = SwNodes::GoPrevSection( pPos );
                if( pCNd )
                {
                    if( pPos->GetNode() >= rPam.GetPoint()->GetNode() )
                        pPos->Assign( *pCNd, pCNd->Len() );
                    else
                        pFnd = nullptr;
                }
            }
 
            if( pFnd )
            {
                pPos = rPam.GetPoint();
                if( !pPos->GetNode().IsContentNode() )
                {
                    pCNd = SwNodes::GoNextSection(pPos);
                    if( pCNd )
                    {
                        if( pPos->GetNode() <= rPam.GetMark()->GetNode() )
                            pPos->Assign( *pCNd, 0 );
                        else
                            pFnd = nullptr;
                    }
                }
            }
 
            if( !pFnd || *rPam.GetMark() == *rPam.GetPoint() )
            {
                if( n )
                {
                    bRestart = true;
                    *rPam.GetPoint() = *pSaveFnd->Start();
                }
                else
                {
                    rPam.DeleteMark();
                    *rPam.GetPoint() = aSavePos;
                }
                pFnd = nullptr;
            }
        }
    } while( bRestart );
 
    return pFnd;
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
// Set comment at the Redline
bool DocumentRedlineManager::SetRedlineComment( const SwPaM& rPaM, const OUString& rS )
{
    bool bRet = false;
    auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition*
    SwRedlineTable::size_type n = 0;
    if( GetRedlineTable().FindAtPosition( *pStt, n ) )
    {
        for( ; n < maRedlineTable.size(); ++n )
        {
            bRet = true;
            SwRangeRedline* pTmp = maRedlineTable[ n ];
            if( pStt != pEnd && *pTmp->Start() > *pEnd )
                break;
 
            pTmp->SetComment( rS );
            if( *pTmp->End() >= *pEnd )
                break;
        }
    }
    if( bRet )
        m_rDoc.getIDocumentState().SetModified();
 
    return bRet;
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
// Create a new author if necessary
std::size_t DocumentRedlineManager::GetRedlineAuthor()
{
    return SW_MOD()->GetRedlineAuthor();
}
 
/// Insert new author into the Table for the Readers etc.
std::size_t DocumentRedlineManager::InsertRedlineAuthor( const OUString& rNew )
{
    return SW_MOD()->InsertRedlineAuthor(rNew);
}
 
void DocumentRedlineManager::UpdateRedlineAttr()
{
    const SwRedlineTable& rTable = GetRedlineTable();
    for(SwRangeRedline* pRedl : rTable)
    {
        if( pRedl->IsVisible() )
            pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add);
    }
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
const uno::Sequence <sal_Int8>& DocumentRedlineManager::GetRedlinePassword() const
{
    return maRedlinePasswd;
}
 
void DocumentRedlineManager::SetRedlinePassword(
            /*[in]*/const uno::Sequence <sal_Int8>& rNewPassword)
{
    maRedlinePasswd = rNewPassword;
    m_rDoc.getIDocumentState().SetModified();
}
 
/// Set comment text for the Redline, which is inserted later on via
/// AppendRedline. Is used by Autoformat.
/// A null pointer resets the mode. The pointer is not copied, so it
/// needs to stay valid!
void DocumentRedlineManager::SetAutoFormatRedlineComment( const OUString* pText, sal_uInt16 nSeqNo )
{
    m_rDoc.SetAutoFormatRedline( nullptr != pText );
    if( pText )
    {
        moAutoFormatRedlnComment = *pText;
    }
    else
    {
        moAutoFormatRedlnComment.reset();
    }
 
    mnAutoFormatRedlnCommentNo = nSeqNo;
}
 
void DocumentRedlineManager::HideAll( bool bDeletion )
{
    const SwRedlineTable& rTable = GetRedlineTable();
    for (SwRedlineTable::size_type i = rTable.size(); i > 0; --i)
    {
        SwRangeRedline* pRedline = rTable[i-1];
        if ( pRedline->GetType() == RedlineType::Delete )
        {
            if ( bDeletion && pRedline->IsVisible() )
            {
                pRedline->Hide(0, rTable.GetPos(pRedline), false);
                pRedline->Hide(1, rTable.GetPos(pRedline), false);
            }
            else if ( !bDeletion && !pRedline->IsVisible() )
            {
                pRedline->Show(0, rTable.GetPos(pRedline), true);
                pRedline->Show(1, rTable.GetPos(pRedline), true);
            }
        }
        else if ( pRedline->GetType() == RedlineType::Insert )
        {
            if ( !bDeletion && pRedline->IsVisible() )
            {
                pRedline->ShowOriginal(0, rTable.GetPos(pRedline), false);
                pRedline->ShowOriginal(1, rTable.GetPos(pRedline), false);
            }
            else if ( bDeletion && !pRedline->IsVisible() )
            {
                pRedline->Show(0, rTable.GetPos(pRedline), true);
                pRedline->Show(1, rTable.GetPos(pRedline), true);
            }
        }
    }
}
 
void DocumentRedlineManager::ShowAll()
{
    const SwRedlineTable& rTable = GetRedlineTable();
    for (SwRedlineTable::size_type i = rTable.size(); i > 0; --i)
    {
        SwRangeRedline* pRedline = rTable[i-1];
        if ( !pRedline->IsVisible() )
        {
            pRedline->Show(0, rTable.GetPos(pRedline), true);
            pRedline->Show(1, rTable.GetPos(pRedline), true);
        }
    }
}
 
DocumentRedlineManager::~DocumentRedlineManager()
{
}
 
}
 
/* 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 'Assign' is required to be utilized.

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

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

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

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

V773 The return value of function 'lcl_CopyStyle' is required to be utilized. A memory leak is possible.

V773 The return value of function 'lcl_CopyStyle' is required to be utilized. A memory leak is possible.