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

#include <libxml/xmlwriter.h>
#include <boost/property_tree/json_parser.hpp>

#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <tools/datetimeutils.hxx>
#include <hintids.hxx>
#include <svl/itemiter.hxx>
#include <editeng/prntitem.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/string.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <unotools/datetime.hxx>
#include <sfx2/viewsh.hxx>
#include <o3tl/string_view.hxx>
#include <swmodule.hxx>
#include <doc.hxx>
#include <docredln.hxx>
#include <IDocumentUndoRedo.hxx>
#include <DocumentContentOperationsManager.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentState.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <docary.hxx>
#include <ndtxt.hxx>
#include <redline.hxx>
#include <UndoCore.hxx>
#include <hints.hxx>
#include <pamtyp.hxx>
#include <poolfmt.hxx>
#include <algorithm>
#include <limits>
#include <utility>
#include <view.hxx>
#include <viewopt.hxx>
#include <usrpref.hxx>
#include <viewsh.hxx>
#include <viscrs.hxx>
#include <rootfrm.hxx>
#include <strings.hrc>
#include <swtypes.hxx>
#include <wrtsh.hxx>
#include <txtfld.hxx>

#include <flowfrm.hxx>
#include <txtfrm.hxx>
#include <annotationmark.hxx>

using namespace com::sun::star;

#ifdef DBG_UTIL

    void sw_DebugRedline( const SwDoc& rDoc )
    {
        static SwRedlineTable::size_type nWatch = 0; // loplugin:constvars:ignore
        const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
        for( SwRedlineTable::size_type n = 0; n < rTable.size(); ++n )
        {
            volatile SwRedlineTable::size_type nDummy = 0;
            const SwRangeRedline* pCurrent = rTable[ n ];
            const SwRangeRedline* pNext = n+1 < rTable.size() ? rTable[ n+1 ] : nullptr;
            if( pCurrent == pNext )
                (void) nDummy;
            if( n == nWatch )
                (void) nDummy; // Possible debugger breakpoint
        }
    }

#endif


SwExtraRedlineTable::~SwExtraRedlineTable()
{
    DeleteAndDestroyAll();
}

void SwExtraRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedlineTable"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);

    for (sal_uInt16 nCurExtraRedlinePos = 0; nCurExtraRedlinePos < GetSize(); ++nCurExtraRedlinePos)
    {
        const SwExtraRedline* pExtraRedline = GetRedline(nCurExtraRedlinePos);
        (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedline"));
        (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
        (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s", BAD_CAST(typeid(*pExtraRedline).name()));
        (void)xmlTextWriterEndElement(pWriter);
    }
    (void)xmlTextWriterEndElement(pWriter);
}

#if OSL_DEBUG_LEVEL > 0
static bool CheckPosition( const SwPosition* pStart, const SwPosition* pEnd )
{
    int nError = 0;
    SwNode* pSttNode = &pStart->GetNode();
    SwNode* pEndNode = &pEnd->GetNode();
    SwNode* pSttTab = pSttNode->StartOfSectionNode()->FindTableNode();
    SwNode* pEndTab = pEndNode->StartOfSectionNode()->FindTableNode();
    SwNode* pSttStart = pSttNode;
    while( pSttStart && (!pSttStart->IsStartNode() || pSttStart->IsSectionNode() ||
        pSttStart->IsTableNode() ) )
        pSttStart = pSttStart->StartOfSectionNode();
    SwNode* pEndStart = pEndNode;
    while( pEndStart && (!pEndStart->IsStartNode() || pEndStart->IsSectionNode() ||
        pEndStart->IsTableNode() ) )
        pEndStart = pEndStart->StartOfSectionNode();
    assert(pSttTab == pEndTab);
    if( pSttTab != pEndTab )
        nError = 1;
    assert(pSttTab || pSttStart == pEndStart);
    if( !pSttTab && pSttStart != pEndStart )
        nError |= 2;
    if( nError )
        nError += 10;
    return nError != 0;
}
#endif

bool SwExtraRedlineTable::DeleteAllTableRedlines( SwDoc& rDoc, const SwTable& rTable, bool bSaveInUndo, RedlineType nRedlineTypeToDelete )
{
    bool bChg = false;

    if (bSaveInUndo && rDoc.GetIDocumentUndoRedo().DoesUndo())
    {
        // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines
        /*
        SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange );
        if( pUndo->GetRedlSaveCount() )
        {
            GetIDocumentUndoRedo().AppendUndo(pUndo);
        }
        else
            delete pUndo;
        */
    }

    for (sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); )
    {
        SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos);
        const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline);
        if (pTableCellRedline)
        {
            const SwTableBox *pRedTabBox = &pTableCellRedline->GetTableBox();
            const SwTable& rRedTable = pRedTabBox->GetSttNd()->FindTableNode()->GetTable();
            if ( &rRedTable == &rTable )
            {
                // Redline for this table
                const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData();
                const RedlineType nRedlineType = aRedlineData.GetType();

                // Check if this redline object type should be deleted
                if (RedlineType::Any == nRedlineTypeToDelete || nRedlineTypeToDelete == nRedlineType)
                {

                    DeleteAndDestroy( nCurRedlinePos );
                    bChg = true;
                    continue; // don't increment position after delete
                }
            }
        }
        ++nCurRedlinePos;
    }

    if( bChg )
        rDoc.getIDocumentState().SetModified();

    return bChg;
}

bool SwExtraRedlineTable::DeleteTableRowRedline( SwDoc& rDoc, const SwTableLine& rTableLine, bool bSaveInUndo, RedlineType nRedlineTypeToDelete )
{
    bool bChg = false;

    if (bSaveInUndo && rDoc.GetIDocumentUndoRedo().DoesUndo())
    {
        // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines
        /*
        SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange );
        if( pUndo->GetRedlSaveCount() )
        {
            GetIDocumentUndoRedo().AppendUndo(pUndo);
        }
        else
            delete pUndo;
        */
    }

    for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ++nCurRedlinePos )
    {
        SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos);
        const SwTableRowRedline* pTableRowRedline = dynamic_cast<const SwTableRowRedline*>(pExtraRedline);
        if (!pTableRowRedline)
            continue;
        const SwTableLine& rRedTabLine = pTableRowRedline->GetTableLine();
        if ( &rRedTabLine == &rTableLine )
        {
            // Redline for this table row
            const SwRedlineData& aRedlineData = pTableRowRedline->GetRedlineData();
            const RedlineType nRedlineType = aRedlineData.GetType();

            // Check if this redline object type should be deleted
            if( RedlineType::Any != nRedlineTypeToDelete && nRedlineTypeToDelete != nRedlineType )
                continue;

            DeleteAndDestroy( nCurRedlinePos );
            bChg = true;
        }
    }

    if( bChg )
        rDoc.getIDocumentState().SetModified();

    return bChg;
}

bool SwExtraRedlineTable::DeleteTableCellRedline( SwDoc& rDoc, const SwTableBox& rTableBox, bool bSaveInUndo, RedlineType nRedlineTypeToDelete )
{
    bool bChg = false;

    if (bSaveInUndo && rDoc.GetIDocumentUndoRedo().DoesUndo())
    {
        // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines
        /*
        SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange );
        if( pUndo->GetRedlSaveCount() )
        {
            GetIDocumentUndoRedo().AppendUndo(pUndo);
        }
        else
            delete pUndo;
        */
    }

    for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ++nCurRedlinePos )
    {
        SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos);
        const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline);
        if (!pTableCellRedline)
            continue;
        const SwTableBox& rRedTabBox = pTableCellRedline->GetTableBox();
        if (&rRedTabBox == &rTableBox)
        {
            // Redline for this table cell
            const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData();
            const RedlineType nRedlineType = aRedlineData.GetType();

            // Check if this redline object type should be deleted
            if( RedlineType::Any != nRedlineTypeToDelete && nRedlineTypeToDelete != nRedlineType )
                continue;

            DeleteAndDestroy( nCurRedlinePos );
            bChg = true;
        }
    }

    if( bChg )
        rDoc.getIDocumentState().SetModified();

    return bChg;
}

namespace
{

void lcl_LOKInvalidateFrames(const sw::BroadcastingModify& rMod, const SwRootFrame* pLayout,
        SwFrameType const nFrameType, const Point* pPoint)
{
    SwIterator<SwFrame, sw::BroadcastingModify, sw::IteratorMode::UnwrapMulti> aIter(rMod);

    for (SwFrame* pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() )
    {
        if ((pTmpFrame->GetType() & nFrameType) &&
            (!pLayout || pLayout == pTmpFrame->getRootFrame()) &&
            (!pTmpFrame->IsFlowFrame() || !SwFlowFrame::CastFlowFrame( pTmpFrame )->IsFollow()))
        {
            if (pPoint)
            {
                pTmpFrame->InvalidateSize();

                // Also empty the text portion cache, so it gets rebuilt, taking the new redlines
                // into account.
                if (pTmpFrame->IsTextFrame())
                {
                    auto pTextFrame = static_cast<SwTextFrame*>(pTmpFrame);
                    pTextFrame->ClearPara();
                }
            }
        }
    }
}

void lcl_LOKInvalidateStartEndFrames(SwShellCursor& rCursor)
{
    if (!(rCursor.HasMark() &&
        rCursor.GetPoint()->GetNode().IsContentNode() &&
        rCursor.GetPoint()->GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout()) &&
        (rCursor.GetMark()->GetNode() == rCursor.GetPoint()->GetNode() ||
        (rCursor.GetMark()->GetNode().IsContentNode() &&
         rCursor.GetMark()->GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout())))))
    {
        return;
    }

    auto [pStartPos, pEndPos] = rCursor.StartEnd(); // SwPosition*

    lcl_LOKInvalidateFrames(*(pStartPos->GetNode().GetContentNode()),
                            rCursor.GetShell()->GetLayout(),
                            FRM_CNTNT, &rCursor.GetSttPos());

    lcl_LOKInvalidateFrames(*(pEndPos->GetNode().GetContentNode()),
                            rCursor.GetShell()->GetLayout(),
                            FRM_CNTNT, &rCursor.GetEndPos());
}

bool lcl_LOKRedlineNotificationEnabled()
{
    static bool bDisableRedlineComments = getenv("DISABLE_REDLINE") != nullptr;
    if (comphelper::LibreOfficeKit::isActive() && !bDisableRedlineComments)
        return true;

    return false;
}

} // anonymous namespace

void SwRedlineTable::setMovedIDIfNeeded(sal_uInt32 nMax)
{
    if (nMax > m_nMaxMovedID)
        m_nMaxMovedID = nMax;
}

