/* -*- 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>
#include <istyleaccess.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
    std::unique_ptr<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
            SfxItemSet aTmp(SfxItemSet::makeFixedSfxItemSet<
                        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>(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))
            {
                IStyleAccess& rStyleAccess = rDoc.GetIStyleAccess();
                std::shared_ptr<SfxItemSet> pAutoStyle = rStyleAccess.getAutomaticStyle(aTmp2, IStyleAccess::AUTO_STYLE_CHAR);
                return std::make_unique<SwRedlineExtraData_FormatColl>( pFromColl->GetName(), USHRT_MAX, pAutoStyle );
            }
        }
        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* pStart = pRedl->Start();
                    SwTextNode* pTNd = pStart->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;
    }
 
    /// Given a redline that has another underlying redline, drop that underlying redline.
    /// Used to accept an insert or rejecting a delete, i.e. no changes to the text node strings.
    bool lcl_DeleteInnerRedline(const SwRedlineTable& rArr, const 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;
    }
 
    /// Given a redline that has an other underlying redline, drop the redline on top.
    /// Used to accept a format on top of insert/delete, no changes to the text node string.
    bool lcl_AcceptOuterFormat(SwRedlineTable& rArr, SwRedlineTable::size_type& rPos)
    {
        SwRangeRedline* pRedl = rArr[rPos];
        return pRedl->PopData();
    }
 
    /// Given a redline that has an other underlying redline, drop the redline on top & restore the
    /// old doc model. Used to reject a format on top of insert/delete.
    bool lcl_RejectOuterFormat(SwRedlineTable& rArr, SwRedlineTable::size_type& rPos)
    {
        SwRangeRedline* pRedl = rArr[rPos];
        SwDoc& rDoc = pRedl->GetDoc();
        SwPaM aPam(*(pRedl->Start()), *(pRedl->End()));
        rDoc.ResetAttrs(aPam);
        if (pRedl->GetExtraData())
            pRedl->GetExtraData()->Reject(*pRedl);
        return pRedl->PopData();
    }
 
    /// Given a redline that has two types and the underlying type is
    /// delete, reject the redline based on that underlying type. Used
    /// to accept a delete-then-format, i.e. this does change the text
    /// node string.
    bool lcl_AcceptInnerDelete(SwRangeRedline& rRedline, SwRedlineTable& rRedlines,
                               SwRedlineTable::size_type& rRedlineIndex, bool bCallDelete)
    {
        bool bRet = false;
 
        SwDoc& rDoc = rRedline.GetDoc();
        SwPaM aPam(*rRedline.Start(), *rRedline.End());
        bRet |= lcl_RejectRedline(rRedlines, rRedlineIndex, bCallDelete);
        // Handles undo/redo itself.
        rDoc.getIDocumentContentOperations().DeleteRange(aPam);
 
        return bRet;
    }
 
    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* pStart = rPam.Start(),
                        * pEnd = rPam.End();
        const SwRangeRedline* pFnd = rArr.FindAtPosition( *pStart, n );
        if( pFnd &&     // Is new a part of it?
            ( *pFnd->Start() != *pStart || *pFnd->End() > *pEnd ))
        {
            // Only revoke the partial selection
            if( (*fn_AcceptReject)( rArr, n, bCallDelete, pStart, 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, pStart, 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 [pStart, pEnd] = rPam.StartEnd(); // SwPosition*
        SwDoc& rDoc = rPam.GetDoc();
        if( !pStart->GetContentIndex() &&
            !rDoc.GetNodes()[ pStart->GetNodeIndex() - 1 ]->IsContentNode() )
        {
            const SwRangeRedline* pRedl = rDoc.getIDocumentRedlineAccess().GetRedline( *pStart, nullptr );
            if( pRedl )
            {
                const SwPosition* pRStt = pRedl->Start();
                if( !pRStt->GetContentIndex() && pRStt->GetNodeIndex() ==
                    pStart->GetNodeIndex() - 1 )
                    *pStart = *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;
        }
    };
 
/// Decides if it's OK to combine two types of redlines next to each other, e.g. insert and
/// delete-on-insert can be combined if accepting an insert.
bool CanCombineTypesForAcceptReject(SwRedlineData& rInnerData, SwRangeRedline& rOuterRedline)
{
    if (rInnerData.GetType() == RedlineType::Delete)
    {
        // Delete is OK to have 'format' on it, but 'insert' will be next to the 'delete'.
        return rOuterRedline.GetType() == RedlineType::Format
            && rOuterRedline.GetStackCount() > 1
            && rOuterRedline.GetType(1) == RedlineType::Delete;
    }
 
    if (rInnerData.GetType() != RedlineType::Insert)
    {
        return false;
    }
 
    switch (rOuterRedline.GetType())
    {
        case RedlineType::Delete:
        case RedlineType::Format:
            break;
        default:
            return false;
    }
 
    if (rOuterRedline.GetStackCount() <= 1)
    {
        return false;
    }
 
    if (rOuterRedline.GetType(1) != RedlineType::Insert)
    {
        return false;
    }
 
    return true;
}
 
/// Decides if it's OK to combine this rInnerData having 2 types with an outer rOuterRedline for
/// accept or reject purposes. E.g. format-on-delete and delete can be combined if accepting a
/// delete.
bool CanReverseCombineTypesForAcceptReject(SwRangeRedline& rOuterRedline, SwRedlineData& rInnerData)
{
    switch (rOuterRedline.GetType())
    {
        case RedlineType::Insert:
        case RedlineType::Delete:
            break;
        default:
            return false;
    }
 
    if (rInnerData.GetType() != RedlineType::Format)
    {
        return false;
    }
 
    const SwRedlineData* pInnerDataNext = rInnerData.Next();
    if (!pInnerDataNext)
    {
        return false;
    }
 
    switch (pInnerDataNext->GetType())
    {
        case RedlineType::Insert:
        case RedlineType::Delete:
            return pInnerDataNext->GetType() == rOuterRedline.GetType();
        default:
            return false;
    }
}
}
 
namespace sw
{
 
DocumentRedlineManager::DocumentRedlineManager(SwDoc& i_rSwdoc)
    : m_rDoc(i_rSwdoc)
    , meRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)
    , mbIsRedlineMove(false)
    , mnAutoFormatRedlnCommentNo(0)
{
}
 
RedlineFlags DocumentRedlineManager::GetRedlineFlags(const SwViewShell* pViewShell) const
{
    if (!pViewShell)
    {
        SwDocShell* pDocShell = m_rDoc.GetDocShell();
        if (pDocShell)
        {
            pViewShell = pDocShell->GetWrtShell();
        }
    }
 
    RedlineFlags eRedlineFlags = meRedlineFlags;
 
    if (pViewShell)
    {
        // Recording can be per-view, the rest is per-document.
        eRedlineFlags = eRedlineFlags & ~RedlineFlags::On;
        if (pViewShell->GetViewOptions()->IsRedlineRecordingOn())
        {
            eRedlineFlags |= RedlineFlags::On;
        }
    }
 
    return eRedlineFlags;
}
 
void DocumentRedlineManager::SetRedlineFlags( RedlineFlags eMode, SfxRedlineRecordingMode eRedlineRecordingMode, bool bRecordModeChange )
{
    if( GetRedlineFlags() == eMode && !bRecordModeChange )
        return;
 
    if( (RedlineFlags::ShowMask & GetRedlineFlags()) != (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 );
    }
    SetRedlineFlags_intern(eMode, eRedlineRecordingMode, bRecordModeChange);
    m_rDoc.getIDocumentState().SetModified();
 
    // #TODO - add 'SwExtraRedlineTable' also ?
}
 
bool DocumentRedlineManager::IsRedlineOn() const
{
    return IDocumentRedlineAccess::IsRedlineOn(GetRedlineFlags());
}
 
bool DocumentRedlineManager::IsIgnoreRedline() const
{
    return bool(RedlineFlags::Ignore & GetRedlineFlags());
}
 
void DocumentRedlineManager::SetRedlineFlags_intern(RedlineFlags eMode, SfxRedlineRecordingMode eRedlineRecordingMode, bool bRecordModeChange)
{
    SwDocShell* pDocShell = m_rDoc.GetDocShell();
    SwViewShell* pViewShell = pDocShell ? pDocShell->GetWrtShell() : nullptr;
    if (pViewShell && eRedlineRecordingMode == SfxRedlineRecordingMode::ViewAgnostic)
    {
        // Just set the requested flags on the model and on the current view, so setting flags &
        // restoring them result in the same state (no matter if that was this-view or all-views).
        auto bRedlineRecordingOn = bool(eMode & RedlineFlags::On);
        SwViewOption aOpt(*pViewShell->GetViewOptions());
        if (aOpt.IsRedlineRecordingOn() != bRedlineRecordingOn)
        {
            aOpt.SetRedlineRecordingOn(bRedlineRecordingOn);
            pViewShell->ApplyViewOptions(aOpt);
        }
    }
    else if (pViewShell)
    {
        bool bRecordAllViews = eRedlineRecordingMode == SfxRedlineRecordingMode::AllViews;
        // Recording may be per-view, the rest is per-document.
        for(SwViewShell& rSh : pViewShell->GetRingContainer())
        {
            auto bRedlineRecordingOn = bool(eMode & RedlineFlags::On);
            SwViewOption aOpt(*rSh.GetViewOptions());
            bool bOn = aOpt.IsRedlineRecordingOn();
            if (bRedlineRecordingOn)
            {
                // We'll want some kind of recording enabled.
                if (bRecordAllViews)
                {
                    // Enable for all views: turn it on everywhere.
                    bOn = true;
                }
                else
                {
                    // Enable it for this view was requested.
                    if (bRecordModeChange)
                    {
                        // Transitioning from "all views" to "this view", turn it off everywhere
                        // except in this view.
                        bOn = &rSh == pViewShell;
                    }
                    else if (&rSh == pViewShell)
                    {
                        // Transitioning from "no record": just touch the current view, leave
                        // others unchanged.
                        bOn = true;
                    }
                }
            }
            else
            {
                // Disable everywhere.
                bOn = false;
            }
 
            if (aOpt.IsRedlineRecordingOn() != bOn)
            {
                aOpt.SetRedlineRecordingOn(bOn);
                rSh.ApplyViewOptions(aOpt);
            }
        }
    }
 
    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;
}
 
/// Data shared between DocumentRedlineManager::AppendRedline() and PreAppendInsertRedline().
class AppendRedlineContext
{
public:
    SwRangeRedline*& pNewRedl;
    SwPosition*& pStart;
    SwPosition*& pEnd;
 
    SwRangeRedline*& pRedl;
    SwPosition*& pRStt;
    SwPosition*& pREnd;
 
    const SwComparePosition eCmpPos;
    SwRedlineTable::size_type& n;
    bool& bMerged;
    bool& bDec;
    bool& bCompress;
    const bool bCallDelete;
 
    const sal_uInt32 nMoveIDToDelete;
    std::set<sal_uInt32>& deletedMoveIDs;
};
 
void DocumentRedlineManager::PreAppendForeignRedline(AppendRedlineContext& rCtx)
{
    // 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( rCtx.eCmpPos )
    {
    case SwComparePosition::Equal:
        {
            rCtx.pRedl->PushData( *rCtx.pNewRedl );
            delete rCtx.pNewRedl;
            rCtx.pNewRedl = nullptr;
            if( IsHideChanges( GetRedlineFlags() ))
            {
                rCtx.pRedl->Hide(0, maRedlineTable.GetPos(rCtx.pRedl));
            }
            rCtx.bCompress = true;
 
            if (rCtx.pNewRedl && rCtx.pNewRedl->GetType() == RedlineType::Delete)
            {
                // set IsMoved checking nearby redlines
                SwRedlineTable::size_type nRIdx = maRedlineTable.GetPos(rCtx.pRedl);
                if (nRIdx < maRedlineTable.size()) // in case above 're-insert' failed
                    maRedlineTable.isMoved(nRIdx);
            }
 
        }
        break;
 
    case SwComparePosition::Inside:
        {
            if( *rCtx.pRStt == *rCtx.pStart )
            {
                // #i97421#
                // redline w/out extent loops
                if (*rCtx.pStart != *rCtx.pEnd)
                {
                    rCtx.pNewRedl->PushData( *rCtx.pRedl, false );
                    rCtx.pRedl->SetStart( *rCtx.pEnd, rCtx.pRStt );
                    // re-insert
                    maRedlineTable.Remove( rCtx.n );
                    maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
                    rCtx.bDec = true;
                }
            }
            else
            {
                rCtx.pNewRedl->PushData( *rCtx.pRedl, false );
                if( *rCtx.pREnd != *rCtx.pEnd )
                {
                    pNew = new SwRangeRedline( *rCtx.pRedl );
                    pNew->SetStart( *rCtx.pEnd );
                }
                rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
                if( !rCtx.pRedl->HasValidRange() )
                {
                    // re-insert
                    maRedlineTable.Remove( rCtx.n );
                    maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
                }
            }
        }
        break;
 
    case SwComparePosition::Outside:
        {
            rCtx.pRedl->PushData( *rCtx.pNewRedl );
            if( *rCtx.pEnd == *rCtx.pREnd )
            {
                rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
            }
            else if (*rCtx.pStart == *rCtx.pRStt)
            {
                rCtx.pNewRedl->SetStart(*rCtx.pREnd, rCtx.pStart);
            }
            else
            {
                pNew = new SwRangeRedline( *rCtx.pNewRedl );
                pNew->SetEnd( *rCtx.pRStt );
                rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
            }
            rCtx.bCompress = true;
        }
        break;
 
    case SwComparePosition::OverlapBefore:
        {
            if( *rCtx.pEnd == *rCtx.pREnd )
            {
                rCtx.pRedl->PushData( *rCtx.pNewRedl );
                rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
                if( IsHideChanges( GetRedlineFlags() ))
                {
                    maRedlineTable.Insert(rCtx.pNewRedl);
                    rCtx.pRedl->Hide(0, maRedlineTable.GetPos(rCtx.pRedl));
                    maRedlineTable.Remove( rCtx.pNewRedl );
                }
            }
            else
            {
                pNew = new SwRangeRedline( *rCtx.pRedl );
                pNew->PushData( *rCtx.pNewRedl );
                pNew->SetEnd( *rCtx.pEnd );
                rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
                rCtx.pRedl->SetStart( *pNew->End(), rCtx.pRStt ) ;
                // re-insert
                maRedlineTable.Remove( rCtx.n );
                maRedlineTable.Insert( rCtx.pRedl );
                rCtx.bDec = true;
            }
        }
        break;
 
    case SwComparePosition::OverlapBehind:
        {
            if( *rCtx.pStart == *rCtx.pRStt )
            {
                rCtx.pRedl->PushData( *rCtx.pNewRedl );
                rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
                if( IsHideChanges( GetRedlineFlags() ))
                {
                    maRedlineTable.Insert( rCtx.pNewRedl );
                    rCtx.pRedl->Hide(0, maRedlineTable.GetPos(rCtx.pRedl));
                    maRedlineTable.Remove( rCtx.pNewRedl );
                }
            }
            else
            {
                pNew = new SwRangeRedline( *rCtx.pRedl );
                pNew->PushData( *rCtx.pNewRedl );
                pNew->SetStart( *rCtx.pStart );
                rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
                rCtx.pRedl->SetEnd( *pNew->Start(), rCtx.pREnd );
                if( !rCtx.pRedl->HasValidRange() )
                {
                    // re-insert
                    maRedlineTable.Remove( rCtx.n );
                    maRedlineTable.Insert( rCtx.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)
        rCtx.n = 0;
        rCtx.bDec = true;
    }
}
 
void DocumentRedlineManager::PreAppendInsertRedline(AppendRedlineContext& rCtx)
{
    switch( rCtx.pRedl->GetType() )
    {
    case RedlineType::Insert:
        if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
            // don't join inserted characters with moved text
            !rCtx.pRedl->IsMoved() )
        {
            bool bDelete = false;
            bool bMaybeNotify = false;
 
            // Merge if applicable?
            if( (( SwComparePosition::Behind == rCtx.eCmpPos &&
                   IsPrevPos( *rCtx.pREnd, *rCtx.pStart ) ) ||
                 ( SwComparePosition::CollideStart == rCtx.eCmpPos ) ||
                 ( SwComparePosition::OverlapBehind == rCtx.eCmpPos ) ) &&
                rCtx.pRedl->CanCombine( *rCtx.pNewRedl ) &&
                ( rCtx.n+1 >= maRedlineTable.size() ||
                 ( *maRedlineTable[ rCtx.n+1 ]->Start() >= *rCtx.pEnd &&
                 *maRedlineTable[ rCtx.n+1 ]->Start() != *rCtx.pREnd ) ) )
            {
                rCtx.pRedl->SetEnd( *rCtx.pEnd, rCtx.pREnd );
                if( !rCtx.pRedl->HasValidRange() )
                {
                    // re-insert
                    maRedlineTable.Remove( rCtx.n );
                    maRedlineTable.Insert( rCtx.pRedl );
                }
 
                rCtx.bMerged = true;
                bDelete = true;
            }
            else if( (( SwComparePosition::Before == rCtx.eCmpPos &&
                        IsPrevPos( *rCtx.pEnd, *rCtx.pRStt ) ) ||
                       ( SwComparePosition::CollideEnd == rCtx.eCmpPos ) ||
                      ( SwComparePosition::OverlapBefore == rCtx.eCmpPos ) ) &&
                rCtx.pRedl->CanCombine( *rCtx.pNewRedl ) &&
                ( !rCtx.n ||
                 *maRedlineTable[ rCtx.n-1 ]->End() != *rCtx.pRStt ))
            {
                rCtx.pRedl->SetStart( *rCtx.pStart, rCtx.pRStt );
                // re-insert
                maRedlineTable.Remove( rCtx.n );
                maRedlineTable.Insert( rCtx.pRedl );
 
                rCtx.bMerged = true;
                bDelete = true;
            }
            else if ( SwComparePosition::Outside == rCtx.eCmpPos )
            {
                // own insert-over-insert redlines:
                // just scrap the inside ones
                maRedlineTable.DeleteAndDestroy( rCtx.n );
                rCtx.bDec = true;
            }
            else if( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
            {
                *rCtx.pStart = *rCtx.pREnd;
                if( ( *rCtx.pStart == *rCtx.pEnd ) &&
                    ( rCtx.pNewRedl->GetContentIdx() == nullptr ) )
                    bDelete = bMaybeNotify = true;
            }
            else if( SwComparePosition::OverlapBefore == rCtx.eCmpPos )
            {
                *rCtx.pEnd = *rCtx.pRStt;
                if( ( *rCtx.pStart == *rCtx.pEnd ) &&
                    ( rCtx.pNewRedl->GetContentIdx() == nullptr ) )
                    bDelete = bMaybeNotify = true;
            }
            else if( SwComparePosition::Inside == rCtx.eCmpPos )
            {
                bDelete = bMaybeNotify = true;
                rCtx.bMerged = true;
            }
            else if( SwComparePosition::Equal == rCtx.eCmpPos )
                bDelete = bMaybeNotify = true;
 
            if( bDelete )
            {
                delete rCtx.pNewRedl;
                rCtx.pNewRedl = nullptr;
                rCtx.bCompress = true;
 
                if (bMaybeNotify)
                    MaybeNotifyRedlineModification(*rCtx.pRedl, m_rDoc);
 
                // set IsMoved checking nearby redlines
                if (rCtx.n < maRedlineTable.size()) // in case above 're-insert' failed
                    maRedlineTable.isMoved(rCtx.n);
            }
        }
        else if( SwComparePosition::Inside == rCtx.eCmpPos )
        {
            // split up
            if( *rCtx.pEnd != *rCtx.pREnd )
            {
                SwRangeRedline* pCpy = new SwRangeRedline( *rCtx.pRedl );
                pCpy->SetStart( *rCtx.pEnd );
                maRedlineTable.Insert( pCpy );
            }
            rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
            if( ( *rCtx.pStart == *rCtx.pRStt ) &&
                ( rCtx.pRedl->GetContentIdx() == nullptr ) )
            {
                maRedlineTable.DeleteAndDestroy( rCtx.n );
                rCtx.bDec = true;
            }
            else if( !rCtx.pRedl->HasValidRange() )
            {
                // re-insert
                maRedlineTable.Remove( rCtx.n );
                maRedlineTable.Insert( rCtx.pRedl );
            }
        }
        else if ( SwComparePosition::Outside == rCtx.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( *rCtx.pNewRedl );
            pSplit->SetEnd( *rCtx.pRStt );
            rCtx.pNewRedl->SetStart( *rCtx.pREnd );
            maRedlineTable.Insert( pSplit );
            if( *rCtx.pStart == *rCtx.pEnd && rCtx.pNewRedl->GetContentIdx() == nullptr )
            {
                delete rCtx.pNewRedl;
                rCtx.pNewRedl = nullptr;
                rCtx.bCompress = true;
            }
        }
        else if ( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
        {
            // handle overlapping redlines in broken documents
            rCtx.pNewRedl->SetStart( *rCtx.pREnd );
        }
        else if ( SwComparePosition::OverlapBefore == rCtx.eCmpPos )
        {
            // handle overlapping redlines in broken documents
            *rCtx.pEnd = *rCtx.pRStt;
            if( ( *rCtx.pStart == *rCtx.pEnd ) &&
                ( rCtx.pNewRedl->GetContentIdx() == nullptr ) )
            {
                delete rCtx.pNewRedl;
                rCtx.pNewRedl = nullptr;
                rCtx.bCompress = true;
 
                MaybeNotifyRedlineModification(*rCtx.pRedl, m_rDoc);
            }
        }
        break;
    case RedlineType::Delete:
        if( SwComparePosition::Inside == rCtx.eCmpPos )
        {
            // split up
            if( *rCtx.pEnd != *rCtx.pREnd )
            {
                SwRangeRedline* pCpy = new SwRangeRedline( *rCtx.pRedl );
                pCpy->SetStart( *rCtx.pEnd );
                maRedlineTable.Insert( pCpy );
            }
            rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
            if( ( *rCtx.pStart == *rCtx.pRStt ) &&
                ( rCtx.pRedl->GetContentIdx() == nullptr ) )
            {
                maRedlineTable.DeleteAndDestroy( rCtx.n );
                rCtx.bDec = true;
            }
            else if( !rCtx.pRedl->HasValidRange() )
            {
                // re-insert
                maRedlineTable.Remove( rCtx.n );
                maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
            }
        }
        else if ( SwComparePosition::Outside == rCtx.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( *rCtx.pNewRedl );
            pSplit->SetEnd( *rCtx.pRStt );
            rCtx.pNewRedl->SetStart( *rCtx.pREnd );
            maRedlineTable.Insert( pSplit );
            if( *rCtx.pStart == *rCtx.pEnd && rCtx.pNewRedl->GetContentIdx() == nullptr )
            {
                delete rCtx.pNewRedl;
                rCtx.pNewRedl = nullptr;
                rCtx.bCompress = true;
            }
        }
        else if ( SwComparePosition::Equal == rCtx.eCmpPos )
        {
            // handle identical redlines in broken documents
            // delete old (delete) redline
            maRedlineTable.DeleteAndDestroy( rCtx.n );
            rCtx.bDec = true;
        }
        else if ( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
        {   // Another workaround for broken redlines
            rCtx.pNewRedl->SetStart( *rCtx.pREnd );
        }
        break;
    case RedlineType::Format:
        switch( rCtx.eCmpPos )
        {
        case SwComparePosition::OverlapBefore:
            rCtx.pRedl->SetStart( *rCtx.pEnd, rCtx.pRStt );
            // re-insert
            maRedlineTable.Remove( rCtx.n );
            maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
            rCtx.bDec = true;
            break;
 
        case SwComparePosition::OverlapBehind:
            rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
            if( *rCtx.pStart == *rCtx.pRStt && rCtx.pRedl->GetContentIdx() == nullptr )
            {
                maRedlineTable.DeleteAndDestroy( rCtx.n );
                rCtx.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( rCtx.n );
            rCtx.bDec = true;
            break;
 
        case SwComparePosition::Inside:
            // Overlaps the current one completely,
            // split or shorten the new one
            if( *rCtx.pEnd != *rCtx.pREnd )
            {
                if( *rCtx.pEnd != *rCtx.pRStt )
                {
                    SwRangeRedline* pNew = new SwRangeRedline( *rCtx.pRedl );
                    pNew->SetStart( *rCtx.pEnd );
                    rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
                    if( *rCtx.pStart == *rCtx.pRStt && rCtx.pRedl->GetContentIdx() == nullptr )
                        maRedlineTable.DeleteAndDestroy( rCtx.n );
                    AppendRedline( pNew, rCtx.bCallDelete );
                    rCtx.n = 0;      // re-initialize
                    rCtx.bDec = true;
                }
            }
            else
                rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }
}
 
void DocumentRedlineManager::PreAppendDeleteRedline(AppendRedlineContext& rCtx)
{
    RedlineType eRedlType = rCtx.pRedl->GetType();
    bool bHierarchicalFormat = rCtx.pRedl->GetType() == RedlineType::Format && rCtx.pRedl->GetStackCount() > 1;
    if (bHierarchicalFormat && rCtx.pRedl->GetType(1) == RedlineType::Insert)
    {
        eRedlType = rCtx.pRedl->GetType(1);
    }
 
    switch (eRedlType)
    {
    case RedlineType::Delete:
        switch( rCtx.eCmpPos )
        {
        case SwComparePosition::Outside:
            {
                // Overlaps the current one completely,
                // split the new one
                if (*rCtx.pEnd == *rCtx.pREnd)
                {
                    rCtx.pNewRedl->SetEnd(*rCtx.pRStt, rCtx.pEnd);
                }
                else if (*rCtx.pStart == *rCtx.pRStt)
                {
                    rCtx.pNewRedl->SetStart(*rCtx.pREnd, rCtx.pStart);
                }
                else
                {
                    SwRangeRedline* pNew = new SwRangeRedline( *rCtx.pNewRedl );
                    pNew->SetStart( *rCtx.pREnd );
                    rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
                    AppendRedline( pNew, rCtx.bCallDelete );
                    rCtx.n = 0;      // re-initialize
                    rCtx.bDec = true;
                }
            }
            break;
 
        case SwComparePosition::Inside:
        case SwComparePosition::Equal:
            delete rCtx.pNewRedl;
            rCtx.pNewRedl = nullptr;
            rCtx.bCompress = true;
 
            MaybeNotifyRedlineModification(*rCtx.pRedl, m_rDoc);
            break;
 
        case SwComparePosition::OverlapBefore:
        case SwComparePosition::OverlapBehind:
            if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
                rCtx.pRedl->CanCombine( *rCtx.pNewRedl ))
            {
                // If that's the case we can merge it, meaning
                // the new one covers this well
                if( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
                    rCtx.pNewRedl->SetStart( *rCtx.pRStt, rCtx.pStart );
                else
                    rCtx.pNewRedl->SetEnd( *rCtx.pREnd, rCtx.pEnd );
                maRedlineTable.DeleteAndDestroy( rCtx.n );
                rCtx.bDec = true;
            }
            else if( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
                rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
            else
                rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
            break;
 
        case SwComparePosition::CollideEnd:
            if (rCtx.pRStt->GetContentIndex() != 0
                && rCtx.pRStt->GetNode() != rCtx.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( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
                rCtx.pRedl->CanCombine( *rCtx.pNewRedl ) )
            {
                if( IsHideChanges( GetRedlineFlags() ))
                {
                    // Before we can merge, we make it visible!
                    // We insert temporarily so that pNew is
                    // also dealt with when moving the indices.
                    maRedlineTable.Insert(rCtx.pNewRedl);
                    rCtx.pRedl->Show(0, maRedlineTable.GetPos(rCtx.pRedl));
                    maRedlineTable.Remove( rCtx.pNewRedl );
                    rCtx.pRStt = rCtx.pRedl->Start();
                    rCtx.pREnd = rCtx.pRedl->End();
                }
 
                // If that's the case we can merge it, meaning
                // the new one covers this well
                if( SwComparePosition::CollideStart == rCtx.eCmpPos )
                    rCtx.pNewRedl->SetStart( *rCtx.pRStt, rCtx.pStart );
                else
                    rCtx.pNewRedl->SetEnd( *rCtx.pREnd, rCtx.pEnd );
 
                // delete current (below), and restart process with
                // previous
                SwRedlineTable::size_type nToBeDeleted = rCtx.n;
                rCtx.bDec = true;
 
                if( *(rCtx.pNewRedl->Start()) <= *rCtx.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.
                    rCtx.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 = GetRedlineFlags();
        if( !( eOld & RedlineFlags::DontCombineRedlines ) &&
            rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
            // tdf#116084 tdf#121176 don't combine anonymized deletion
            // and anonymized insertion, i.e. with the same dummy timestamp
            !rCtx.pRedl->GetRedlineData(0).IsAnonymized() )
        {
            // Collect MoveID's of the redlines we delete.
            if (rCtx.nMoveIDToDelete > 1 && maRedlineTable[rCtx.n]->GetMoved() > 0
                && (rCtx.eCmpPos == SwComparePosition::Equal
                    || rCtx.eCmpPos == SwComparePosition::Inside
                    || rCtx.eCmpPos == SwComparePosition::Outside
                    || rCtx.eCmpPos == SwComparePosition::OverlapBefore
                    || rCtx.eCmpPos == SwComparePosition::OverlapBehind))
            {
                rCtx.deletedMoveIDs.insert(maRedlineTable[rCtx.n]->GetMoved());
            }
 
            // Set to NONE, so that the Delete::Redo merges the Redline data correctly!
            // The ShowMode needs to be retained!
            SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));
            switch( rCtx.eCmpPos )
            {
            case SwComparePosition::Equal:
                rCtx.bCompress = true;
                maRedlineTable.DeleteAndDestroy( rCtx.n );
                rCtx.bDec = true;
                [[fallthrough]];
 
            case SwComparePosition::Inside:
                if( rCtx.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( (rCtx.pStart->GetContentIndex() == 0) &&
                        rCtx.pEnd->GetNode().IsEndNode() )
                    {
                        rCtx.pEnd->Adjust(SwNodeOffset(-1));
                        m_rDoc.getIDocumentContentOperations().DelFullPara( *rCtx.pNewRedl );
                    }
                    else
                        m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *rCtx.pNewRedl );
 
                    rCtx.bCompress = true;
                }
                delete rCtx.pNewRedl;
                rCtx.pNewRedl = nullptr;
 
                // No need to call MaybeNotifyRedlineModification, because a notification
                // was already sent in DocumentRedlineManager::DeleteRedline
                break;
 
            case SwComparePosition::Outside:
                {
                    maRedlineTable.Remove( rCtx.n );
                    rCtx.bDec = true;
                    if( rCtx.bCallDelete )
                    {
                        TemporaryRedlineUpdater const u(m_rDoc, *rCtx.pNewRedl);
                        m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *rCtx.pRedl );
                        rCtx.n = 0;      // re-initialize
                    }
                    delete rCtx.pRedl;
                }
                break;
 
            case SwComparePosition::OverlapBefore:
                {
                    SwPaM aPam( *rCtx.pRStt, *rCtx.pEnd );
 
                    if( *rCtx.pEnd == *rCtx.pREnd )
                        maRedlineTable.DeleteAndDestroy( rCtx.n );
                    else
                    {
                        rCtx.pRedl->SetStart( *rCtx.pEnd, rCtx.pRStt );
                        // re-insert
                        maRedlineTable.Remove( rCtx.n );
                        maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
                    }
 
                    if( rCtx.bCallDelete )
                    {
                        TemporaryRedlineUpdater const u(m_rDoc, *rCtx.pNewRedl);
                        m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam );
                        rCtx.n = 0;      // re-initialize
                    }
                    rCtx.bDec = true;
                }
                break;
 
            case SwComparePosition::OverlapBehind:
                {
                    SwPaM aPam( *rCtx.pStart, *rCtx.pREnd );
 
                    if( *rCtx.pStart == *rCtx.pRStt )
                    {
                        maRedlineTable.DeleteAndDestroy( rCtx.n );
                        rCtx.bDec = true;
                    }
                    else
                        rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
 
                    if( rCtx.bCallDelete )
                    {
                        TemporaryRedlineUpdater const u(m_rDoc, *rCtx.pNewRedl);
                        m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam );
                        rCtx.n = 0;      // re-initialize
                        rCtx.bDec = true;
                    }
                }
                break;
            default:
                break;
            }
 
            SetRedlineFlags_intern(eOld);
        }
        else
        {
            PreAppendForeignRedline(rCtx);
        }
    }
    break;
 
    case RedlineType::Format:
        switch( rCtx.eCmpPos )
        {
        case SwComparePosition::OverlapBefore:
            rCtx.pRedl->SetStart( *rCtx.pEnd, rCtx.pRStt );
            // re-insert
            maRedlineTable.Remove( rCtx.n );
            maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
            rCtx.bDec = true;
            break;
 
        case SwComparePosition::OverlapBehind:
            rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
            break;
 
        case SwComparePosition::Equal:
        case SwComparePosition::Outside:
            // Overlaps the current one completely or has the
            // same dimension, delete the old one
            maRedlineTable.DeleteAndDestroy( rCtx.n );
            rCtx.bDec = true;
            break;
 
        case SwComparePosition::Inside:
            // Overlaps the current one completely,
            // split or shorten the new one
            if( *rCtx.pEnd != *rCtx.pREnd )
            {
                if( *rCtx.pEnd != *rCtx.pRStt )
                {
                    // At this point, rCtx.pRedl is the old format redline and rCtx.pNewRedl is the
                    // new delete redline. Make sure that when this pNewRedl gets appended, it still
                    // has the formatting redline data from rCtx.pRedl / pNew, and format is on top
                    // of delete.
                    rCtx.pNewRedl->PushData(*rCtx.pRedl);
 
                    SwRangeRedline* pNew = new SwRangeRedline( *rCtx.pRedl );
                    pNew->SetStart( *rCtx.pEnd );
                    rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
                    if( ( *rCtx.pStart == *rCtx.pRStt ) &&
                        ( rCtx.pRedl->GetContentIdx() == nullptr ) )
                        maRedlineTable.DeleteAndDestroy( rCtx.n );
                    AppendRedline( pNew, rCtx.bCallDelete );
                    rCtx.n = 0;      // re-initialize
                    rCtx.bDec = true;
                }
            }
            else
                rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }
}
 
void DocumentRedlineManager::PreAppendFormatRedline(AppendRedlineContext& rCtx)
{
    switch( rCtx.pRedl->GetType() )
    {
    case RedlineType::Insert:
    case RedlineType::Delete:
    {
        RedlineFlags eOld = GetRedlineFlags();
        bool bCombineRedlines = !(eOld & RedlineFlags::DontCombineRedlines)
                                && rCtx.pRedl->IsOwnRedline(*rCtx.pNewRedl)
                                && !rCtx.pRedl->GetRedlineData(0).IsAnonymized();
 
        if (bCombineRedlines && rCtx.pRedl->GetType() == RedlineType::Delete
            && rCtx.pNewRedl->GetType() == RedlineType::Format)
        {
            // Don't fold format on top of delete into the delete, that would give an incorrect
            // result when rejecting this redline, the format would remain in the doc model.
            bCombineRedlines = false;
        }
 
        if (bCombineRedlines || rCtx.pRedl->IsMoved())
        {
            switch( rCtx.eCmpPos )
            {
            case SwComparePosition::OverlapBefore:
                rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
                break;
 
            case SwComparePosition::OverlapBehind:
                rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
                break;
 
            case SwComparePosition::Inside:
                if (*rCtx.pRStt < *rCtx.pStart && *rCtx.pREnd == *rCtx.pEnd)
                {
                    // pRedl start is before pNewRedl start, the ends match: then create the
                    // format on top of insert/delete & reduce the end of the original
                    // insert/delete to avoid an overlap.
                    rCtx.pNewRedl->PushData(*rCtx.pRedl, false);
                    rCtx.pRedl->SetEnd(*rCtx.pStart);
                    rCtx.n = 0;
                    rCtx.bDec = true;
                    break;
                }
                [[fallthrough]];
            case SwComparePosition::Equal:
                delete rCtx.pNewRedl;
                rCtx.pNewRedl = nullptr;
 
                MaybeNotifyRedlineModification(*rCtx.pRedl, m_rDoc);
                break;
 
            case SwComparePosition::Outside:
                // Overlaps the current one completely,
                // split or shorten the new one
                if (*rCtx.pEnd == *rCtx.pREnd)
                {
                    rCtx.pNewRedl->SetEnd(*rCtx.pRStt, rCtx.pEnd);
                }
                else if (*rCtx.pStart == *rCtx.pRStt)
                {
                    rCtx.pNewRedl->SetStart(*rCtx.pREnd, rCtx.pStart);
                }
                else
                {
                    SwRangeRedline* pNew = new SwRangeRedline( *rCtx.pNewRedl );
                    pNew->SetStart( *rCtx.pREnd );
                    rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
                    AppendRedline( pNew, rCtx.bCallDelete );
                    rCtx.n = 0;      // re-initialize
                    rCtx.bDec = true;
                }
                break;
            default:
                break;
            }
        }
        else
        {
            PreAppendForeignRedline(rCtx);
        }
        break;
    }
    case RedlineType::Format:
        switch( rCtx.eCmpPos )
        {
        case SwComparePosition::Outside:
        case SwComparePosition::Equal:
            {
                // Overlaps the current one completely or has the
                // same dimension, delete the old one
                maRedlineTable.DeleteAndDestroy( rCtx.n );
                rCtx.bDec = true;
            }
            break;
 
        case SwComparePosition::Inside:
            if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
                rCtx.pRedl->CanCombine( *rCtx.pNewRedl ))
            {
                // own one can be ignored completely
                delete rCtx.pNewRedl;
                rCtx.pNewRedl = nullptr;
 
                MaybeNotifyRedlineModification(*rCtx.pRedl, m_rDoc);
            }
            else if( *rCtx.pREnd == *rCtx.pEnd )
                // or else only shorten the current one
                rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
            else if( *rCtx.pRStt == *rCtx.pStart )
            {
                // or else only shorten the current one
                rCtx.pRedl->SetStart( *rCtx.pEnd, rCtx.pRStt );
                // re-insert
                maRedlineTable.Remove( rCtx.n );
                maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
                rCtx.bDec = true;
            }
            else
            {
                // If it lies completely within the current one
                // we need to split it
                SwRangeRedline* pNew = new SwRangeRedline( *rCtx.pRedl );
                pNew->SetStart( *rCtx.pEnd );
                rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
                AppendRedline( pNew, rCtx.bCallDelete );
                rCtx.n = 0;      // re-initialize
                rCtx.bDec = true;
            }
            break;
 
        case SwComparePosition::OverlapBefore:
        case SwComparePosition::OverlapBehind:
            if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
                rCtx.pRedl->CanCombine( *rCtx.pNewRedl ))
            {
                // If that's the case we can merge it, meaning
                // the new one covers this well
                if( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
                    rCtx.pNewRedl->SetStart( *rCtx.pRStt, rCtx.pStart );
                else
                    rCtx.pNewRedl->SetEnd( *rCtx.pREnd, rCtx.pEnd );
                maRedlineTable.DeleteAndDestroy( rCtx.n );
                rCtx.bDec = false;
            }
            else if( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
                rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
            else
                rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
            break;
 
        case SwComparePosition::CollideEnd:
            if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
                rCtx.pRedl->CanCombine( *rCtx.pNewRedl ) &&
                (rCtx.n == 0 || *maRedlineTable[ rCtx.n-1 ]->End() < *rCtx.pStart))
            {
                // If that's the case we can merge it, meaning
                // the new one covers this well
                rCtx.pNewRedl->SetEnd( *rCtx.pREnd, rCtx.pEnd );
                maRedlineTable.DeleteAndDestroy( rCtx.n );
                rCtx.bDec = true;
            }
            break;
        case SwComparePosition::CollideStart:
            if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
                rCtx.pRedl->CanCombine( *rCtx.pNewRedl ) &&
                (rCtx.n+1 >= maRedlineTable.size() ||
                 (*maRedlineTable[ rCtx.n+1 ]->Start() >= *rCtx.pEnd &&
                  *maRedlineTable[ rCtx.n+1 ]->Start() != *rCtx.pREnd)))
            {
                // If that's the case we can merge it, meaning
                // the new one covers this well
                rCtx.pNewRedl->SetStart( *rCtx.pRStt, rCtx.pStart );
                maRedlineTable.DeleteAndDestroy( rCtx.n );
                rCtx.bDec = true;
            }
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }
}
 
/*
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(GetRedlineFlags()))
    {
        if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() )
        {
            RedlineFlags eOld = GetRedlineFlags();
            // Set to NONE, so that the Delete::Redo merges the Redline data correctly!
            // The ShowMode needs to be retained!
            SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));
            m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl );
            SetRedlineFlags_intern(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 [pStart, pEnd] = pNewRedl->StartEnd(); // SwPosition*
    {
        SwTextNode* pTextNode = pStart->GetNode().GetTextNode();
        if( pTextNode == nullptr )
        {
            if( pStart->GetContentIndex() > 0 )
            {
                OSL_ENSURE( false, "Redline start: non-text-node with content" );
                pStart->SetContent( 0 );
            }
        }
        else
        {
            if( pStart->GetContentIndex() > pTextNode->Len() )
            {
                OSL_ENSURE( false, "Redline start: index after text" );
                pStart->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( ( *pStart == *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( *pStart, &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( *pStart, *pEnd, *pRStt, *pREnd );
 
        if ( SwComparePosition::Before == eCmpPos && !IsPrevPos( *pEnd, *pRStt ))
            break;
 
        AppendRedlineContext aContext{ pNewRedl,
                                       pStart,
                                       pEnd,
                                       pRedl,
                                       pRStt,
                                       pREnd,
                                       eCmpPos,
                                       n,
                                       bMerged,
                                       bDec,
                                       bCompress,
                                       bCallDelete,
                                       nMoveIDToDelete,
                                       deletedMoveIDs };
        switch (pNewRedl->GetType())
        {
            case RedlineType::Insert:
                PreAppendInsertRedline(aContext);
                break;
 
            case RedlineType::Delete:
                PreAppendDeleteRedline(aContext);
                break;
 
            case RedlineType::Format:
                PreAppendFormatRedline(aContext);
                break;
 
            case RedlineType::FmtColl:
                // How should we behave here?
                // insert as is
                break;
            default:
                break;
        }
    }
 
    if( pNewRedl )
    {
        if( ( *pStart == *pEnd ) &&
            ( pNewRedl->GetContentIdx() == nullptr ) )
        {   // Do not insert empty redlines
            delete pNewRedl;
            pNewRedl = nullptr;
        }
        else
        {
            if ( bCallDelete && RedlineType::Delete == pNewRedl->GetType() )
            {
                if ( pStart->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 = pStart->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
                                std::unique_ptr<SwRedlineExtraData_FormatColl> pExtraData = lcl_CopyStyle(aPos, *pStart, false);
                                if (pExtraData)
                                {
                                    if (!bFirst)
                                        pExtraData->SetFormatAll(false);
                                    pPar->SetExtraData( pExtraData.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(*pStart, 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( pStart->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(GetRedlineFlags()))
    {
        // #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 = GetRedlineFlags();
            // Set to NONE, so that the Delete::Redo merges the Redline data correctly!
            // The ShowMode needs to be retained!
            SetRedlineFlags_intern(eOld & ~(RedlineFlags::On | RedlineFlags::Ignore));
            DeleteAndJoin( *pNewRedl );
            SetRedlineFlags_intern(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(GetRedlineFlags()))
    {
        // #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 = GetRedlineFlags();
            // Set to NONE, so that the Delete::Redo merges the Redline data correctly!
            // The ShowMode needs to be retained!
            SetRedlineFlags_intern(eOld & ~(RedlineFlags::On | RedlineFlags::Ignore));
            DeleteAndJoin( *pNewRedl );
            SetRedlineFlags_intern(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 & GetRedlineFlags();
    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 [pStart, 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( *pStart, &n );
    while (n < maRedlineTable.size())
    {
        SwRangeRedline * pRedline = maRedlineTable[ n ];
        auto [pRedlineStart, pRedlineEnd] = pRedline->StartEnd();
        if (*pRedlineStart <= *pStart && *pEnd <= *pRedlineEnd)
        {
            bChg = true;
            int nn = 0;
            if (*pStart == *pRedlineStart)
                nn += 1;
            if (*pEnd == *pRedlineEnd)
                nn += 2;
 
            SwRangeRedline* pNew = nullptr;
            switch( nn )
            {
            case 0:
                pNew = new SwRangeRedline( *pRedline );
                pRedline->SetEnd( *pStart, pRedlineEnd );
                pNew->SetStart( *pEnd );
                break;
 
            case 1:
                *pRedlineStart = *pEnd;
                break;
 
            case 2:
                *pRedlineEnd = *pStart;
                break;
 
            case 3:
                pRedline->InvalidateRange(SwRangeRedline::Invalidation::Remove);
                maRedlineTable.DeleteAndDestroy( n );
                // loop again with the same n to iterate to the next entry
                pRedline = nullptr;
                break;
            }
 
            if (pRedline)
            {
                if (!pRedline->HasValidRange())
                {
                    // re-insert
                    maRedlineTable.Remove( n );
                    maRedlineTable.Insert( pRedline, n );
                }
 
                if (pNew)
                    maRedlineTable.Insert(pNew, n);
            }
        }
        else if (*pEnd < *pRedlineStart)
            break;
        if (pRedline)
            ++n;
    }
    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 [pStart, pEnd] = rRange.StartEnd(); // SwPosition*
    SwRedlineTable::size_type n = 0;
    GetRedline( *pStart, &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( *pStart, *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( *pStart, 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 == *pStart )
                {
                    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( *pStart, 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());
        }
    }
}
 
void DocumentRedlineManager::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("DocumentRedlineManager"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
 
    if (meRedlineFlags != RedlineFlags::NONE)
    {
        (void)xmlTextWriterStartElement(pWriter, BAD_CAST("meRedlineFlags"));
        if (meRedlineFlags & RedlineFlags::On)
        {
            (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("On"), BAD_CAST("true"));
        }
        if (meRedlineFlags & RedlineFlags::Ignore)
        {
            (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("Ignore"), BAD_CAST("true"));
        }
        if (meRedlineFlags & RedlineFlags::ShowInsert)
        {
            (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("ShowInsert"), BAD_CAST("true"));
        }
        if (meRedlineFlags & RedlineFlags::ShowDelete)
        {
            (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("ShowDelete"), BAD_CAST("true"));
        }
        if (meRedlineFlags & RedlineFlags::DeleteRedlines)
        {
            (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("DeleteRedlines"), BAD_CAST("true"));
        }
        if (meRedlineFlags & RedlineFlags::DontCombineRedlines)
        {
            (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("DontCombineRedlines"), BAD_CAST("true"));
        }
        (void)xmlTextWriterEndElement(pWriter);
    }
    maRedlineTable.dumpAsXml(pWriter);
    maExtraRedlineTable.dumpAsXml(pWriter);
 
    (void)xmlTextWriterEndElement(pWriter);
}
 
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
{
    if (maRedlineTable.HasOverlappingElements())
    {
        for (auto it = maRedlineTable.begin(), itEnd = maRedlineTable.end(); it != itEnd; ++it)
        {
            auto [pStart, pEnd] = (**it).StartEnd();
            if (rPos < *pStart)
            {
                if (pFndPos)
                {
                    *pFndPos = std::distance(maRedlineTable.begin(), it);
                }
                return nullptr;
            }
            if (pEnd == pStart
                    ? *pStart == rPos
                    : (*pStart <= rPos && rPos < *pEnd))
            {
                if (pFndPos)
                {
                    *pFndPos = std::distance(maRedlineTable.begin(), it);
                }
                return *it;
            }
        }
        if (pFndPos)
        {
            *pFndPos = maRedlineTable.size();
        }
        return nullptr;
    }
    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 [pStart, pEnd] = pRedl->StartEnd();
            if( pEnd == pStart
                    ? *pStart == rPos
                    : ( *pStart <= 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,
                                                const SwRedlineTable::size_type& nPosStart,
                                                const SwRedlineTable::size_type& nPosEnd,
                                                bool bCallDelete, bool bDirect)
{
    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 nPamEndNI = 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() > nPamEndNI
            || (pTmp->End()->GetNodeIndex() == nPamEndNI
                && pTmp->End()->GetContentIndex() > nPamEndCI))
        {
        }
        else if (pTmp->GetRedlineData(0).CanCombineForAcceptReject(aOrigData))
        {
            bool bHierarchicalFormat = pTmp->GetType() == RedlineType::Format && pTmp->GetStackCount() > 1;
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                // Set up the undo action with the correct depth & directness.
                sal_Int8 nDepth = 0;
                if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Insert && !bDirect)
                {
                    // Only work with the underlying insert, so the undo action matches the UI
                    // action below.
                    nDepth = 1;
                }
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(
                    std::make_unique<SwUndoAcceptRedline>(*pTmp, nDepth, bDirect));
            }
            nPamEndNI = pTmp->Start()->GetNodeIndex();
            nPamEndCI = pTmp->Start()->GetContentIndex();
 
            if (bHierarchicalFormat && bDirect
                && (pTmp->GetType(1) == RedlineType::Insert
                    || pTmp->GetType(1) == RedlineType::Delete))
            {
                bRet |= lcl_AcceptOuterFormat(maRedlineTable, nRdlIdx);
            }
            else if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Insert)
            {
                // This combination of 2 redline types prefers accepting the inner one first.
                bRet |= lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, 1);
            }
            else if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Delete)
            {
                // Get rid of the format itself and then accept the delete by deleting the range.
                bRet |= lcl_AcceptInnerDelete(*pTmp, maRedlineTable, nRdlIdx, bCallDelete);
            }
            else
            {
                bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
            }
 
            nRdlIdx++; //we will decrease it in the loop anyway.
        }
        else if (CanCombineTypesForAcceptReject(aOrigData, *pTmp)
                 && pTmp->GetRedlineData(1).CanCombineForAcceptReject(aOrigData))
        {
            // The Insert/Delete redline we want to accept has another type of redline too
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(
                    std::make_unique<SwUndoAcceptRedline>(*pTmp, 1));
            }
            nPamEndNI = pTmp->Start()->GetNodeIndex();
            nPamEndCI = pTmp->Start()->GetContentIndex();
            if (aOrigData.GetType() == RedlineType::Delete)
            {
                // We should delete the other type of redline when accepting the inner delete.
                bRet |= lcl_AcceptInnerDelete(*pTmp, maRedlineTable, nRdlIdx, bCallDelete);
            }
            else
            {
                // we should leave the other type of redline, and only accept the inner insert.
                bRet |= lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, 1);
            }
            nRdlIdx++; //we will decrease it in the loop anyway.
        }
        else if (CanReverseCombineTypesForAcceptReject(*pTmp, aOrigData) && !bDirect)
        {
            // The aOrigData has 2 types and for these types we want the underlying type to be
            // combined with the type of the surrounding redlines, so accept pTmp, too.
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(
                    std::make_unique<SwUndoAcceptRedline>(*pTmp));
            }
            nPamEndNI = pTmp->Start()->GetNodeIndex();
            nPamEndCI = pTmp->Start()->GetContentIndex();
            bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
            nRdlIdx++;
        }
    } 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_DeleteInnerRedline(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 bDirect)
{
    bool bRet = false;
 
    // Switch to visible in any case
    if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
        (RedlineFlags::ShowMask & GetRedlineFlags()) )
      SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | GetRedlineFlags() );
 
    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, bDirect);
            }
        }
        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, bool bDirect )
{
    // Switch to visible in any case
    if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
        (RedlineFlags::ShowMask & GetRedlineFlags()) )
      SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | GetRedlineFlags() );
 
    // 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)
    {
        SwRedlineTable::size_type nRdlIdx = 0;
        const SwRangeRedline* pRedline = maRedlineTable.FindAtPosition(*rPam.Start(), nRdlIdx);
        bool bHierarchicalFormat = pRedline && pRedline->GetType() == RedlineType::Format
                                   && pRedline->GetStackCount() > 1;
        if (bHierarchicalFormat && bDirect
            && (pRedline->GetType(1) == RedlineType::Insert
                || pRedline->GetType(1) == RedlineType::Delete))
        {
            // Direct accept: work with the format redline on top.
            if (lcl_AcceptOuterFormat(maRedlineTable, nRdlIdx))
            {
                nRet = 1;
            }
        }
        else
        {
            // Non-direct accept: work with all redlines under pPam.
            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_DeleteInnerRedline(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 [pStart, pEnd] = rPam.StartEnd(); // SwPosition*
 
    const SwNodeOffset nSttIdx = pStart->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,
                                                const SwRedlineTable::size_type& nPosStart,
                                                const SwRedlineTable::size_type& nPosEnd,
                                                bool bCallDelete, bool bDirect)
{
    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 nPamEndNI = 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() > nPamEndNI
            || (pTmp->End()->GetNodeIndex() == nPamEndNI
                && pTmp->End()->GetContentIndex() > nPamEndCI))
        {
        }
        else if (pTmp->GetRedlineData(0).CanCombineForAcceptReject(aOrigData))
        {
            bool bHierarchical = pTmp->GetStackCount() > 1;
            bool bHierarchicalFormat = bHierarchical && pTmp->GetType() == RedlineType::Format;
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                sal_Int8 nDepth = 0;
                if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Delete && !bDirect)
                {
                    // Only work with the underlying delete, so the undo action matches the UI
                    // action below.
                    nDepth = 1;
                }
                auto pUndoRdl = std::make_unique<SwUndoRejectRedline>(*pTmp, nDepth, bHierarchical, bDirect);
#if OSL_DEBUG_LEVEL > 0
                pUndoRdl->SetRedlineCountDontCheck(true);
#endif
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl));
            }
            nPamEndNI = pTmp->Start()->GetNodeIndex();
            nPamEndCI = pTmp->Start()->GetContentIndex();
 
            if (bHierarchicalFormat && bDirect
                && (pTmp->GetType(1) == RedlineType::Insert
                    || pTmp->GetType(1) == RedlineType::Delete))
            {
                    bRet |= lcl_RejectOuterFormat(maRedlineTable, nRdlIdx);
            }
            else if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Insert)
            {
                // Accept the format itself and then reject the insert by deleting the range.
                SwPaM aPam(*pTmp->Start(), *pTmp->End());
                bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
                // Handles undo/redo itself.
                m_rDoc.getIDocumentContentOperations().DeleteRange(aPam);
            }
            else if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Delete)
            {
                // Keep the format redline on top, just get rid of the delete at the bottom.
                bRet |= lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, 1);
            }
            else
            {
                bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, bCallDelete);
            }
 
            nRdlIdx++; //we will decrease it in the loop anyway.
        }
        else if (CanCombineTypesForAcceptReject(aOrigData, *pTmp)
                 && pTmp->GetRedlineData(1).CanCombineForAcceptReject(aOrigData))
        {
            RedlineType eInnerType = aOrigData.GetType();
            RedlineType eOuterType = pTmp->GetType();
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                std::unique_ptr<SwUndoRedline> pUndoRdl;
                if (eInnerType == RedlineType::Delete && eOuterType == RedlineType::Format)
                {
                    // Format on delete: record rejection of the underlying delete.
                    pUndoRdl = std::make_unique<SwUndoRejectRedline>(*pTmp, /*nDepth=*/1, /*bHierarchical=*/true);
                }
                else
                {
                    // The Insert/Delete redline we want to reject has another type of redline too
                    pUndoRdl = std::make_unique<SwUndoRejectRedline>(*pTmp, /*nDepth=*/0, /*bHierarchical=*/true);
                }