/// Emits LOK notification about one addition / removal of a redline item.
void SwRedlineTable::LOKRedlineNotification(RedlineNotification nType, SwRangeRedline* pRedline)
{
    // Disable since usability is very low beyond some small number of changes.
    if (!lcl_LOKRedlineNotificationEnabled())
        return;

    boost::property_tree::ptree aRedline;
    aRedline.put("action", (nType == RedlineNotification::Add ? "Add" :
                            (nType == RedlineNotification::Remove ? "Remove" :
                             (nType == RedlineNotification::Modify ? "Modify" : "???"))));
    aRedline.put("index", pRedline->GetId());
    aRedline.put("author", pRedline->GetAuthorString(1).toUtf8().getStr());
    aRedline.put("type", SwRedlineTypeToOUString(pRedline->GetRedlineData().GetType()).toUtf8().getStr());
    aRedline.put("comment", pRedline->GetRedlineData().GetComment().toUtf8().getStr());
    aRedline.put("description", pRedline->GetDescr().toUtf8().getStr());
    OUString sDateTime = utl::toISO8601(pRedline->GetRedlineData().GetTimeStamp().GetUNODateTime());
    aRedline.put("dateTime", sDateTime.toUtf8().getStr());

    auto [pStartPos, pEndPos] = pRedline->StartEnd(); // SwPosition*
    SwContentNode* pContentNd = pRedline->GetPointContentNode();
    SwView* pView = dynamic_cast<SwView*>(SfxViewShell::Current());
    if (pView && pContentNd)
    {
        SwShellCursor aCursor(pView->GetWrtShell(), *pStartPos);
        aCursor.SetMark();
        *aCursor.GetMark() = *pEndPos;

        aCursor.FillRects();

        SwRects* pRects(&aCursor);
        std::vector<OString> aRects;
        for(const SwRect& rNextRect : *pRects)
            aRects.push_back(rNextRect.SVRect().toString());

        const OString sRects = comphelper::string::join("; ", aRects);
        aRedline.put("textRange", sRects.getStr());

        lcl_LOKInvalidateStartEndFrames(aCursor);

        // When this notify method is called text invalidation is not done yet
        // Calling FillRects updates the text area so invalidation will not run on the correct rects
        // So we need to do an own invalidation here. It invalidates text frames containing the redlining
        SwDoc& rDoc = pRedline->GetDoc();
        SwViewShell* pSh;
        if( !rDoc.IsInDtor() )
        {
            pSh = rDoc.getIDocumentLayoutAccess().GetCurrentViewShell();
            if( pSh )
                for(SwNodeIndex nIdx(pStartPos->GetNode()); nIdx <= pEndPos->GetNode(); ++nIdx)
                {
                    SwContentNode* pContentNode = nIdx.GetNode().GetContentNode();
                    if (pContentNode)
                        pSh->InvalidateWindows(pContentNode->FindLayoutRect());
                }
        }
    }

    boost::property_tree::ptree aTree;
    aTree.add_child("redline", aRedline);
    std::stringstream aStream;
    boost::property_tree::write_json(aStream, aTree);
    std::string aPayload = aStream.str();

    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pView && pView->GetDocId() == pViewShell->GetDocId())
            pViewShell->libreOfficeKitViewCallback(nType == RedlineNotification::Modify ? LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED : LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED, OString(aPayload));
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}

bool SwRedlineTable::Insert(SwRangeRedline*& p)
{
    if( p->HasValidRange() )
    {
        std::pair<vector_type::const_iterator, bool> rv = maVector.insert( p );
        size_type nP = rv.first - begin();
        LOKRedlineNotification(RedlineNotification::Add, p);

        // detect text moving by checking nearby redlines, except during Undo
        // (apply isMoved() during OpenDocument and DOCX import, too, to fix
        // missing text moving handling in ODF and e.g. web version of MSO)
        if ( p->GetDoc().GetIDocumentUndoRedo().DoesUndo() ||
             p->GetDoc().IsInWriterfilterImport() ||
             p->GetDoc().IsInXMLImport() )
        {
            isMoved(nP);
        }

        p->CallDisplayFunc(nP);
        if (rv.second)
        {
            CheckOverlapping(rv.first);
            if (!mpMaxEndPos || (*(*rv.first)->End()) > *mpMaxEndPos->End())
                mpMaxEndPos = *rv.first;
        }
        return rv.second;
    }
    return InsertWithValidRanges( p );
}

void SwRedlineTable::CheckOverlapping(vector_type::const_iterator it)
{
    if (m_bHasOverlappingElements)
        return;
    if (maVector.size() <= 1) // a single element cannot be overlapping
        return;
    auto pCurr = *it;
    auto itNext = it + 1;
    if (itNext != maVector.end())
    {
        auto pNext = *itNext;
        if (pCurr->End()->GetNodeIndex() >= pNext->Start()->GetNodeIndex())
        {
            m_bHasOverlappingElements = true;
            return;
        }
    }
    if (it != maVector.begin())
    {
        auto pPrev = *(it - 1);
        if (pPrev->End()->GetNodeIndex() >= pCurr->Start()->GetNodeIndex())
            m_bHasOverlappingElements = true;
    }
}

bool SwRedlineTable::Insert(SwRangeRedline*& p, size_type& rP)
{
    if( p->HasValidRange() )
    {
        std::pair<vector_type::const_iterator, bool> rv = maVector.insert( p );
        rP = rv.first - begin();
        p->CallDisplayFunc(rP);
        if (rv.second)
        {
            CheckOverlapping(rv.first);
            if (!mpMaxEndPos || (*(*rv.first)->End()) > *mpMaxEndPos->End())
                mpMaxEndPos = *rv.first;
        }
        return rv.second;
    }
    return InsertWithValidRanges( p, &rP );
}

namespace sw {

std::vector<std::unique_ptr<SwRangeRedline>> GetAllValidRanges(std::unique_ptr<SwRangeRedline> p)
{
    std::vector<std::unique_ptr<SwRangeRedline>> ret;
    // Create valid "sub-ranges" from the Selection
    auto [pStart, pEnd] = p->StartEnd(); // SwPosition*
    SwPosition aNewStt( *pStart );
    SwNodes& rNds = aNewStt.GetNodes();
    SwContentNode* pC;

    if( !aNewStt.GetNode().IsContentNode() )
    {
        pC = SwNodes::GoNext(&aNewStt);
        if( !pC )
            aNewStt.Assign(rNds.GetEndOfContent());
    }


    if( aNewStt >= *pEnd )
        return ret;

    std::unique_ptr<SwRangeRedline> pNew;
    do {
        if( !pNew )
            pNew.reset(new SwRangeRedline( p->GetRedlineData(), aNewStt ));
        else
        {
            pNew->DeleteMark();
            *pNew->GetPoint() = aNewStt;
        }

        pNew->SetMark();
        GoEndSection( pNew->GetPoint() );
        // i60396: If the redlines starts before a table but the table is the last member
        // of the section, the GoEndSection will end inside the table.
        // This will result in an incorrect redline, so we've to go back
        SwNode* pTab = pNew->GetPoint()->GetNode().StartOfSectionNode()->FindTableNode();
        // We end in a table when pTab != 0
        if( pTab && !pNew->GetMark()->GetNode().StartOfSectionNode()->FindTableNode() )
        { // but our Mark was outside the table => Correction
            do
            {
                // We want to be before the table
                pNew->GetPoint()->Assign(*pTab);
                pC = GoPreviousPos( pNew->GetPoint(), false ); // here we are.
                if( pC )
                    pNew->GetPoint()->SetContent( 0 );
                pTab = pNew->GetPoint()->GetNode().StartOfSectionNode()->FindTableNode();
            } while( pTab ); // If there is another table we have to repeat our step backwards
        }

        // insert dummy character to the empty table rows to keep their changes
        SwNode& rBoxNode = pNew->GetMark()->GetNode();
        if ( rBoxNode.GetDoc().GetIDocumentUndoRedo().DoesUndo() && rBoxNode.GetTableBox() &&
             rBoxNode.GetTableBox()->GetUpper()->IsEmpty() && rBoxNode.GetTextNode() )
        {
            ::sw::UndoGuard const undoGuard(rBoxNode.GetDoc().GetIDocumentUndoRedo());
            rBoxNode.GetTextNode()->InsertDummy();
            pNew->GetMark()->SetContent( 1 );
        }

        if( *pNew->GetPoint() > *pEnd )
        {
            pC = nullptr;
            if( aNewStt.GetNode() != pEnd->GetNode() )
                do {
                    SwNode& rCurNd = aNewStt.GetNode();
                    if( rCurNd.IsStartNode() )
                    {
                        if( rCurNd.EndOfSectionIndex() < pEnd->GetNodeIndex() )
                            aNewStt.Assign( *rCurNd.EndOfSectionNode() );
                        else
                            break;
                    }
                    else if( rCurNd.IsContentNode() )
                        pC = rCurNd.GetContentNode();
                    aNewStt.Adjust(SwNodeOffset(1));
                } while( aNewStt.GetNodeIndex() < pEnd->GetNodeIndex() );

            if( aNewStt.GetNode() == pEnd->GetNode() )
                aNewStt.SetContent(pEnd->GetContentIndex());
            else if( pC )
            {
                aNewStt.Assign(*pC, pC->Len() );
            }

            if( aNewStt <= *pEnd )
                *pNew->GetPoint() = aNewStt;
        }
        else
            aNewStt = *pNew->GetPoint();
#if OSL_DEBUG_LEVEL > 0
        CheckPosition( pNew->GetPoint(), pNew->GetMark() );
#endif

        if( *pNew->GetPoint() != *pNew->GetMark() &&
            pNew->HasValidRange())
        {
            ret.push_back(std::move(pNew));
        }

        if( aNewStt >= *pEnd )
            break;
        pC = SwNodes::GoNext(&aNewStt);
        if( !pC )
            break;
    } while( aNewStt < *pEnd );

    return ret;
}

} // namespace sw

static void lcl_setRowNotTracked(SwNode& rNode)
{
    SwDoc& rDoc = rNode.GetDoc();
    const SwTableBox* pTableBox = rNode.GetTableBox();
    if ( rDoc.GetIDocumentUndoRedo().DoesUndo() && pTableBox )
    {
        SvxPrintItem aSetTracking(RES_PRINT, false);
        SwNodeIndex aInsPos( *(pTableBox->GetSttNd()), 1);
        SwCursor aCursor( SwPosition(aInsPos), nullptr );
        ::sw::UndoGuard const undoGuard(rNode.GetDoc().GetIDocumentUndoRedo());
        rDoc.SetRowNotTracked( aCursor, aSetTracking );
    }
}

bool SwRedlineTable::InsertWithValidRanges(SwRangeRedline*& p, size_type* pInsPos)
{
    bool bAnyIns = false;
    bool bInsert = RedlineType::Insert == p->GetType();
    SwNode* pSttNode = &p->Start()->GetNode();

    std::vector<std::unique_ptr<SwRangeRedline>> redlines(
            GetAllValidRanges(std::unique_ptr<SwRangeRedline>(p)));

    // tdf#147180 set table change tracking in the empty row with text insertion
    if ( bInsert )
        lcl_setRowNotTracked(*pSttNode);

    for (std::unique_ptr<SwRangeRedline> & pRedline : redlines)
    {
        assert(pRedline->HasValidRange());
        size_type nInsPos;
        auto pTmpRedline = pRedline.release();
        if (Insert(pTmpRedline, nInsPos))
        {
            // tdf#147180 set table tracking to the table row
            lcl_setRowNotTracked(pTmpRedline->GetPointNode());

            pTmpRedline->CallDisplayFunc(nInsPos);
            bAnyIns = true;
            if (pInsPos && *pInsPos < nInsPos)
            {
                *pInsPos = nInsPos;
            }
        }
    }
    p = nullptr;
    return bAnyIns;
}

bool CompareSwRedlineTable::operator()(const SwRangeRedline* lhs, const SwRangeRedline* rhs) const
{
    return *lhs < *rhs;
}

SwRedlineTable::~SwRedlineTable()
{
   maVector.DeleteAndDestroyAll();
}

SwRedlineTable::size_type SwRedlineTable::GetPos(const SwRangeRedline* p) const
{
    vector_type::const_iterator it = maVector.find(p);
    if( it == maVector.end() )
        return npos;
    return it - maVector.begin();
}

void SwRedlineTable::Remove( const SwRangeRedline* p )
{
    const size_type nPos = GetPos(p);
    if (nPos == npos)
        return;
    Remove(nPos);
}

void SwRedlineTable::Remove( size_type nP )
{
    LOKRedlineNotification(RedlineNotification::Remove, maVector[nP]);
    SwDoc* pDoc = nullptr;
    if( !nP && 1 == size() )
        pDoc = &maVector.front()->GetDoc();

    if (mpMaxEndPos == maVector[nP])
        mpMaxEndPos = nullptr;
    maVector.erase( maVector.begin() + nP );

    if( pDoc && !pDoc->IsInDtor() )
    {
        SwViewShell* pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell();
        if( pSh )
            pSh->InvalidateWindows( SwRect( 0, 0, SAL_MAX_INT32, SAL_MAX_INT32 ) );
    }
}

void SwRedlineTable::DeleteAndDestroyAll()
{
    while (!maVector.empty())
    {
        auto const pRedline = maVector.back();
        maVector.erase_at(maVector.size() - 1);
        LOKRedlineNotification(RedlineNotification::Remove, pRedline);
        delete pRedline;
    }
    m_bHasOverlappingElements = false;
    mpMaxEndPos = nullptr;
}

void SwRedlineTable::DeleteAndDestroy(size_type const nP)
{
    auto const pRedline = maVector[nP];
    if (pRedline == mpMaxEndPos)
        mpMaxEndPos = nullptr;
    maVector.erase(maVector.begin() + nP);
    LOKRedlineNotification(RedlineNotification::Remove, pRedline);
    delete pRedline;
}

SwRedlineTable::size_type SwRedlineTable::FindNextOfSeqNo( size_type nSttPos ) const
{
    return nSttPos + 1 < size()
                ? FindNextSeqNo( operator[]( nSttPos )->GetSeqNo(), nSttPos+1 )
                : npos;
}

SwRedlineTable::size_type SwRedlineTable::FindPrevOfSeqNo( size_type nSttPos ) const
{
    return nSttPos ? FindPrevSeqNo( operator[]( nSttPos )->GetSeqNo(), nSttPos-1 )
                   : npos;
}

/// Find the next or preceding Redline with the same seq.no.
/// We can limit the search using look ahead (0 searches the whole array).
SwRedlineTable::size_type SwRedlineTable::FindNextSeqNo( sal_uInt16 nSeqNo, size_type nSttPos ) const
{
    auto constexpr nLookahead = 20;
    size_type nRet = npos;
    if( nSeqNo && nSttPos < size() )
    {
        size_type nEnd = size();
        const size_type nTmp = nSttPos + nLookahead;
        if (nTmp < nEnd)
        {
            nEnd = nTmp;
        }

        for( ; nSttPos < nEnd; ++nSttPos )
            if( nSeqNo == operator[]( nSttPos )->GetSeqNo() )
            {
                nRet = nSttPos;
                break;
            }
    }
    return nRet;
}

SwRedlineTable::size_type SwRedlineTable::FindPrevSeqNo( sal_uInt16 nSeqNo, size_type nSttPos ) const
{
    auto constexpr nLookahead = 20;
    size_type nRet = npos;
    if( nSeqNo && nSttPos < size() )
    {
        size_type nEnd = 0;
        if( nSttPos > nLookahead )
            nEnd = nSttPos - nLookahead;

        ++nSttPos;
        while( nSttPos > nEnd )
        {
            --nSttPos;
            if( nSeqNo == operator[](nSttPos)->GetSeqNo() )
            {
                nRet = nSttPos;
                break;
            }
        }
    }
    return nRet;
}

const SwRangeRedline* SwRedlineTable::FindAtPosition( const SwPosition& rSttPos,
                                        size_type& rPos,
                                        bool bNext ) const
{
    const SwRangeRedline* pFnd = nullptr;
    for( ; rPos < maVector.size() ; ++rPos )
    {
        const SwRangeRedline* pTmp = (*this)[ rPos ];
        if( pTmp->HasMark() && pTmp->IsVisible() )
        {
            auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition*
            if( bNext ? *pRStt <= rSttPos : *pRStt < rSttPos )
            {
                if( bNext ? *pREnd > rSttPos : *pREnd >= rSttPos )
                {
                    pFnd = pTmp;
                    break;
                }
            }
            else
                break;
        }
    }
    return pFnd;
}

namespace
{
bool lcl_CanCombineWithRange(const SwRangeRedline* pOrigin, SwRangeRedline* pActual,
                             SwRangeRedline* pOther, bool bReverseDir, bool bCheckChilds)
{
    if (pOrigin->IsVisible() != pOther->IsVisible())
        return false;

    if (bReverseDir)
    {
        if (*(pOther->End()) != *(pActual->Start()))
            return false;
    }
    else
    {
        if (*(pActual->End()) != *(pOther->Start()))
            return false;
    }

    if (!pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(0)))
    {
        if (!bCheckChilds)
        {
            return false;
        }

        // See if pOrigin and pOther can be combined because one redline data can combine with the
        // underlying redline data of the other redline.
        bool bChildCanCombine = false;
        if (pOther->GetStackCount() > 1
            && pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(1)))
        {
            bChildCanCombine = true;
        }

        if (!bChildCanCombine && pOrigin->GetStackCount() > 1
            && pOther->GetRedlineData(0).CanCombineForAcceptReject(pOrigin->GetRedlineData(1)))
        {
            bChildCanCombine = true;
        }

        if (!bChildCanCombine)
        {
            return false;
        }
    }
    if (pOther->Start()->GetNode().StartOfSectionNode()
        != pActual->Start()->GetNode().StartOfSectionNode())
        return false;

    return true;
}
}

const SwPosition& SwRedlineTable::GetMaxEndPos() const
{
    assert(!empty() && "cannot call this when the redline table is empty");
    if (mpMaxEndPos)
        return *mpMaxEndPos->End();
    for (const SwRangeRedline* i : maVector)
    {
        if (!mpMaxEndPos || *i->End() > *mpMaxEndPos->End())
            mpMaxEndPos = i;
    }
    assert(mpMaxEndPos);
    return *mpMaxEndPos->End();
}

void SwRedlineTable::getConnectedArea(size_type nPosOrigin, size_type& rPosStart,
                                      size_type& rPosEnd, bool bCheckChilds) const
{
    // Keep the original redline .. else we should memorize which children was checked
    // at the last combined redline.
    SwRangeRedline* pOrigin = (*this)[nPosOrigin];
    rPosStart = nPosOrigin;
    rPosEnd = nPosOrigin;
    SwRangeRedline* pRedline = pOrigin;
    SwRangeRedline* pOther;

    // connection info is already here..only the actual text is missing at import time
    // so no need to check Redline->GetContentIdx() here yet.
    while (rPosStart > 0 && (pOther = (*this)[rPosStart - 1])
           && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, true, bCheckChilds))
    {
        rPosStart--;
        pRedline = pOther;
    }
    pRedline = pOrigin;
    while (rPosEnd + 1 < size() && (pOther = (*this)[rPosEnd + 1])
           && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, false, bCheckChilds))
    {
        rPosEnd++;
        pRedline = pOther;
    }
}

OUString SwRedlineTable::getTextOfArea(size_type rPosStart, size_type rPosEnd) const
{
    // Normally a SwPaM::GetText() would be enough with rPosStart-start and rPosEnd-end
    // But at import time some text is not present there yet
    // we have to collect them 1 by 1

    OUStringBuffer sRet(256);

    for (size_type nIdx = rPosStart; nIdx <= rPosEnd; ++nIdx)
    {
        SwRangeRedline* pRedline = (*this)[nIdx];

        if (nullptr == pRedline->GetContentIdx())
        {
            pRedline->AppendTextTo(sRet);
        }
        else // otherwise it is saved in pContentSect, e.g. during ODT import
        {
            SwPaM aTmpPaM(pRedline->GetContentIdx()->GetNode(),
                              *pRedline->GetContentIdx()->GetNode().EndOfSectionNode());
            if (!aTmpPaM.Start()->nNode.GetNode().GetTextNode())
            {
                OUString sNew = aTmpPaM.GetText();
                if (sNew[0] == CH_TXTATR_NEWLINE)
                    sRet.append(sNew.subView(1));
                else
                    sRet.append(sNew);
            }
            else
                aTmpPaM.AppendTextTo(sRet); // append contents of aTmpPaM to sRet
        }
    }

    return sRet.makeStringAndClear();
}

bool SwRedlineTable::isMoved(size_type rPos) const
{
    // If it is already a part of a movement, then don't check it.
    if ((*this)[rPos]->GetMoved() != 0)
        return false;
    // First try with single redline. then try with combined redlines
    if (isMovedImpl(rPos, false))
        return true;
    else
        return isMovedImpl(rPos, true);
}