#if OSL_DEBUG_LEVEL > 0
                pUndoRdl->SetRedlineCountDontCheck(true);
#endif
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl));
            }
            nPamEndNI = pTmp->Start()->GetNodeIndex();
            nPamEndCI = pTmp->Start()->GetContentIndex();
            std::optional<SwPaM> oPam;
            if (eInnerType == RedlineType::Insert && eOuterType == RedlineType::Format)
            {
                // The accept won't implicitly delete the range, so track its boundaries.
                oPam.emplace(*pTmp->Start(), *pTmp->End());
            }
 
            if (eInnerType == RedlineType::Delete && eOuterType == RedlineType::Format)
            {
                // Keep the outer redline, just get rid of the underlying delete.
                bRet |= lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, 1);
            }
            else
            {
                // without the insert, the other type is meaningless
                // so we rather just accept the other type of redline
                bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
            }
            if (oPam)
            {
                // Handles undo/redo itself.
                m_rDoc.getIDocumentContentOperations().DeleteRange(*oPam);
            }
            nRdlIdx++; //we will decrease it in the loop anyway.
        }
        else if (CanReverseCombineTypesForAcceptReject(*pTmp, aOrigData) && !bDirect)
        {
            // The aOrigData has 2 types and for these types we want the underlying type to be
            // combined with the type of the surrounding redlines, so reject pTmp, too.
            if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
            {
                std::unique_ptr<SwUndoRedline> pUndoRdl
                    = std::make_unique<SwUndoRejectRedline>(*pTmp);
                m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl));
            }
            nPamEndNI = pTmp->Start()->GetNodeIndex();
            nPamEndCI = pTmp->Start()->GetContentIndex();
            bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, bCallDelete);
            nRdlIdx++;
        }
 
    } 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 bDirect)
{
    bool bRet = false;
 
    // Switch to visible in any case
    if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
        (RedlineFlags::ShowMask & GetRedlineFlags()) )
      SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | GetRedlineFlags() );
 
    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, bDirect);
            }
        }
        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, bool bDirect )
{
    // Switch to visible in any case
    if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
        (RedlineFlags::ShowMask & GetRedlineFlags()) )
      SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | GetRedlineFlags() );
 
    // 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;
    SwRedlineTable::size_type nRdlIdx = 0;
    const SwRangeRedline* pRedline = maRedlineTable.FindAtPosition(*rPam.Start(), nRdlIdx);
    if (nDepth == 0)
    {
        bool bHierarchicalFormat = pRedline && pRedline->GetType() == RedlineType::Format && pRedline->GetStackCount() > 1;
        if (bHierarchicalFormat && bDirect && (pRedline->GetType(1) == RedlineType::Insert || pRedline->GetType(1) == RedlineType::Delete))
        {
            // Direct reject: work with the format redline on top.
            if (lcl_RejectOuterFormat(maRedlineTable, nRdlIdx))
            {
                nRet = 1;
            }
        }
        else
        {
            // Non-direct reject: work with all redlines under aPam.
            nRet = lcl_AcceptRejectRedl(lcl_RejectRedline, maRedlineTable, bCallDelete, aPam);
        }
    }
    else
    {
        if (nDepth == 1 && pRedline && pRedline->GetType(0) == RedlineType::Format
            && pRedline->GetType(1) == RedlineType::Delete)
        {
            // Reject a format-on-delete by getting rid of the underlying delete.
            if (lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, nDepth))
            {
                nRet = 1;
            }
        }
        else
        {
            // For now it is called only if it is an Insert redline in a delete redline.
            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* pStart = pFnd->Start();
        if( !pStart->GetNode().IsContentNode() )
        {
            SwNodeIndex aTmp( pStart->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 [pStart, pEnd] = rPaM.StartEnd(); // SwPosition*
    SwRedlineTable::size_type n = 0;
    if( GetRedlineTable().FindAtPosition( *pStart, n ) )
    {
        for( ; n < maRedlineTable.size(); ++n )
        {
            bRet = true;
            SwRangeRedline* pTmp = maRedlineTable[ n ];
            if( pStart != 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 SwModule::get()->GetRedlineAuthor();
}
 
/// Insert new author into the Table for the Readers etc.
std::size_t DocumentRedlineManager::InsertRedlineAuthor( const OUString& rNew )
{
    return SwModule::get()->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.

V560 A part of conditional expression is always false: rCtx.pNewRedl.