bool SwRedlineTable::isMovedImpl(size_type rPos, bool bTryCombined) const
{
    bool bRet = false;
    auto constexpr nLookahead = 20;
    SwRangeRedline* pRedline = (*this)[ rPos ];

    // set redline type of the searched pair
    RedlineType nPairType = pRedline->GetType();
    if ( RedlineType::Delete == nPairType )
        nPairType = RedlineType::Insert;
    else if ( RedlineType::Insert == nPairType )
        nPairType = RedlineType::Delete;
    else
        // only deleted or inserted text can be moved
        return false;

    OUString sTrimmed;
    SwRedlineTable::size_type nPosStart = rPos;
    SwRedlineTable::size_type nPosEnd = rPos;

    if (bTryCombined)
    {
        getConnectedArea(rPos, nPosStart, nPosEnd, false);
        if (nPosStart != nPosEnd)
            sTrimmed = getTextOfArea(nPosStart, nPosEnd).trim();
    }

    if (sTrimmed.isEmpty())
    {
        // if this redline is visible the content is in this PaM
        if (nullptr == pRedline->GetContentIdx())
        {
            sTrimmed = pRedline->GetText().trim();
        }
        else // otherwise it is saved in pContentSect, e.g. during ODT import
        {
            SwPaM aTmpPaM(pRedline->GetContentIdx()->GetNode(),
                             *pRedline->GetContentIdx()->GetNode().EndOfSectionNode());
            sTrimmed = aTmpPaM.GetText().trim();
        }
    }

    // detection of move needs at least 6 characters with an inner
    // space after stripping white spaces of the redline to skip
    // frequent deleted and inserted articles or other common
    // word parts, e.g. 'the' and 'of a' to detect as text moving
    if (sTrimmed.getLength() < 6 || sTrimmed.indexOf(' ') == -1)
    {
        return false;
    }

    // Todo: lessen the previous condition..:
    // if the source / destination is a whole node change then maybe space is not needed

    // search pair around the actual redline
    size_type nEnd = rPos + nLookahead < size()
        ? rPos + nLookahead
        : size();
    size_type nStart = rPos > nLookahead ? rPos - nLookahead : 0;
    // first, try to compare to single redlines
    // next, try to compare to combined redlines
    for (int nPass = 0; nPass < 2 && !bRet; nPass++)
    {
        for (size_type nPosAct = nStart; nPosAct < nEnd && !bRet; ++nPosAct)
        {
            SwRangeRedline* pPair = (*this)[nPosAct];

            // redline must be the requested type and from the same author
            if (nPairType != pPair->GetType() || pRedline->GetAuthor() != pPair->GetAuthor())
            {
                continue;
            }

            OUString sPairTrimmed = u""_ustr;
            SwRedlineTable::size_type nPairStart = nPosAct;
            SwRedlineTable::size_type nPairEnd = nPosAct;

            if (nPass == 0)
            {
                // if this redline is visible the content is in this PaM
                if (nullptr == pPair->GetContentIdx())
                {
                    sPairTrimmed = o3tl::trim(pPair->GetText());
                }
                else // otherwise it is saved in pContentSect, e.g. during ODT import
                {
                    // saved in pContentSect, e.g. during ODT import
                    SwPaM aPairPaM(pPair->GetContentIdx()->GetNode(),
                                         *pPair->GetContentIdx()->GetNode().EndOfSectionNode());
                    sPairTrimmed = o3tl::trim(aPairPaM.GetText());
                }
            }
            else
            {
                getConnectedArea(nPosAct, nPairStart, nPairEnd, false);
                if (nPairStart != nPairEnd)
                    sPairTrimmed = getTextOfArea(nPairStart, nPairEnd).trim();
            }

            // pair at tracked moving: same text by trimming trailing white spaces
            if (abs(sTrimmed.getLength() - sPairTrimmed.getLength()) <= 2
                && sTrimmed == sPairTrimmed)
            {
                sal_uInt32 nMID = getNewMovedID();
                if (nPosStart != nPosEnd)
                {
                    for (size_type nIdx = nPosStart; nIdx <= nPosEnd; ++nIdx)
                    {
                        (*this)[nIdx]->SetMoved(nMID);
                        if (nIdx != rPos)
                            (*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add);
                    }
                }
                else
                    pRedline->SetMoved(nMID);

                //in (nPass == 0) it will only call once .. as nPairStart == nPairEnd == nPosAct
                for (size_type nIdx = nPairStart; nIdx <= nPairEnd; ++nIdx)
                {
                    (*this)[nIdx]->SetMoved(nMID);
                    (*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add);
                }

                bRet = true;
            }

            //we can skip the combined redlines
            if (nPass == 1)
                nPosAct = nPairEnd;
        }
    }

    return bRet;
}

void SwRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineTable"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);

    for (SwRedlineTable::size_type nCurRedlinePos = 0; nCurRedlinePos < size(); ++nCurRedlinePos)
        operator[](nCurRedlinePos)->dumpAsXml(pWriter);

    (void)xmlTextWriterEndElement(pWriter);
}

SwRedlineExtraData::~SwRedlineExtraData()
{
}

void SwRedlineExtraData::Reject( SwPaM& ) const
{
}

bool SwRedlineExtraData::operator == ( const SwRedlineExtraData& ) const
{
    return false;
}

void SwRedlineExtraData::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineExtraData"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("typeName"),
                                      BAD_CAST(typeid(*this).name()));
    (void)xmlTextWriterEndElement(pWriter);
}

SwRedlineExtraData_FormatColl::SwRedlineExtraData_FormatColl( UIName aColl,
                                                sal_uInt16 nPoolFormatId,
                                                const std::shared_ptr<SfxItemSet>& pItemSet,
                                                bool bFormatAll )
    : m_sFormatNm(std::move(aColl)), m_nPoolId(nPoolFormatId), m_bFormatAll(bFormatAll)
{
    if( pItemSet && pItemSet->Count() )
        m_pSet = pItemSet;
}

SwRedlineExtraData_FormatColl::~SwRedlineExtraData_FormatColl()
{
}

SwRedlineExtraData* SwRedlineExtraData_FormatColl::CreateNew() const
{
    return new SwRedlineExtraData_FormatColl( m_sFormatNm, m_nPoolId, m_pSet, m_bFormatAll );
}

void SwRedlineExtraData_FormatColl::Reject( SwPaM& rPam ) const
{
    SwDoc& rDoc = rPam.GetDoc();

    // What about Undo? Is it turned off?
    SwTextFormatColl* pColl = USHRT_MAX == m_nPoolId
                            ? rDoc.FindTextFormatCollByName( m_sFormatNm )
                            : rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( m_nPoolId );

    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));

    SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() );

    const SwPosition* pEnd = rPam.End();

    if ( !m_bFormatAll || pEnd->GetContentIndex() == 0 )
    {
        // don't reject the format of the next paragraph (that is handled by the next redline)
        if (aPam.GetPoint()->GetNode() > aPam.GetMark()->GetNode())
        {
            aPam.GetPoint()->Adjust(SwNodeOffset(-1));
            SwContentNode* pNode = aPam.GetPoint()->GetNode().GetContentNode();
            if ( pNode )
                aPam.GetPoint()->SetContent( pNode->Len() );
            else
                // tdf#147507 set it back to a content node to avoid of crashing
                aPam.GetPoint()->Adjust(SwNodeOffset(+1));
        }
        else if (aPam.GetPoint()->GetNode() < aPam.GetMark()->GetNode())
        {
            aPam.GetMark()->Adjust(SwNodeOffset(-1));
            SwContentNode* pNode = aPam.GetMark()->GetNode().GetContentNode();
            aPam.GetMark()->SetContent( pNode->Len() );
        }
    }

    if( pColl )
        rDoc.SetTextFormatColl( aPam, pColl, false );

    if( m_pSet )
        rDoc.getIDocumentContentOperations().InsertItemSet( aPam, *m_pSet );

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

bool SwRedlineExtraData_FormatColl::operator == ( const SwRedlineExtraData& r) const
{
    const SwRedlineExtraData_FormatColl& rCmp = static_cast<const SwRedlineExtraData_FormatColl&>(r);
    return m_sFormatNm == rCmp.m_sFormatNm && m_nPoolId == rCmp.m_nPoolId &&
            m_bFormatAll == rCmp.m_bFormatAll &&
            ( ( !m_pSet && !rCmp.m_pSet ) ||
               ( m_pSet && rCmp.m_pSet && *m_pSet == *rCmp.m_pSet ) );
}

void SwRedlineExtraData_FormatColl::SetItemSet( const std::shared_ptr<SfxItemSet>& pSet )
{
    if( pSet && pSet->Count() )
        m_pSet = pSet;
    else
        m_pSet.reset();
}

void SwRedlineExtraData_FormatColl::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineExtraData_FormatColl"));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("format-name"), BAD_CAST(m_sFormatNm.toString().toUtf8().getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pool-id"), BAD_CAST(OString::number(m_nPoolId).getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("format-all"), BAD_CAST(OString::boolean(m_bFormatAll).getStr()));

    SwRedlineExtraData::dumpAsXml(pWriter);

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

    (void)xmlTextWriterEndElement(pWriter);
}

SwRedlineExtraData_Format::SwRedlineExtraData_Format( const SfxItemSet& rSet )
{
    SfxItemIter aIter( rSet );
    for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
    {
        m_aWhichIds.push_back( pItem->Which() );
    }
}

SwRedlineExtraData_Format::SwRedlineExtraData_Format(
        const SwRedlineExtraData_Format& rCpy )
    : SwRedlineExtraData()
{
    m_aWhichIds.insert( m_aWhichIds.begin(), rCpy.m_aWhichIds.begin(), rCpy.m_aWhichIds.end() );
}

SwRedlineExtraData_Format::~SwRedlineExtraData_Format()
{
}

SwRedlineExtraData* SwRedlineExtraData_Format::CreateNew() const
{
    return new SwRedlineExtraData_Format( *this );
}

void SwRedlineExtraData_Format::Reject( SwPaM& rPam ) const
{
    SwDoc& rDoc = rPam.GetDoc();

    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));

    // Actually we need to reset the Attribute here!
    for( const auto& rWhichId : m_aWhichIds )
    {
        rDoc.getIDocumentContentOperations().InsertPoolItem( rPam, *GetDfltAttr( rWhichId ),
            SetAttrMode::DONTEXPAND );
    }

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

bool SwRedlineExtraData_Format::operator == ( const SwRedlineExtraData& rCmp ) const
{
    const size_t nEnd = m_aWhichIds.size();
    if( nEnd != static_cast<const SwRedlineExtraData_Format&>(rCmp).m_aWhichIds.size() )
        return false;

    for( size_t n = 0; n < nEnd; ++n )
    {
        if( static_cast<const SwRedlineExtraData_Format&>(rCmp).m_aWhichIds[n] != m_aWhichIds[n])
        {
            return false;
        }
    }
    return true;
}

void SwRedlineSaveDatas::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineSaveDatas"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);

    for (const auto& rRedlineData : m_Data)
    {
        (void)xmlTextWriterStartElement(pWriter, BAD_CAST("data"));
        const SwRedlineData* pData = rRedlineData.get();
        while (pData)
        {
            pData->dumpAsXml(pWriter);
            pData = pData->Next();
        }
        (void)xmlTextWriterEndElement(pWriter);
    }

    (void)xmlTextWriterEndElement(pWriter);
}

SwRedlineData::SwRedlineData( RedlineType eT, std::size_t nAut, sal_uInt32 nMovedID )
    : m_pNext( nullptr ), m_pExtraData( nullptr ),
    m_aStamp( DateTime::SYSTEM ),
    m_nAuthor( nAut ), m_eType( eT ), m_nSeqNo( 0 ), m_bAutoFormat(false), m_nMovedID(nMovedID)
{
    m_aStamp.SetNanoSec( 0 );
}

SwRedlineData::SwRedlineData(
    const SwRedlineData& rCpy,
    bool bCpyNext )
    : m_pNext( ( bCpyNext && rCpy.m_pNext ) ? new SwRedlineData( *rCpy.m_pNext ) : nullptr )
    , m_pExtraData( rCpy.m_pExtraData ? rCpy.m_pExtraData->CreateNew() : nullptr )
    , m_sComment( rCpy.m_sComment )
    , m_aStamp( rCpy.m_aStamp )
    , m_nAuthor( rCpy.m_nAuthor )
    , m_eType( rCpy.m_eType )
    , m_nSeqNo( rCpy.m_nSeqNo )
    , m_bAutoFormat(false)
    , m_nMovedID( rCpy.m_nMovedID )
{
}

// For sw3io: We now own pNext!
SwRedlineData::SwRedlineData(RedlineType eT, std::size_t nAut, const DateTime& rDT,
    sal_uInt32 nMovedID, OUString aCmnt, SwRedlineData *pNxt)
    : m_pNext(pNxt), m_pExtraData(nullptr), m_sComment(std::move(aCmnt)), m_aStamp(rDT),
    m_nAuthor(nAut), m_eType(eT), m_nSeqNo(0), m_bAutoFormat(false), m_nMovedID(nMovedID)
{
}

SwRedlineData::~SwRedlineData()
{
    delete m_pExtraData;
    delete m_pNext;
}

// Check whether the absolute difference between the two dates is no larger than one minute (can
// give inaccurate results if at least one of the dates is not valid/normalized):
static bool deltaOneMinute(DateTime const & t1, DateTime const & t2) {
    auto const [min, max] = std::minmax(t1, t2);
    // Avoid overflow of `min + tools::Time(0, 1)` below when min is close to the maximum valid
    // DateTime:
    if (min >= DateTime({31, 12, std::numeric_limits<sal_Int16>::max()}, {23, 59})) {
        return true;
    }
    return max <= min + tools::Time(0, 1);
}

bool SwRedlineData::CanCombine(const SwRedlineData& rCmp) const
{
    return m_nAuthor == rCmp.m_nAuthor &&
            m_eType == rCmp.m_eType &&
            m_sComment == rCmp.m_sComment &&
            deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) &&
            m_nMovedID == rCmp.m_nMovedID &&
            (( !m_pNext && !rCmp.m_pNext ) ||
                ( m_pNext && rCmp.m_pNext &&
                m_pNext->CanCombine( *rCmp.m_pNext ))) &&
            (( !m_pExtraData && !rCmp.m_pExtraData ) ||
                ( m_pExtraData && rCmp.m_pExtraData &&
                    *m_pExtraData == *rCmp.m_pExtraData ));
}

// Check if we could/should accept/reject the 2 redlineData at the same time.
// No need to check its children equality
bool SwRedlineData::CanCombineForAcceptReject(const SwRedlineData& rCmp) const
{
    return m_nAuthor == rCmp.m_nAuthor &&
            m_eType == rCmp.m_eType &&
            m_sComment == rCmp.m_sComment &&
            deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) &&
            m_nMovedID == rCmp.m_nMovedID &&
            (( !m_pExtraData && !rCmp.m_pExtraData ) ||
                ( m_pExtraData && rCmp.m_pExtraData &&
                    *m_pExtraData == *rCmp.m_pExtraData ));
}

/// ExtraData is copied. The Pointer's ownership is thus NOT transferred
/// to the Redline Object!
void SwRedlineData::SetExtraData( const SwRedlineExtraData* pData )
{
    delete m_pExtraData;

    // Check if there is data - and if so - delete it
    if( pData )
        m_pExtraData = pData->CreateNew();
    else
        m_pExtraData = nullptr;
}

const TranslateId STR_REDLINE_ARY[] =
{
    STR_UNDO_REDLINE_INSERT,
    STR_UNDO_REDLINE_DELETE,
    STR_UNDO_REDLINE_FORMAT,
    STR_UNDO_REDLINE_TABLE,
    STR_UNDO_REDLINE_FMTCOLL,
    STR_UNDO_REDLINE_PARAGRAPH_FORMAT,
    STR_UNDO_REDLINE_TABLE_ROW_INSERT,
    STR_UNDO_REDLINE_TABLE_ROW_DELETE,
    STR_UNDO_REDLINE_TABLE_CELL_INSERT,
    STR_UNDO_REDLINE_TABLE_CELL_DELETE
};

OUString SwRedlineData::GetDescr() const
{
    return SwResId(STR_REDLINE_ARY[static_cast<int>(GetType())]);
}

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

    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"), BAD_CAST(OString::number(GetSeqNo()).getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("author"), BAD_CAST(SwModule::get()->GetRedlineAuthor(GetAuthor()).toUtf8().getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date"), BAD_CAST(DateTimeToOString(GetTimeStamp()).getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("descr"), BAD_CAST(GetDescr().toUtf8().getStr()));

    OString sRedlineType;
    switch (GetType())
    {
        case RedlineType::Insert:
            sRedlineType = "REDLINE_INSERT"_ostr;
            break;
        case RedlineType::Delete:
            sRedlineType = "REDLINE_DELETE"_ostr;
            break;
        case RedlineType::Format:
            sRedlineType = "REDLINE_FORMAT"_ostr;
            break;
        case RedlineType::Table:
            sRedlineType = "REDLINE_TABLE"_ostr;
            break;
        case RedlineType::FmtColl:
            sRedlineType = "REDLINE_FMTCOLL"_ostr;
            break;
        case RedlineType::ParagraphFormat:
            sRedlineType = "REDLINE_PARAGRAPH_FORMAT"_ostr;
            break;
        default:
            sRedlineType = "UNKNOWN"_ostr;
            break;
    }
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), BAD_CAST(sRedlineType.getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("moved"), BAD_CAST(OString::number(m_nMovedID).getStr()));

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

    (void)xmlTextWriterEndElement(pWriter);
}

sal_uInt32 SwRangeRedline::s_nLastId = 1;

namespace
{
void lcl_LOKBroadcastCommentOperation(RedlineType type, const SwPaM& rPam)
{
    if (comphelper::LibreOfficeKit::isActive())
    {
        auto eHintType = RedlineType::Delete == type ? SwFormatFieldHintWhich::REDLINED_DELETION: SwFormatFieldHintWhich::INSERTED;
        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)
            const_cast<SwFormatField&>(pTextField->GetFormatField()).Broadcast(SwFormatFieldHint(&pTextField->GetFormatField(), eHintType));
    }
}
} // anonymous namespace

SwRangeRedline::SwRangeRedline(RedlineType eTyp, const SwPaM& rPam, sal_uInt32 nMovedID )
    : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), m_pRedlineData(
          new SwRedlineData(eTyp, GetDoc().getIDocumentRedlineAccess().GetRedlineAuthor(), nMovedID ) )
    ,
    m_nId( s_nLastId++ )
{
    GetBound().SetOwner(this);
    GetBound(false).SetOwner(this);

    m_bDelLastPara = false;
    m_bIsVisible = true;
    if( !rPam.HasMark() )
        DeleteMark();

    // set default comment for single annotations added or deleted
    if ( IsAnnotation() )
    {
        SetComment( RedlineType::Delete == eTyp
            ? SwResId(STR_REDLINE_COMMENT_DELETED)
            : SwResId(STR_REDLINE_COMMENT_ADDED) );

        lcl_LOKBroadcastCommentOperation(eTyp, rPam);
    }
}

SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPaM& rPam )
    : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ),
    m_pRedlineData( new SwRedlineData( rData )),
    m_nId( s_nLastId++ )
{
    GetBound().SetOwner(this);
    GetBound(false).SetOwner(this);

    m_bDelLastPara = false;
    m_bIsVisible = true;
    if( !rPam.HasMark() )
        DeleteMark();

    // set default comment for single annotations added or deleted
    if ( IsAnnotation() )
    {
        SetComment( RedlineType::Delete == rData.m_eType
            ? SwResId(STR_REDLINE_COMMENT_DELETED)
            : SwResId(STR_REDLINE_COMMENT_ADDED) );

        lcl_LOKBroadcastCommentOperation(rData.m_eType, rPam);
    }
}

SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPosition& rPos )
    : SwPaM( rPos ),
    m_pRedlineData( new SwRedlineData( rData )),
    m_nId( s_nLastId++ )
{
    GetBound().SetOwner(this);
    GetBound(false).SetOwner(this);

    m_bDelLastPara = false;
    m_bIsVisible = true;
}

SwRangeRedline::SwRangeRedline( const SwRangeRedline& rCpy )
    : SwPaM( *rCpy.GetMark(), *rCpy.GetPoint() ),
    m_pRedlineData( new SwRedlineData( *rCpy.m_pRedlineData )),
    m_nId( s_nLastId++ )
{
    GetBound().SetOwner(this);
    GetBound(false).SetOwner(this);

    m_bDelLastPara = false;
    m_bIsVisible = true;
    if( !rCpy.HasMark() )
        DeleteMark();
}

SwRangeRedline::~SwRangeRedline()
{
    if( m_oContentSect )
    {
        // delete the ContentSection
        if( !GetDoc().IsInDtor() )
            GetDoc().getIDocumentContentOperations().DeleteSection( &m_oContentSect->GetNode() );
        m_oContentSect.reset();
    }
    delete m_pRedlineData;
}

void MaybeNotifyRedlineModification(SwRangeRedline& rRedline, SwDoc& rDoc)
{
    if (!lcl_LOKRedlineNotificationEnabled())
        return;

    const SwRedlineTable& rRedTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
    for (SwRedlineTable::size_type i = 0; i < rRedTable.size(); ++i)
    {
        if (rRedTable[i] == &rRedline)
        {
            SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, &rRedline);
            break;
        }
    }
}

void SwRangeRedline::MaybeNotifyRedlinePositionModification(tools::Long nTop)
{
    if (!lcl_LOKRedlineNotificationEnabled())
        return;

    if(!m_oLOKLastNodeTop || *m_oLOKLastNodeTop != nTop)
    {
        m_oLOKLastNodeTop = nTop;
        SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, this);
    }
}

void SwRangeRedline::SetStart( const SwPosition& rPos, SwPosition* pSttPtr )
{
    if( !pSttPtr ) pSttPtr = Start();
    *pSttPtr = rPos;

    MaybeNotifyRedlineModification(*this, GetDoc());
}

void SwRangeRedline::SetEnd( const SwPosition& rPos, SwPosition* pEndPtr )
{
    if( !pEndPtr ) pEndPtr = End();
    *pEndPtr = rPos;

    MaybeNotifyRedlineModification(*this, GetDoc());
}

/// Do we have a valid Selection?
bool SwRangeRedline::HasValidRange() const
{
    const SwNode* pPtNd = &GetPoint()->GetNode(),
                * pMkNd = &GetMark()->GetNode();
    if( pPtNd->StartOfSectionNode() == pMkNd->StartOfSectionNode() &&
        !pPtNd->StartOfSectionNode()->IsTableNode() &&
        // invalid if points on the end of content
        // end-of-content only invalid if no content index exists
        ( pPtNd != pMkNd || GetContentIdx() != nullptr ||
          pPtNd != &pPtNd->GetNodes().GetEndOfContent() )
        )
        return true;
    return false;
}

void SwRangeRedline::CallDisplayFunc(size_t nMyPos)
{
    RedlineFlags eShow = RedlineFlags::ShowMask & GetDoc().getIDocumentRedlineAccess().GetRedlineFlags();
    if (eShow == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete))
        Show(0, nMyPos);
    else if (eShow == RedlineFlags::ShowInsert)
        Hide(0, nMyPos);
    else if (eShow == RedlineFlags::ShowDelete)
        ShowOriginal(0, nMyPos);
}

namespace
{
RedlineType GetRedlineTypeIgnoringAdditonalFormat(const SwRangeRedline& rRedline)
{
    RedlineType eType = rRedline.GetType();

    if (eType == RedlineType::Format && rRedline.GetStackCount() > 1
        && rRedline.GetType(1) == RedlineType::Delete)
    {
        // Consider format-on-delete the same as simple delete, so the range gets moved to the
        // "Deleted Change Tracking content" toplevel section from body content during file save.
        eType = RedlineType::Delete;
    }

    return eType;
}
}

void SwRangeRedline::Show(sal_uInt16 nLoop, size_t nMyPos, bool bForced)
{
    SwDoc& rDoc = GetDoc();

    bool bIsShowChangesInMargin = false;
    if ( !bForced )
    {
        SwViewShell* pSh = rDoc.getIDocumentLayoutAccess().GetCurrentViewShell();
        if (pSh)
            bIsShowChangesInMargin = pSh->GetViewOptions()->IsShowChangesInMargin();
        else
            bIsShowChangesInMargin = SwModule::get()->GetUsrPref(false)->IsShowChangesInMargin();
    }

    if( 1 > nLoop && !bIsShowChangesInMargin )
        return;

    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
    ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());

    switch (GetRedlineTypeIgnoringAdditonalFormat(*this))
    {
    case RedlineType::Insert:           // Content has been inserted
        m_bIsVisible = true;
        MoveFromSection(nMyPos);
        break;

    case RedlineType::Delete:           // Content has been deleted
        m_bIsVisible = !bIsShowChangesInMargin;

        if (m_bIsVisible)
            MoveFromSection(nMyPos);
        else
        {
            switch( nLoop )
            {
            case 0: MoveToSection();    break;
            case 1: CopyToSection();    break;
            case 2: DelCopyOfSection(nMyPos); break;
            }
        }
        break;

    case RedlineType::Format:           // Attributes have been applied
    case RedlineType::Table:            // Table structure has been modified
        InvalidateRange(Invalidation::Add);
        break;
    default:
        break;
    }
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
}

void SwRangeRedline::Hide(sal_uInt16 nLoop, size_t nMyPos, bool /*bForced*/)
{
    SwDoc& rDoc = GetDoc();
    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
    ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());

    switch (GetRedlineTypeIgnoringAdditonalFormat(*this))
    {
    case RedlineType::Insert:           // Content has been inserted
        m_bIsVisible = true;
        if( 1 <= nLoop )
            MoveFromSection(nMyPos);
        break;

    case RedlineType::Delete:           // Content has been deleted
        m_bIsVisible = false;
        switch( nLoop )
        {
        case 0: MoveToSection();    break;
        case 1: CopyToSection();    break;
        case 2: DelCopyOfSection(nMyPos); break;
        }
        break;

    case RedlineType::Format:           // Attributes have been applied
    case RedlineType::Table:            // Table structure has been modified
        if( 1 <= nLoop )
            InvalidateRange(Invalidation::Remove);
        break;
    default:
        break;
    }
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
}

void SwRangeRedline::ShowOriginal(sal_uInt16 nLoop, size_t nMyPos, bool /*bForced*/)
{
    SwDoc& rDoc = GetDoc();
    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();

    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
    ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());

    // Determine the Type, it's the first on Stack
    SwRedlineData* pCur = m_pRedlineData;
    while (pCur->m_pNext)
        pCur = pCur->m_pNext;

    switch( pCur->m_eType )
    {
    case RedlineType::Insert:           // Content has been inserted
        m_bIsVisible = false;
        switch( nLoop )
        {
        case 0: MoveToSection();    break;
        case 1: CopyToSection();    break;
        case 2: DelCopyOfSection(nMyPos); break;
        }
        break;

    case RedlineType::Delete:           // Content has been deleted
        m_bIsVisible = true;
        if( 1 <= nLoop )
            MoveFromSection(nMyPos);
        break;

    case RedlineType::Format:           // Attributes have been applied
    case RedlineType::Table:            // Table structure has been modified
        if( 1 <= nLoop )
            InvalidateRange(Invalidation::Remove);
        break;
    default:
        break;
    }
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
}

// trigger the Layout
void SwRangeRedline::InvalidateRange(Invalidation const eWhy)
{
    auto [pRStt, pREnd] = StartEnd(); // SwPosition*
    SwNodeOffset nSttNd = pRStt->GetNodeIndex(),
                 nEndNd = pREnd->GetNodeIndex();
    sal_Int32 nSttCnt = pRStt->GetContentIndex();
    sal_Int32 nEndCnt = pREnd->GetContentIndex();

    SwNodes& rNds = GetDoc().GetNodes();
    for (SwNodeOffset n(nSttNd); n <= nEndNd; ++n)
    {
        SwNode* pNode = rNds[n];

        if (pNode && pNode->IsTextNode())
        {
            SwTextNode* pNd = pNode->GetTextNode();

            SwUpdateAttr aHt(
                n == nSttNd ? nSttCnt : 0,
                n == nEndNd ? nEndCnt : pNd->GetText().getLength(),
                RES_UPDATEATTR_FMT_CHG);

            pNd->TriggerNodeUpdate(sw::UpdateAttrHint(&aHt, &aHt));

            // SwUpdateAttr must be handled first, otherwise indexes are off
            if (GetType() == RedlineType::Delete)
            {
                sal_Int32 const nStart(n == nSttNd ? nSttCnt : 0);
                sal_Int32 const nLen((n == nEndNd ? nEndCnt : pNd->GetText().getLength()) - nStart);
                if (eWhy == Invalidation::Add)
                {
                    sw::RedlineDelText const hint(nStart, nLen);
                    pNd->CallSwClientNotify(hint);
                }
                else
                {
                    sw::RedlineUnDelText const hint(nStart, nLen);
                    pNd->CallSwClientNotify(hint);
                }

                if (comphelper::LibreOfficeKit::isActive() && IsAnnotation())
                {
                    auto eHintType = eWhy == Invalidation::Add ? SwFormatFieldHintWhich::INSERTED: SwFormatFieldHintWhich::REMOVED;
                    const SwTextNode *pTextNode = this->GetPointNode().GetTextNode();
                    SwTextAttr* pTextAttr = pTextNode ? pTextNode->GetFieldTextAttrAt(this->GetPoint()->GetContentIndex() - 1, ::sw::GetTextAttrMode::Default) : nullptr;
                    SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pTextAttr));
                    if (pTextField)
                        const_cast<SwFormatField&>(pTextField->GetFormatField()).Broadcast(SwFormatFieldHint(&pTextField->GetFormatField(), eHintType));
                }
            }
        }
    }
}

/** Calculates the start and end position of the intersection rTmp and
    text node nNdIdx */
bool SwRangeRedline::CalcStartEnd(SwNodeOffset const nNdIdx,
        sal_Int32 & rStart, sal_Int32 & rEnd) const
{
    auto [pRStt, pREnd] = StartEnd(); // SwPosition*
    if( pRStt->GetNodeIndex() < nNdIdx )
    {
        if( pREnd->GetNodeIndex() > nNdIdx )
        {
            rStart = 0;             // Paragraph is completely enclosed
            rEnd = COMPLETE_STRING;
        }
        else if (pREnd->GetNodeIndex() == nNdIdx)
        {
            rStart = 0;             // Paragraph is overlapped in the beginning
            rEnd = pREnd->GetContentIndex();
        }
        else // redline ends before paragraph
        {
            rStart = COMPLETE_STRING;
            rEnd = COMPLETE_STRING;
            return true;
        }
    }
    else if( pRStt->GetNodeIndex() == nNdIdx )
    {
        rStart = pRStt->GetContentIndex();
        if( pREnd->GetNodeIndex() == nNdIdx )
            rEnd = pREnd->GetContentIndex(); // Within the Paragraph
        else
            rEnd = COMPLETE_STRING;      // Paragraph is overlapped in the end
    }
    else
    {
        rStart = COMPLETE_STRING;
        rEnd = COMPLETE_STRING;
    }
    return false;
}

static void lcl_storeAnnotationMarks(SwDoc& rDoc, const SwPosition* pStart, const SwPosition* pEnd)
{
    // tdf#115815 keep original start position of collapsed annotation ranges
    // as temporary bookmarks (removed after file saving and file loading)
    IDocumentMarkAccess& rDMA(*rDoc.getIDocumentMarkAccess());
    for (auto iter = rDMA.findFirstAnnotationMarkNotStartsBefore(*pStart);
          iter != rDMA.getAnnotationMarksEnd(); ++iter)
    {
        SwPosition const& rStartPos((**iter).GetMarkStart());
        // vector is sorted by start pos, so we can exit early
        if ( rStartPos > *pEnd )
            break;
        if ( *pStart <= rStartPos && rStartPos < *pEnd )
        {
            auto pOldMark = rDMA.findAnnotationBookmark((**iter).GetName());
            if ( pOldMark == rDMA.getBookmarksEnd() )
            {
                // at start of redlines use a 1-character length bookmark range
                // instead of a 0-character length bookmark position to avoid its losing
                sal_Int32 nLen = (*pStart == rStartPos) ? 1 : 0;
                SwPaM aPam( rStartPos.GetNode(), rStartPos.GetContentIndex(),
                                rStartPos.GetNode(), rStartPos.GetContentIndex() + nLen);
                ::sw::mark::Bookmark* pBookmark = rDMA.makeAnnotationBookmark(
                    aPam,
                    (**iter).GetName(),
                    sw::mark::InsertMode::New);
                if (pBookmark)
                {
                    pBookmark->SetKeyCode(vcl::KeyCode());
                    pBookmark->SetShortName(OUString());
                }
            }
        }
    }
}

void SwRangeRedline::MoveToSection()
{
    if( !m_oContentSect )
    {
        auto [pStart, pEnd] = StartEnd(); // SwPosition*

        SwDoc& rDoc = GetDoc();
        SwPaM aPam( *pStart, *pEnd );
        SwContentNode* pCSttNd = pStart->GetNode().GetContentNode();
        SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode();

        if( !pCSttNd )
        {
            // In order to not move other Redlines' indices, we set them
            // to the end (is exclusive)
            const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
            for(SwRangeRedline* pRedl : rTable)
            {
                if( pRedl->GetBound() == *pStart )
                    pRedl->GetBound() = *pEnd;
                if( pRedl->GetBound(false) == *pStart )
                    pRedl->GetBound(false) = *pEnd;
            }
        }

        SwStartNode* pSttNd;
        SwNodes& rNds = rDoc.GetNodes();
        if( pCSttNd || pCEndNd )
        {
            SwTextFormatColl* pColl = (pCSttNd && pCSttNd->IsTextNode() )
                                    ? pCSttNd->GetTextNode()->GetTextColl()
                                    : (pCEndNd && pCEndNd->IsTextNode() )
                                        ? pCEndNd->GetTextNode()->GetTextColl()
                                        : rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD);

            pSttNd = rNds.MakeTextSection( rNds.GetEndOfRedlines(),
                                            SwNormalStartNode, pColl );
            SwTextNode* pTextNd = rNds[ pSttNd->GetIndex() + 1 ]->GetTextNode();

            SwPosition aPos( *pTextNd );
            if( pCSttNd && pCEndNd )
            {
                // tdf#140982 keep annotation ranges in deletions in margin mode
                lcl_storeAnnotationMarks( rDoc, pStart, pEnd );
                rDoc.getIDocumentContentOperations().MoveAndJoin( aPam, aPos );
            }
            else
            {
                if( pCSttNd && !pCEndNd )
                    m_bDelLastPara = true;
                rDoc.getIDocumentContentOperations().MoveRange( aPam, aPos,
                    SwMoveFlags::DEFAULT );
            }
        }
        else
        {
            pSttNd = SwNodes::MakeEmptySection( rNds.GetEndOfRedlines() );

            SwPosition aPos( *pSttNd->EndOfSectionNode() );
            rDoc.getIDocumentContentOperations().MoveRange( aPam, aPos,
                SwMoveFlags::DEFAULT );
        }
        m_oContentSect.emplace( *pSttNd );

        if( pStart == GetPoint() )
            Exchange();

        DeleteMark();
    }
    else
        InvalidateRange(Invalidation::Remove);
}

void SwRangeRedline::CopyToSection()
{
    if( m_oContentSect )
        return;

    auto [pStart, pEnd] = StartEnd(); // SwPosition*

    SwContentNode* pCSttNd = pStart->GetNode().GetContentNode();
    SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode();

    SwStartNode* pSttNd;
    SwDoc& rDoc = GetDoc();
    SwNodes& rNds = rDoc.GetNodes();

    bool bSaveCopyFlag = rDoc.IsCopyIsMove(),
         bSaveRdlMoveFlg = rDoc.getIDocumentRedlineAccess().IsRedlineMove();
    rDoc.SetCopyIsMove( true );

    // The IsRedlineMove() flag causes the behaviour of the
    // DocumentContentOperationsManager::CopyFlyInFlyImpl() method to change,
    // which will eventually be called by the CopyRange() below.
    rDoc.getIDocumentRedlineAccess().SetRedlineMove(true);

    if( pCSttNd )
    {
        SwTextFormatColl* pColl = pCSttNd->IsTextNode()
                                ? pCSttNd->GetTextNode()->GetTextColl()
                                : rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD);

        pSttNd = rNds.MakeTextSection( rNds.GetEndOfRedlines(),
                                        SwNormalStartNode, pColl );

        SwPosition aPos( *pSttNd, SwNodeOffset(1) );

        // tdf#115815 keep original start position of collapsed annotation ranges
        // as temporary bookmarks (removed after file saving and file loading)
        lcl_storeAnnotationMarks( rDoc, pStart, pEnd );
        rDoc.getIDocumentContentOperations().CopyRange(*this, aPos, SwCopyFlags::CheckPosInFly);

        // Take over the style from the EndNode if needed
        // We don't want this in Doc::Copy
        if( pCEndNd && pCEndNd != pCSttNd )
        {
            SwContentNode* pDestNd = aPos.GetNode().GetContentNode();
            if( pDestNd )
            {
                if( pDestNd->IsTextNode() && pCEndNd->IsTextNode() )
                    pCEndNd->GetTextNode()->CopyCollFormat(*pDestNd->GetTextNode());
                else
                    pDestNd->ChgFormatColl( pCEndNd->GetFormatColl() );
            }
        }
    }
    else
    {
        pSttNd = SwNodes::MakeEmptySection( rNds.GetEndOfRedlines() );

        if( pCEndNd )
        {
            SwPosition aPos( *pSttNd->EndOfSectionNode() );
            rDoc.getIDocumentContentOperations().CopyRange(*this, aPos, SwCopyFlags::CheckPosInFly);
        }
        else
        {
            SwNodeRange aRg( pStart->GetNode(), SwNodeOffset(0), pEnd->GetNode(), SwNodeOffset(1) );
            rDoc.GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, *pSttNd->EndOfSectionNode());
        }
    }
    m_oContentSect.emplace( *pSttNd );

    rDoc.SetCopyIsMove( bSaveCopyFlag );
    rDoc.getIDocumentRedlineAccess().SetRedlineMove( bSaveRdlMoveFlg );
}

void SwRangeRedline::DelCopyOfSection(size_t nMyPos)
{
    if( !m_oContentSect )
        return;

    auto [pStart, pEnd] = StartEnd(); // SwPosition*

    SwDoc& rDoc = GetDoc();
    SwPaM aPam( *pStart, *pEnd );
    SwContentNode* pCSttNd = pStart->GetNode().GetContentNode();
    SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode();

    if( !pCSttNd )
    {
        // In order to not move other Redlines' indices, we set them
        // to the end (is exclusive)
        const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
        for(SwRangeRedline* pRedl : rTable)
        {
            if( pRedl->GetBound() == *pStart )
                pRedl->GetBound() = *pEnd;
            if( pRedl->GetBound(false) == *pStart )
                pRedl->GetBound(false) = *pEnd;
        }
    }

    if( pCSttNd && pCEndNd )
    {
        // #i100466# - force a <join next> on <delete and join> operation
        // tdf#125319 - rather not?
        rDoc.getIDocumentContentOperations().DeleteAndJoin(aPam/*, true*/);
    }
    else if( pCSttNd || pCEndNd )
    {
        if( pCSttNd && !pCEndNd )
            m_bDelLastPara = true;
        rDoc.getIDocumentContentOperations().DeleteRange( aPam );

        if( m_bDelLastPara )
        {
            // To prevent dangling references to the paragraph to
            // be deleted, redline that point into this paragraph should be
            // moved to the new end position. Since redlines in the redline
            // table are sorted and the pEnd position is an endnode (see
            // bDelLastPara condition above), only redlines before the
            // current ones can be affected.
            const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
            size_t n = nMyPos;
            for( bool bBreak = false; !bBreak && n > 0; )
            {
                --n;
                bBreak = true;
                if( rTable[ n ]->GetBound() == *aPam.GetPoint() )
                {
                    rTable[ n ]->GetBound() = *pEnd;
                    bBreak = false;
                }
                if( rTable[ n ]->GetBound(false) == *aPam.GetPoint() )
                {
                    rTable[ n ]->GetBound(false) = *pEnd;
                    bBreak = false;
                }
            }

            *GetPoint() = *pEnd;
            *GetMark() = *pEnd;
            DeleteMark();

            aPam.DeleteMark();
            aPam.GetPoint()->SetContent(0);;
            rDoc.getIDocumentContentOperations().DelFullPara( aPam );
        }
    }
    else
    {
        rDoc.getIDocumentContentOperations().DeleteRange( aPam );
    }

    if( pStart == GetPoint() )
        Exchange();

    DeleteMark();
}

void SwRangeRedline::MoveFromSection(size_t nMyPos)
{
    if( m_oContentSect )
    {
        SwDoc& rDoc = GetDoc();
        const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
        std::vector<SwPosition*> aBeforeArr, aBehindArr;
        bool bBreak = false;
        SwRedlineTable::size_type n;

        for( n = nMyPos+1; !bBreak && n < rTable.size(); ++n )
        {
            bBreak = true;
            if( rTable[ n ]->GetBound() == *GetPoint() )
            {
                SwRangeRedline* pRedl = rTable[n];
                aBehindArr.push_back(&pRedl->GetBound());
                bBreak = false;
            }
            if( rTable[ n ]->GetBound(false) == *GetPoint() )
            {
                SwRangeRedline* pRedl = rTable[n];
                aBehindArr.push_back(&pRedl->GetBound(false));
                bBreak = false;
            }
        }
        for( bBreak = false, n = nMyPos; !bBreak && n ; )
        {
            --n;
            bBreak = true;
            if( rTable[ n ]->GetBound() == *GetPoint() )
            {
                SwRangeRedline* pRedl = rTable[n];
                aBeforeArr.push_back(&pRedl->GetBound());
                bBreak = false;
            }
            if( rTable[ n ]->GetBound(false) == *GetPoint() )
            {
                SwRangeRedline* pRedl = rTable[n];
                aBeforeArr.push_back(&pRedl->GetBound(false));
                bBreak = false;
            }
        }

        const SwNode* pKeptContentSectNode( &m_oContentSect->GetNode() ); // #i95711#
        {
            SwPaM aPam( m_oContentSect->GetNode(),
                        *m_oContentSect->GetNode().EndOfSectionNode(), SwNodeOffset(1),
                        SwNodeOffset( m_bDelLastPara ? -2 : -1 ) );
            SwContentNode* pCNd = aPam.GetPointContentNode();
            if( pCNd )
                aPam.GetPoint()->SetContent( pCNd->Len() );
            else
                aPam.GetPoint()->Adjust(SwNodeOffset(+1));

            SwFormatColl* pColl = pCNd && pCNd->Len() && aPam.GetPoint()->GetNode() !=
                                        aPam.GetMark()->GetNode()
                                ? pCNd->GetFormatColl() : nullptr;

            SwNodeIndex aNdIdx( GetPoint()->GetNode(), -1 );
            const sal_Int32 nPos = GetPoint()->GetContentIndex();

            SwPosition aPos( *GetPoint() );
            if( m_bDelLastPara && *aPam.GetPoint() == *aPam.GetMark() )
            {
                aPos.Adjust(SwNodeOffset(-1));

                rDoc.getIDocumentContentOperations().AppendTextNode( aPos );
            }
            else
            {
                rDoc.getIDocumentContentOperations().MoveRange( aPam, aPos,
                    SwMoveFlags::ALLFLYS );
            }

            SetMark();
            *GetPoint() = std::move(aPos);
            GetMark()->Assign(aNdIdx.GetIndex() + 1);
            pCNd = GetMark()->GetNode().GetContentNode();
            if( pCNd )
                GetMark()->SetContent( nPos );

            if( m_bDelLastPara )
            {
                GetPoint()->Adjust(SwNodeOffset(+1));
                pCNd = GetPointContentNode();
                m_bDelLastPara = false;
            }
            else if( pColl )
                pCNd = GetPointContentNode();

            if( pColl && pCNd )
                pCNd->ChgFormatColl( pColl );
        }

        // #i95771#
        // Under certain conditions the previous <SwDoc::Move(..)> has already
        // removed the change tracking section of this <SwRangeRedline> instance from
        // the change tracking nodes area.
        // Thus, check if <pContentSect> still points to the change tracking section
        // by comparing it with the "indexed" <SwNode> instance copied before
        // perform the intrinsic move.
        // Note: Such condition is e.g. a "delete" change tracking only containing a table.
        if ( &m_oContentSect->GetNode() == pKeptContentSectNode )
        {
            rDoc.getIDocumentContentOperations().DeleteSection( &m_oContentSect->GetNode() );
        }
        m_oContentSect.reset();

        // adjustment of redline table positions must take start and
        // end into account, not point and mark.
        for( auto& pItem : aBeforeArr )
            *pItem = *Start();
        for( auto& pItem : aBehindArr )
            *pItem = *End();
    }
    else
        InvalidateRange(Invalidation::Add);
}

// for Undo
void SwRangeRedline::SetContentIdx( const SwNodeIndex& rIdx )
{
    if( !m_oContentSect )
    {
        m_oContentSect = rIdx;
        m_bIsVisible = false;
    }
    else
    {
        OSL_FAIL("SwRangeRedline::SetContentIdx: invalid state");
    }
}

// for Undo
void SwRangeRedline::ClearContentIdx()
{
    if( m_oContentSect )
    {
        m_oContentSect.reset();
    }
    else
    {
        OSL_FAIL("SwRangeRedline::ClearContentIdx: invalid state");
    }
}

bool SwRangeRedline::CanCombine( const SwRangeRedline& rRedl ) const
{
    return  IsVisible() && rRedl.IsVisible() &&
            m_pRedlineData->CanCombine( *rRedl.m_pRedlineData );
}

void SwRangeRedline::PushData( const SwRangeRedline& rRedl, bool bOwnAsNext )
{
    SwRedlineData* pNew = new SwRedlineData( *rRedl.m_pRedlineData, false );
    if( bOwnAsNext )
    {
        pNew->m_pNext = m_pRedlineData;
        m_pRedlineData = pNew;
    }
    else
    {
        pNew->m_pNext = m_pRedlineData->m_pNext;
        m_pRedlineData->m_pNext = pNew;
    }
}

bool SwRangeRedline::PopData()
{
    if( !m_pRedlineData->m_pNext )
        return false;
    SwRedlineData* pCur = m_pRedlineData;
    m_pRedlineData = pCur->m_pNext;
    pCur->m_pNext = nullptr;
    delete pCur;
    return true;
}

bool SwRangeRedline::PopAllDataAfter(int depth)
{
    assert(depth > 0);
    SwRedlineData* pCur = m_pRedlineData;
    while (depth > 1)
    {
        pCur = pCur->m_pNext;
        if (!pCur)
            return false;
        depth--;
    }

    while (pCur->m_pNext)
    {
        SwRedlineData* pToDelete = pCur->m_pNext;
        pCur->m_pNext = pToDelete->m_pNext;
        delete pToDelete;
    }
    return true;
}

sal_uInt16 SwRangeRedline::GetStackCount() const
{
    sal_uInt16 nRet = 1;
    for( SwRedlineData* pCur = m_pRedlineData; pCur->m_pNext; pCur = pCur->m_pNext )
        ++nRet;
    return nRet;
}

std::size_t SwRangeRedline::GetAuthor( sal_uInt16 nPos ) const
{
    return GetRedlineData(nPos).m_nAuthor;
}

OUString const & SwRangeRedline::GetAuthorString( sal_uInt16 nPos ) const
{
    return SwModule::get()->GetRedlineAuthor(GetRedlineData(nPos).m_nAuthor);
}

sal_uInt32 SwRangeRedline::GetMovedID(sal_uInt16 nPos) const
{
    return GetRedlineData(nPos).m_nMovedID;
}

const DateTime& SwRangeRedline::GetTimeStamp(sal_uInt16 nPos) const
{
    return GetRedlineData(nPos).m_aStamp;
}

RedlineType SwRangeRedline::GetType( sal_uInt16 nPos ) const
{
    return GetRedlineData(nPos).m_eType;
}

bool SwRangeRedline::IsAnnotation() const
{
    return GetText().getLength() == 1 && GetText()[0] == CH_TXTATR_INWORD;
}

const OUString& SwRangeRedline::GetComment( sal_uInt16 nPos ) const
{
    return GetRedlineData(nPos).m_sComment;
}

bool SwRangeRedline::operator<( const SwRangeRedline& rCmp ) const
{
    auto [pStart, pEnd] = StartEnd();
    auto [pCmpStart, pCmpEnd] = rCmp.StartEnd();
    if (*pStart < *pCmpStart)
        return true;

    return *pStart == *pCmpStart && *pEnd < *pCmpEnd;
}

const SwRedlineData & SwRangeRedline::GetRedlineData(const sal_uInt16 nPos) const
{
    SwRedlineData * pCur = m_pRedlineData;

    sal_uInt16 nP = nPos;

    while (nP > 0 && nullptr != pCur->m_pNext)
    {
        pCur = pCur->m_pNext;

        nP--;
    }

    SAL_WARN_IF( nP != 0, "sw.core", "Pos " << nPos << " is " << nP << " too big");

    return *pCur;
}

static OUString getRedlineDescrFromPaM(const SwPaM& rPaM, bool bSimplified)
{
    if (const SwTextNode* pTextNode = rPaM.GetPointNode().GetTextNode())
    {
        if (const SwTextAttr* pTextAttr = pTextNode->GetFieldTextAttrAt(
                rPaM.GetPoint()->GetContentIndex() - 1, ::sw::GetTextAttrMode::Default))
        {
            OUString result = pTextAttr->GetFormatField().GetField()->GetFieldName();
            return bSimplified ? result
                               : SwResId(STR_START_QUOTE) + result + SwResId(STR_END_QUOTE);
        }
    }
    return DenoteSpecialCharacters(rPaM.GetText().replace('\n', ' '), /*bQuoted=*/!bSimplified);
}

OUString SwRangeRedline::GetDescr(bool bSimplified) const
{
    // get description of redline data (e.g.: "insert $1")
    OUString aResult = GetRedlineData().GetDescr();

    OUString sDescr;
    // if this redline is visible the content is in this PaM
    if (!m_oContentSect.has_value())
    {
        sDescr = getRedlineDescrFromPaM(*this, bSimplified);
    }
    else // otherwise it is saved in pContentSect
    {
        const SwNode& rNode = m_oContentSect->GetNode();
        sDescr = getRedlineDescrFromPaM(SwPaM(rNode, *rNode.EndOfSectionNode()), bSimplified);
    }

    // replace $1 in description by description of the redlines text
    const OUString aTmpStr = ShortenString(sDescr, nUndoStringLength, SwResId(STR_LDOTS));

    if (!bSimplified)
    {
        SwRewriter aRewriter;
        aRewriter.AddRule(UndoArg1, aTmpStr);

        aResult = aRewriter.Apply(aResult);
    }
    else
    {
        aResult = aTmpStr;
        // more shortening
        sal_Int32 nPos = aTmpStr.indexOf(SwResId(STR_LDOTS));
        if (nPos > 5)
            aResult = aTmpStr.copy(0, nPos + SwResId(STR_LDOTS).getLength());
    }

    return aResult;
}

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

    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("id"), "%s", BAD_CAST(OString::number(m_nId).getStr()));

    const SwRedlineData* pRedlineData = m_pRedlineData;
    while (pRedlineData)
    {
        pRedlineData->dumpAsXml(pWriter);
        pRedlineData = pRedlineData->Next();
    }

    SwPaM::dumpAsXml(pWriter);

    (void)xmlTextWriterEndElement(pWriter);
}

void SwExtraRedlineTable::Insert( SwExtraRedline* p )
{
    m_aExtraRedlines.push_back( p );
    //p->CallDisplayFunc();
}

void SwExtraRedlineTable::DeleteAndDestroy(sal_uInt16 const nPos)
{
    /*
    SwDoc* pDoc = 0;
    if( !nP && nL && nL == size() )
        pDoc = front()->GetDoc();
    */

    delete m_aExtraRedlines[nPos];
    m_aExtraRedlines.erase(m_aExtraRedlines.begin() + nPos);

    /*
    SwViewShell* pSh;
    if( pDoc && !pDoc->IsInDtor() &&
        0 != ( pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) )
        pSh->InvalidateWindows( SwRect( 0, 0, SAL_MAX_INT32, SAL_MAX_INT32 ) );
    */
}

void SwExtraRedlineTable::DeleteAndDestroyAll()
{
    while (!m_aExtraRedlines.empty())
    {
        auto const pRedline = m_aExtraRedlines.back();
        m_aExtraRedlines.pop_back();
        delete pRedline;
    }
}

SwExtraRedline::~SwExtraRedline()
{
}

SwTableRowRedline::SwTableRowRedline(const SwRedlineData& rData, const SwTableLine& rTableLine)
    : m_aRedlineData(rData)
    , m_rTableLine(rTableLine)
{
}

SwTableRowRedline::~SwTableRowRedline()
{
}

SwTableCellRedline::SwTableCellRedline(const SwRedlineData& rData, const SwTableBox& rTableBox)
    : m_aRedlineData(rData)
    , m_rTableBox(rTableBox)
{
}

SwTableCellRedline::~SwTableCellRedline()
{
}

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