/* -*- 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 <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/util/SearchAlgorithms2.hpp>
#include <com/sun/star/util/SearchFlags.hpp>
#include <i18nlangtag/languagetag.hxx>
#include <i18nutil/searchopt.hxx>
#include <osl/diagnose.h>
#include <unotools/syslocale.hxx>
#include <hintids.hxx>
#include <svl/itemiter.hxx>
#include <svl/srchitem.hxx>
#include <svl/whiter.hxx>
#include <editeng/colritem.hxx>
#include <editeng/fontitem.hxx>
#include <fmtpdsc.hxx>
#include <txatbase.hxx>
#include <charfmt.hxx>
#include <crsrsh.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentState.hxx>
#include <swcrsr.hxx>
#include <ndtxt.hxx>
#include <pamtyp.hxx>
#include <txtfrm.hxx>
#include <swundo.hxx>
#include <optional>

#include <algorithm>
#include <memory>

using namespace ::com::sun::star;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::util;

// Special case for SvxFontItem: only compare the name
static bool CmpAttr( const SfxPoolItem& rItem1, const SfxPoolItem& rItem2 )
{
    switch( rItem1.Which() )
    {
    case RES_CHRATR_FONT:
        return rItem1.StaticWhichCast(RES_CHRATR_FONT).GetFamilyName() == rItem2.StaticWhichCast(RES_CHRATR_FONT).GetFamilyName();

    case RES_CHRATR_COLOR:
        return rItem1.StaticWhichCast(RES_CHRATR_COLOR).GetValue().IsRGBEqual(rItem2.StaticWhichCast(RES_CHRATR_COLOR).GetValue());
    case RES_PAGEDESC:
        ::std::optional<sal_uInt16> const oNumOffset1 = rItem1.StaticWhichCast(RES_PAGEDESC).GetNumOffset();
        ::std::optional<sal_uInt16> const oNumOffset2 = rItem2.StaticWhichCast(RES_PAGEDESC).GetNumOffset();

        if (oNumOffset1 != oNumOffset2)
            return false;

        return rItem1.StaticWhichCast(RES_PAGEDESC).GetPageDesc() == rItem2.StaticWhichCast(RES_PAGEDESC).GetPageDesc();
    }
    return rItem1 == rItem2;
}

const SwTextAttr* GetFrwrdTextHint( const SwpHints& rHtsArr, size_t& rPos,
                                    sal_Int32 nContentPos )
{
    while( rPos < rHtsArr.Count() )
    {
        const SwTextAttr *pTextHt = rHtsArr.Get( rPos++ );
        // the start of an attribute has to be in the section
        if( pTextHt->GetStart() >= nContentPos )
            return pTextHt; // valid text attribute
    }
    return nullptr; // invalid text attribute
}

const SwTextAttr* GetBkwrdTextHint( const SwpHints& rHtsArr, size_t& rPos,
                                  sal_Int32 nContentPos )
{
    while( rPos > 0 )
    {
        const SwTextAttr *pTextHt = rHtsArr.Get( --rPos );
        // the start of an attribute has to be in the section
        if( pTextHt->GetStart() < nContentPos )
            return pTextHt; // valid text attribute
    }
    return nullptr; // invalid text attribute
}

static void lcl_SetAttrPam( SwPaM& rPam, sal_Int32 nStart, const sal_Int32* pEnd,
                     const bool bSaveMark )
{
    sal_Int32 nContentPos;
    if( bSaveMark )
        nContentPos = rPam.GetMark()->GetContentIndex();
    else
        nContentPos = rPam.GetPoint()->GetContentIndex();
    bool bTstEnd = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode();

    rPam.GetPoint()->SetContent( nStart );
    rPam.SetMark(); // Point == GetMark

    // Point points to end of search area or end of attribute
    if( pEnd )
    {
        if( bTstEnd && *pEnd > nContentPos )
            rPam.GetPoint()->SetContent(nContentPos);
        else
            rPam.GetPoint()->SetContent(*pEnd);
    }
}

// TODO: provide documentation
/** search for a text attribute

    This function searches in a text node for a given attribute.
    If that is found then the SwPaM contains the section that surrounds the
    attribute (w.r.t. the search area).

    @param rTextNd   Text node to search in.
    @param rPam     ???
    @param rCmpItem ???
    @param fnMove   ???
    @return Returns <true> if found, <false> otherwise.
*/
static bool lcl_SearchAttr( const SwTextNode& rTextNd, SwPaM& rPam,
                    const SfxPoolItem& rCmpItem,
                    SwMoveFnCollection const & fnMove)
{
    if ( !rTextNd.HasHints() )
        return false;

    const SwTextAttr *pTextHt = nullptr;
    bool bForward = &fnMove == &fnMoveForward;
    size_t nPos = bForward ? 0 : rTextNd.GetSwpHints().Count();
    sal_Int32 nContentPos = rPam.GetPoint()->GetContentIndex();

    while( nullptr != ( pTextHt=(*fnMove.fnGetHint)(rTextNd.GetSwpHints(),nPos,nContentPos)))
        if (pTextHt->Which() == rCmpItem.Which())
        {
            lcl_SetAttrPam( rPam, pTextHt->GetStart(), pTextHt->End(), bForward );
            return true;
        }
    return false;
}

namespace {

/// search for multiple text attributes
struct SwSrchChrAttr
{
    sal_uInt16 nWhich;
    sal_Int32 nStt;
    sal_Int32 nEnd;

    SwSrchChrAttr(): nWhich(0), nStt(0), nEnd(0) {}

    SwSrchChrAttr( const SfxPoolItem& rItem,
                    sal_Int32 nStart, sal_Int32 nAnyEnd )
        : nWhich( rItem.Which() ), nStt( nStart ), nEnd( nAnyEnd )
    {}
};

class SwAttrCheckArr
{
    SwSrchChrAttr *m_pFindArr, *m_pStackArr;
    sal_Int32 m_nNodeStart;
    sal_Int32 m_nNodeEnd;
    sal_uInt16 m_nArrStart, m_nArrLen;
    sal_uInt16 m_nFound, m_nStackCount;
    SfxItemSet m_aComapeSet;
    bool m_bNoColls;
    bool m_bForward;

public:
    SwAttrCheckArr( const SfxItemSet& rSet, bool bForward, bool bNoCollections );
    ~SwAttrCheckArr();

    void SetNewSet( const SwTextNode& rTextNd, const SwPaM& rPam );

    /// how many attributes are there in total?
    sal_uInt16 Count() const    { return m_aComapeSet.Count(); }
    bool Found() const       { return m_nFound == m_aComapeSet.Count(); }
    bool CheckStack();

    sal_Int32 Start() const;
    sal_Int32 End() const;

    sal_Int32 GetNdStt() const { return m_nNodeStart; }
    sal_Int32 GetNdEnd() const { return m_nNodeEnd; }

    bool SetAttrFwd( const SwTextAttr& rAttr );
    bool SetAttrBwd( const SwTextAttr& rAttr );
};

}

SwAttrCheckArr::SwAttrCheckArr( const SfxItemSet& rSet, bool bFwd,
                                bool bNoCollections )
    : m_nNodeStart(0)
    , m_nNodeEnd(0)
    , m_nFound(0)
    , m_nStackCount(0)
    , m_aComapeSet( *rSet.GetPool(), svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_END-1> )
    , m_bNoColls(bNoCollections)
    , m_bForward(bFwd)
{
    m_aComapeSet.Put( rSet, false );

    // determine area of Fnd/Stack array (Min/Max)
    sal_uInt16 nMinUsedWhichID(0);
    sal_uInt16 nMaxUsedWhichID(0);

    if (0 != m_aComapeSet.Count())
    {
        nMinUsedWhichID = 5000; // SFX_WHICH_MAX+1;
        for (SfxItemIter aIter(m_aComapeSet); !aIter.IsAtEnd(); aIter.NextItem())
        {
            const sal_uInt16 nCurrentWhich(aIter.GetCurWhich());
            if (SfxItemPool::IsSlot(nCurrentWhich))
                continue;
            nMinUsedWhichID = std::min(nMinUsedWhichID, nCurrentWhich);
            nMaxUsedWhichID = std::max(nMaxUsedWhichID, nCurrentWhich);
        }

        if (nMinUsedWhichID > nMaxUsedWhichID)
            nMinUsedWhichID = nMaxUsedWhichID = 0;
    }

    m_nArrStart = nMinUsedWhichID;//m_aComapeSet.GetWhichByOffset( aIter.GetFirstPos() );
    m_nArrLen = nMaxUsedWhichID - nMinUsedWhichID + 1;//m_aComapeSet.GetWhichByOffset( aIter.GetLastPos() ) - m_nArrStart+1;

    char* pFndChar  = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ];
    char* pStackChar = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ];

    m_pFindArr = reinterpret_cast<SwSrchChrAttr*>(pFndChar);
    m_pStackArr = reinterpret_cast<SwSrchChrAttr*>(pStackChar);
}

SwAttrCheckArr::~SwAttrCheckArr()
{
    delete[] reinterpret_cast<char*>(m_pFindArr);
    delete[] reinterpret_cast<char*>(m_pStackArr);
}

void SwAttrCheckArr::SetNewSet( const SwTextNode& rTextNd, const SwPaM& rPam )
{
    std::fill(m_pFindArr, m_pFindArr + m_nArrLen, SwSrchChrAttr());
    std::fill(m_pStackArr, m_pStackArr + m_nArrLen, SwSrchChrAttr());
    m_nFound = 0;
    m_nStackCount = 0;

    if( m_bForward )
    {
        m_nNodeStart = rPam.GetPoint()->GetContentIndex();
        m_nNodeEnd = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode()
                ? rPam.GetMark()->GetContentIndex()
                : rTextNd.GetText().getLength();
    }
    else
    {
        m_nNodeEnd = rPam.GetPoint()->GetContentIndex();
        m_nNodeStart = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode()
                ? rPam.GetMark()->GetContentIndex()
                : 0;
    }

    if( m_bNoColls && !rTextNd.HasSwAttrSet() )
        return ;

    const SfxItemSet& rSet = rTextNd.GetSwAttrSet();

    SfxItemIter aIter( m_aComapeSet );
    const SfxPoolItem* pItem = aIter.GetCurItem();
    const SfxPoolItem* pFndItem;
    sal_uInt16 nWhich;

    do
    {
        if( IsInvalidItem( pItem ) )
        {
            nWhich = aIter.GetCurWhich();
            if( RES_TXTATR_END <= nWhich )
                break; // end of text attributes

            if( SfxItemState::SET == rSet.GetItemState( nWhich, !m_bNoColls, &pFndItem )
                && !CmpAttr( *pFndItem, rSet.GetPool()->GetUserOrPoolDefaultItem( nWhich ) ))
            {
                m_pFindArr[ nWhich - m_nArrStart ] =
                    SwSrchChrAttr( *pFndItem, m_nNodeStart, m_nNodeEnd );
                m_nFound++;
            }
        }
        else
        {
            nWhich = pItem->Which();
            if( RES_TXTATR_END <= nWhich )
                break; // end of text attributes

            if( CmpAttr( rSet.Get( nWhich, !m_bNoColls ), *pItem ) )
            {
                m_pFindArr[ nWhich - m_nArrStart ] =
                    SwSrchChrAttr( *pItem, m_nNodeStart, m_nNodeEnd );
                m_nFound++;
            }
        }

        pItem = aIter.NextItem();
    } while (pItem);
}

static bool
lcl_IsAttributeIgnorable(sal_Int32 const nNdStart, sal_Int32 const nNdEnd,
        SwSrchChrAttr const& rTmp)
{
    // #i115528#: if there is a paragraph attribute, it has been added by the
    // SwAttrCheckArr ctor, and nFound is 1.
    // if the paragraph is entirely covered by hints that override the paragraph
    // attribute, then this function must find an attribute to decrement nFound!
    // so check for an empty search range, let attributes that start/end there
    // cover it, and hope for the best...
    return ((nNdEnd == nNdStart)
            ? ((rTmp.nEnd <  nNdStart) || (nNdEnd <  rTmp.nStt))
            : ((rTmp.nEnd <= nNdStart) || (nNdEnd <= rTmp.nStt)));
}

bool SwAttrCheckArr::SetAttrFwd( const SwTextAttr& rAttr )
{
    SwSrchChrAttr aTmp( rAttr.GetAttr(), rAttr.GetStart(), rAttr.GetAnyEnd() );

    // ignore all attributes not in search range
    if (lcl_IsAttributeIgnorable(m_nNodeStart, m_nNodeEnd, aTmp))
    {
        return Found();
    }

    const SfxPoolItem* pItem;
    // here we explicitly also search in character templates
    sal_uInt16 nWhch = rAttr.Which();
    std::optional<SfxWhichIter> oIter;
    const SfxPoolItem* pTmpItem = nullptr;
    const SfxItemSet* pSet = nullptr;
    if( RES_TXTATR_CHARFMT == nWhch || RES_TXTATR_AUTOFMT == nWhch )
    {
        if( m_bNoColls && RES_TXTATR_CHARFMT == nWhch )
            return Found();
        pTmpItem = nullptr;
        pSet = CharFormat::GetItemSet( rAttr.GetAttr() );
        if ( pSet )
        {
            oIter.emplace( *pSet );
            nWhch = oIter->FirstWhich();
            while( nWhch &&
                SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
                nWhch = oIter->NextWhich();
            if( !nWhch )
                pTmpItem = nullptr;
        }
    }
    else
        pTmpItem = &rAttr.GetAttr();

    while( pTmpItem )
    {
        SfxItemState eState = m_aComapeSet.GetItemState( nWhch, false, &pItem );
        if( SfxItemState::INVALID == eState || SfxItemState::SET == eState )
        {
            sal_uInt16 n;
            SwSrchChrAttr* pCmp;

            // first delete all up to start position that are already invalid
            SwSrchChrAttr* pArrPtr;
            if( m_nFound )
                for( pArrPtr = m_pFindArr, n = 0; n < m_nArrLen;
                    ++n, ++pArrPtr )
                    if( pArrPtr->nWhich && pArrPtr->nEnd <= aTmp.nStt )
                    {
                        pArrPtr->nWhich = 0; // deleted
                        m_nFound--;
                    }

            // delete all up to start position that are already invalid and
            // move all "open" ones (= stick out over start position) from stack
            // into FndSet
            if( m_nStackCount )
                for( pArrPtr = m_pStackArr, n=0; n < m_nArrLen; ++n, ++pArrPtr )
                {
                    if( !pArrPtr->nWhich )
                        continue;

                    if( pArrPtr->nEnd <= aTmp.nStt )
                    {
                        pArrPtr->nWhich = 0; // deleted
                        if( !--m_nStackCount )
                            break;
                    }
                    else if( pArrPtr->nStt <= aTmp.nStt )
                    {
                        pCmp = &m_pFindArr[ n ];
                        if( pCmp->nWhich )
                        {
                            if( pCmp->nEnd < pArrPtr->nEnd ) // extend
                                pCmp->nEnd = pArrPtr->nEnd;
                        }
                        else
                        {
                            *pCmp = *pArrPtr;
                            m_nFound++;
                        }
                        pArrPtr->nWhich = 0;
                        if( !--m_nStackCount )
                            break;
                    }
                }

            bool bContinue = false;

            if( SfxItemState::INVALID == eState  )
            {
                // Will the attribute become valid?
                if( !CmpAttr( m_aComapeSet.GetPool()->GetUserOrPoolDefaultItem( nWhch ),
                    *pTmpItem ))
                {
                    // search attribute and extend if needed
                    pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
                    if( !pCmp->nWhich )
                    {
                        *pCmp = aTmp; // not found, insert
                        m_nFound++;
                    }
                    else if( pCmp->nEnd < aTmp.nEnd ) // extend?
                        pCmp->nEnd = aTmp.nEnd;

                    bContinue = true;
                }
            }
            // Will the attribute become valid?
            else if(  CmpAttr( *pItem, *pTmpItem ) )
            {
                m_pFindArr[ nWhch - m_nArrStart ] = aTmp;
                ++m_nFound;
                bContinue = true;
            }

            // then is has to go on the stack
            if( !bContinue )
            {
                pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
                if (pCmp->nWhich )
                {
                    // exists on stack, only if it is even bigger
                    if( pCmp->nEnd > aTmp.nEnd )
                    {
                        OSL_ENSURE( !m_pStackArr[ nWhch - m_nArrStart ].nWhich,
                                        "slot on stack is still in use" );

                        if( aTmp.nStt <= pCmp->nStt )
                            pCmp->nStt = aTmp.nEnd;
                        else
                            pCmp->nEnd = aTmp.nStt;

                        m_pStackArr[ nWhch - m_nArrStart ] = *pCmp;
                        m_nStackCount++;
                    }
                    pCmp->nWhich = 0;
                    m_nFound--;
                }
            }
        }
        if( oIter )
        {
            assert(pSet && "otherwise no oIter");
            nWhch = oIter->NextWhich();
            while( nWhch &&
                SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
                nWhch = oIter->NextWhich();
            if( !nWhch )
                break;
        }
        else
            break;
    }
    oIter.reset();
    return Found();
}

bool SwAttrCheckArr::SetAttrBwd( const SwTextAttr& rAttr )
{
    SwSrchChrAttr aTmp( rAttr.GetAttr(), rAttr.GetStart(), rAttr.GetAnyEnd() );

    // ignore all attributes not in search range
    if (lcl_IsAttributeIgnorable(m_nNodeStart, m_nNodeEnd, aTmp))
    {
        return Found();
    }

    const SfxPoolItem* pItem;
    // here we explicitly also search in character templates
    sal_uInt16 nWhch = rAttr.Which();
    std::optional<SfxWhichIter> oIter;
    const SfxPoolItem* pTmpItem = nullptr;
    const SfxItemSet* pSet = nullptr;
    if( RES_TXTATR_CHARFMT == nWhch || RES_TXTATR_AUTOFMT == nWhch )
    {
        if( m_bNoColls && RES_TXTATR_CHARFMT == nWhch )
            return Found();

        pSet = CharFormat::GetItemSet( rAttr.GetAttr() );
        if ( pSet )
        {
            oIter.emplace( *pSet );
            nWhch = oIter->FirstWhich();
            while( nWhch &&
                SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
                nWhch = oIter->NextWhich();
            if( !nWhch )
                pTmpItem = nullptr;
        }
    }
    else
        pTmpItem = &rAttr.GetAttr();

    while( pTmpItem )
    {
        SfxItemState eState = m_aComapeSet.GetItemState( nWhch, false, &pItem );
        if( SfxItemState::INVALID == eState || SfxItemState::SET == eState )
        {
            sal_uInt16 n;
            SwSrchChrAttr* pCmp;

            // first delete all up to start position that are already invalid
            SwSrchChrAttr* pArrPtr;
            if( m_nFound )
                for( pArrPtr = m_pFindArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr )
                    if( pArrPtr->nWhich && pArrPtr->nStt >= aTmp.nEnd )
                    {
                        pArrPtr->nWhich = 0; // deleted
                        m_nFound--;
                    }

            // delete all up to start position that are already invalid and
            // move all "open" ones (= stick out over start position) from stack
            // into FndSet
            if( m_nStackCount )
                for( pArrPtr = m_pStackArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr )
                {
                    if( !pArrPtr->nWhich )
                        continue;

                    if( pArrPtr->nStt >= aTmp.nEnd )
                    {
                        pArrPtr->nWhich = 0; // deleted
                        if( !--m_nStackCount )
                            break;
                    }
                    else if( pArrPtr->nEnd >= aTmp.nEnd )
                    {
                        pCmp = &m_pFindArr[ n ];
                        if( pCmp->nWhich )
                        {
                            if( pCmp->nStt > pArrPtr->nStt ) // extend
                                pCmp->nStt = pArrPtr->nStt;
                        }
                        else
                        {
                            *pCmp = *pArrPtr;
                            m_nFound++;
                        }
                        pArrPtr->nWhich = 0;
                        if( !--m_nStackCount )
                            break;
                    }
                }

            bool bContinue = false;
            if( SfxItemState::INVALID == eState  )
            {
                // Will the attribute become valid?
                if( !CmpAttr( m_aComapeSet.GetPool()->GetUserOrPoolDefaultItem( nWhch ),
                    *pTmpItem ) )
                {
                    // search attribute and extend if needed
                    pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
                    if( !pCmp->nWhich )
                    {
                        *pCmp = aTmp; // not found, insert
                        m_nFound++;
                    }
                    else if( pCmp->nStt > aTmp.nStt ) // extend?
                        pCmp->nStt = aTmp.nStt;

                    bContinue = true;
                }
            }
            // Will the attribute become valid?
            else if( CmpAttr( *pItem, *pTmpItem ))
            {
                m_pFindArr[ nWhch - m_nArrStart ] = aTmp;
                ++m_nFound;
                bContinue = true;
            }

            // then is has to go on the stack
            if( !bContinue )
            {
                pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
                if( pCmp->nWhich )
                {
                    // exists on stack, only if it is even bigger
                    if( pCmp->nStt < aTmp.nStt )
                    {
                        OSL_ENSURE( !m_pStackArr[ nWhch - m_nArrStart ].nWhich,
                                "slot on stack is still in use" );

                        if( aTmp.nEnd <= pCmp->nEnd )
                            pCmp->nEnd = aTmp.nStt;
                        else
                            pCmp->nStt = aTmp.nEnd;

                        m_pStackArr[ nWhch - m_nArrStart ] = *pCmp;
                        m_nStackCount++;
                    }
                    pCmp->nWhich = 0;
                    m_nFound--;
                }
            }
        }
        if( oIter )
        {
            assert(pSet && "otherwise no oIter");
            nWhch = oIter->NextWhich();
            while( nWhch &&
                SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
                nWhch = oIter->NextWhich();
            if( !nWhch )
                break;
        }
        else
            break;
    }
    oIter.reset();
    return Found();
}

sal_Int32 SwAttrCheckArr::Start() const
{
    sal_Int32 nStart = m_nNodeStart;
    SwSrchChrAttr* pArrPtr = m_pFindArr;
    for( sal_uInt16 n = 0; n < m_nArrLen; ++n, ++pArrPtr )
        if( pArrPtr->nWhich && pArrPtr->nStt > nStart )
            nStart = pArrPtr->nStt;

    return nStart;
}

sal_Int32 SwAttrCheckArr::End() const
{
    SwSrchChrAttr* pArrPtr = m_pFindArr;
    sal_Int32 nEnd = m_nNodeEnd;
    for( sal_uInt16 n = 0; n < m_nArrLen; ++n, ++pArrPtr )
        if( pArrPtr->nWhich && pArrPtr->nEnd < nEnd )
            nEnd = pArrPtr->nEnd;

    return nEnd;
}

bool SwAttrCheckArr::CheckStack()
{
    if( !m_nStackCount )
        return false;

    sal_uInt16 n;
    const sal_Int32 nSttPos = Start();
    const sal_Int32 nEndPos = End();
    SwSrchChrAttr* pArrPtr;
    for( pArrPtr = m_pStackArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr )
    {
        if( !pArrPtr->nWhich )
            continue;

        if( m_bForward ? pArrPtr->nEnd <= nSttPos : pArrPtr->nStt >= nEndPos )
        {
            pArrPtr->nWhich = 0; // deleted
            if( !--m_nStackCount )
                return m_nFound == m_aComapeSet.Count();
        }
        else if( m_bForward ? pArrPtr->nStt < nEndPos : pArrPtr->nEnd > nSttPos )
        {
            // move all "open" ones (= stick out over start position) into FndSet
            OSL_ENSURE( !m_pFindArr[ n ].nWhich, "slot in array is already in use" );
            m_pFindArr[ n ] = *pArrPtr;
            pArrPtr->nWhich = 0;
            m_nFound++;
            if( !--m_nStackCount )
                return m_nFound == m_aComapeSet.Count();
        }
    }
    return m_nFound == m_aComapeSet.Count();
}

static bool lcl_SearchForward( const SwTextNode& rTextNd, SwAttrCheckArr& rCmpArr,
                            SwPaM& rPam )
{
    sal_Int32 nEndPos;
    rCmpArr.SetNewSet( rTextNd, rPam );
    if( !rTextNd.HasHints() )
    {
        if( !rCmpArr.Found() )
            return false;
        nEndPos = rCmpArr.GetNdEnd();
        lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, true );
        return true;
    }

    const SwpHints& rHtArr = rTextNd.GetSwpHints();
    const SwTextAttr* pAttr;
    size_t nPos = 0;

    // if everything is already there then check with which it will be ended
    if( rCmpArr.Found() )
    {
        for( ; nPos < rHtArr.Count(); ++nPos )
        {
            pAttr = rHtArr.Get( nPos );
            if( !rCmpArr.SetAttrFwd( *pAttr ) )
            {
                if( rCmpArr.GetNdStt() < pAttr->GetStart() )
                {
                    // found end
                    auto nTmpStart = pAttr->GetStart();
                    lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(),
                                &nTmpStart, true );
                    return true;
                }
                // continue search
                break;
            }
        }

        if( nPos == rHtArr.Count() && rCmpArr.Found() )
        {
            // found
            nEndPos = rCmpArr.GetNdEnd();
            lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, true );
            return true;
        }
    }

    sal_Int32 nSttPos;
    for( ; nPos < rHtArr.Count(); ++nPos )
    {
        pAttr = rHtArr.Get( nPos );
        if( rCmpArr.SetAttrFwd( *pAttr ) )
        {
            // Do multiple start at that position? Do also check those:
            nSttPos = pAttr->GetStart();
            while( ++nPos < rHtArr.Count() )
            {
                pAttr = rHtArr.Get( nPos );
                if( nSttPos != pAttr->GetStart() || !rCmpArr.SetAttrFwd( *pAttr ) )
                    break;
            }

            if( !rCmpArr.Found() )
                continue;

            // then we have our search area
            nSttPos = rCmpArr.Start();
            nEndPos = rCmpArr.End();
            if( nSttPos > nEndPos )
                return false;

            lcl_SetAttrPam( rPam, nSttPos, &nEndPos, true );
            return true;
        }
    }

    if( !rCmpArr.CheckStack() )
        return false;
    nSttPos = rCmpArr.Start();
    nEndPos = rCmpArr.End();
    if( nSttPos > nEndPos )
        return false;

    lcl_SetAttrPam( rPam, nSttPos, &nEndPos, true );
    return true;
}

static bool lcl_SearchBackward( const SwTextNode& rTextNd, SwAttrCheckArr& rCmpArr,
                            SwPaM& rPam )
{
    sal_Int32 nEndPos;
    rCmpArr.SetNewSet( rTextNd, rPam );
    if( !rTextNd.HasHints() )
    {
        if( !rCmpArr.Found() )
            return false;
        nEndPos = rCmpArr.GetNdEnd();
        lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, false );
        return true;
    }

    const SwpHints& rHtArr = rTextNd.GetSwpHints();
    const SwTextAttr* pAttr;
    size_t nPos = rHtArr.Count();
    sal_Int32 nSttPos;

    // if everything is already there then check with which it will be ended
    if( rCmpArr.Found() )
    {
        while( nPos )
        {
            pAttr = rHtArr.GetSortedByEnd( --nPos );
            if( !rCmpArr.SetAttrBwd( *pAttr ) )
            {
                nSttPos = pAttr->GetAnyEnd();
                if( nSttPos < rCmpArr.GetNdEnd() )
                {
                    // found end
                    nEndPos = rCmpArr.GetNdEnd();
                    lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false );
                    return true;
                }

                // continue search
                break;
            }
        }

        if( !nPos && rCmpArr.Found() )
        {
            // found
            nEndPos = rCmpArr.GetNdEnd();
            lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, false );
            return true;
        }
    }

    while( nPos )
    {
        pAttr = rHtArr.GetSortedByEnd( --nPos );
        if( rCmpArr.SetAttrBwd( *pAttr ) )
        {
            // Do multiple start at that position? Do also check those:
            if( nPos )
            {
                nEndPos = pAttr->GetAnyEnd();
                while( --nPos )
                {
                    pAttr = rHtArr.GetSortedByEnd( nPos );
                    if( nEndPos != pAttr->GetAnyEnd() || !rCmpArr.SetAttrBwd( *pAttr ) )
                        break;
                }
            }
            if( !rCmpArr.Found() )
                continue;

            // then we have our search area
            nSttPos = rCmpArr.Start();
            nEndPos = rCmpArr.End();
            if( nSttPos > nEndPos )
                return false;

            lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false );
            return true;
        }
    }

    if( !rCmpArr.CheckStack() )
        return false;
    nSttPos = rCmpArr.Start();
    nEndPos = rCmpArr.End();
    if( nSttPos > nEndPos )
        return false;

    lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false );
    return true;
}

static bool lcl_Search( const SwContentNode& rCNd, const SfxItemSet& rCmpSet, bool bNoColls )
{
    // search only hard attribution?
    if( bNoColls && !rCNd.HasSwAttrSet() )
        return false;

    const SfxItemSet& rNdSet = rCNd.GetSwAttrSet();
    SfxItemIter aIter( rCmpSet );
    const SfxPoolItem* pItem = aIter.GetCurItem();
    const SfxPoolItem* pNdItem;
    sal_uInt16 nWhich;

    do
    {
        if( IsInvalidItem( pItem ))
        {
            nWhich = aIter.GetCurWhich();
            if( SfxItemState::SET != rNdSet.GetItemState( nWhich, !bNoColls, &pNdItem )
                || CmpAttr( *pNdItem, rNdSet.GetPool()->GetUserOrPoolDefaultItem( nWhich ) ))
                return false;
        }
        else
        {
            nWhich = pItem->Which();

            if( !CmpAttr( rNdSet.Get( nWhich, !bNoColls ), *pItem ))
                return false;
        }

        pItem = aIter.NextItem();
    } while (pItem);
    return true; // found
}

namespace sw {

bool FindAttrImpl(SwPaM & rSearchPam,
        const SfxPoolItem& rAttr, SwMoveFnCollection const & fnMove,
        const SwPaM & rRegion, bool bInReadOnly,
        SwRootFrame const*const pLayout)
{
    // determine which attribute is searched:
    const sal_uInt16 nWhich = rAttr.Which();
    bool bCharAttr = isCHRATR(nWhich) || isTXTATR(nWhich);
    assert(isTXTATR(nWhich)); // sw_redlinehide: only works for non-formatting hints such as needed in UpdateFields; use FindAttrsImpl for others

    std::optional<SwPaM> oPam;
    sw::MakeRegion(fnMove, rRegion, oPam);

    bool bFound = false;
    bool bFirst = true;
    const bool bSrchForward = &fnMove == &fnMoveForward;
    SwContentNode * pNode;

    // if at beginning/end then move it out of the node
    if( bSrchForward
        ? oPam->GetPoint()->GetContentIndex() == oPam->GetPointContentNode()->Len()
        : !oPam->GetPoint()->GetContentIndex() )
    {
        if( !(*fnMove.fnPos)( oPam->GetPoint(), false ))
        {
            return false;
        }
        SwContentNode *pNd = oPam->GetPointContentNode();
        oPam->GetPoint()->SetContent( bSrchForward ? 0 : pNd->Len() );
    }

    while (nullptr != (pNode = ::GetNode(*oPam, bFirst, fnMove, bInReadOnly, pLayout)))
    {
        if( bCharAttr )
        {
            if( !pNode->IsTextNode() ) // CharAttr are only in text nodes
                continue;

            SwTextFrame const*const pFrame(pLayout
                ? static_cast<SwTextFrame const*>(pNode->getLayoutFrame(pLayout))
                : nullptr);
            if (pFrame)
            {
                SwTextNode const* pAttrNode(nullptr);
                SwTextAttr const* pAttr(nullptr);
                if (bSrchForward)
                {
                    sw::MergedAttrIter iter(*pFrame);
                    do
                    {
                        pAttr = iter.NextAttr(&pAttrNode);
                    }
                    while (pAttr
                        && (pAttrNode->GetIndex() < oPam->GetPoint()->GetNodeIndex()
                            || (pAttrNode->GetIndex() == oPam->GetPoint()->GetNodeIndex()
                                && pAttr->GetStart() < oPam->GetPoint()->GetContentIndex())
                            || pAttr->Which() != nWhich));
                }
                else
                {
                    sw::MergedAttrIterReverse iter(*pFrame);
                    do
                    {
                        pAttr = iter.PrevAttr(&pAttrNode);
                    }
                    while (pAttr
                        && (oPam->GetPoint()->GetNodeIndex() < pAttrNode->GetIndex()
                            || (oPam->GetPoint()->GetNodeIndex() == pAttrNode->GetIndex()
                                && oPam->GetPoint()->GetContentIndex() <= pAttr->GetStart())
                            || pAttr->Which() != nWhich));
                }
                if (pAttr)
                {
                    assert(pAttrNode);
                    oPam->GetPoint()->Assign(*pAttrNode);
                    lcl_SetAttrPam(*oPam, pAttr->GetStart(), pAttr->End(), bSrchForward);
                    bFound = true;
                    break;
                }
            }
            else if (!pLayout && pNode->GetTextNode()->HasHints() &&
                lcl_SearchAttr(*pNode->GetTextNode(), *oPam, rAttr, fnMove))
            {
                bFound = true;
            }
            if (bFound)
            {
                // set to the values of the attribute
                rSearchPam.SetMark();
                *rSearchPam.GetPoint() = *oPam->GetPoint();
                *rSearchPam.GetMark() = *oPam->GetMark();
                break;
            }
            else if (isTXTATR(nWhich))
                continue;
        }

#if 0
        // no hard attribution, so check if node was asked for this attr before
        if( !pNode->HasSwAttrSet() )
        {
            SwFormat* pTmpFormat = pNode->GetFormatColl();
            if( !aFormatArr.insert( pTmpFormat ).second )
                continue; // collection was requested earlier
        }

        if( SfxItemState::SET == pNode->GetSwAttrSet().GetItemState( nWhich,
                true, &pItem ))
        {
            // FORWARD:  SPoint at the end, GetMark at the beginning of the node
            // BACKWARD: SPoint at the beginning, GetMark at the end of the node
            // always: incl. start and incl. end
            *rSearchPam.GetPoint() = *pPam->GetPoint();
            rSearchPam.SetMark();
            rSearchPam.GetPoint()->SetContent(pNode->Len());
            bFound = true;
            break;
        }
#endif
    }

    // if backward search, switch point and mark
    if( bFound && !bSrchForward )
        rSearchPam.Exchange();

    return bFound;
}

} // namespace sw

typedef bool (*FnSearchAttr)( const SwTextNode&, SwAttrCheckArr&, SwPaM& );

static bool FindAttrsImpl(SwPaM & rSearchPam,
        const SfxItemSet& rSet, bool bNoColls, SwMoveFnCollection const & fnMove,
        const SwPaM & rRegion, bool bInReadOnly, bool bMoveFirst,
        SwRootFrame const*const pLayout)
{
    std::optional<SwPaM> oPam;
    sw::MakeRegion(fnMove, rRegion, oPam);

    bool bFound = false;
    bool bFirst = true;
    const bool bSrchForward = &fnMove == &fnMoveForward;
    SwContentNode * pNode;
    o3tl::sorted_vector<SwFormat*> aFormatArr;

    // check which text/char attributes are searched
    SwAttrCheckArr aCmpArr( rSet, bSrchForward, bNoColls );
    SfxItemSetFixed<RES_PARATR_BEGIN, RES_GRFATR_END-1> aOtherSet( rSearchPam.GetDoc().GetAttrPool() );
    aOtherSet.Put( rSet, false );   // got all invalid items

    FnSearchAttr fnSearch = bSrchForward
                                ? (&::lcl_SearchForward)
                                : (&::lcl_SearchBackward);

    // if at beginning/end then move it out of the node
    if( bMoveFirst &&
        ( bSrchForward
        ? oPam->GetPoint()->GetContentIndex() == oPam->GetPointContentNode()->Len()
        : !oPam->GetPoint()->GetContentIndex() ) )
    {
        if( !(*fnMove.fnPos)( oPam->GetPoint(), false ))
        {
            return false;
        }
        SwContentNode *pNd = oPam->GetPointContentNode();
        oPam->GetPoint()->SetContent( bSrchForward ? 0 : pNd->Len() );
    }

    while (nullptr != (pNode = ::GetNode(*oPam, bFirst, fnMove, bInReadOnly, pLayout)))
    {
        SwTextFrame const*const pFrame(pLayout && pNode->IsTextNode()
            ? static_cast<SwTextFrame const*>(pNode->getLayoutFrame(pLayout))
            : nullptr);
        assert(!pLayout || !pNode->IsTextNode() || pFrame);
        // sw_redlinehide: it's apparently not possible to find break items
        // with the UI, so checking one node is enough
        SwContentNode const& rPropsNode(*(pFrame
            ? pFrame->GetTextNodeForParaProps()
            : pNode));

        if( aCmpArr.Count() )
        {
            if( !pNode->IsTextNode() ) // CharAttr are only in text nodes
                continue;

            if (aOtherSet.Count() &&
                !lcl_Search(rPropsNode, aOtherSet, bNoColls))
            {
                continue;
            }
            sw::MergedPara const*const pMergedPara(pFrame ? pFrame->GetMergedPara() : nullptr);
            if (pMergedPara)
            {
                SwPosition const& rStart(*oPam->Start());
                SwPosition const& rEnd(*oPam->End());
                // no extents? fall back to searching index 0 of propsnode
                // to find its node items
                if (pMergedPara->extents.empty())
                {
                    if (rStart.GetNodeIndex() <= rPropsNode.GetIndex()
                        && rPropsNode.GetIndex() <= rEnd.GetNodeIndex())
                    {
                        SwPaM tmp(rPropsNode, 0, rPropsNode, 0);
                        bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, tmp);
                        if (bFound)
                        {
                            *oPam = tmp;
                        }
                    }
                }
                else
                {
                    // iterate the extents, and intersect with input pPam:
                    // the found ranges should never include delete redlines
                    // so that subsequent Replace will not affect them
                    for (size_t i = 0; i < pMergedPara->extents.size(); ++i)
                    {
                        auto const rExtent(pMergedPara->extents[bSrchForward
                                ? i
                                : pMergedPara->extents.size() - i - 1]);
                        if (rExtent.pNode->GetIndex() < rStart.GetNodeIndex()
                            || rEnd.GetNodeIndex() < rExtent.pNode->GetIndex())
                        {
                            continue;
                        }
                        sal_Int32 const nStart(rExtent.pNode == &rStart.GetNode()
                                ? rStart.GetContentIndex()
                                : 0);
                        if (rExtent.nEnd <= nStart)
                        {
                            continue;
                        }
                        sal_Int32 const nEnd(rExtent.pNode == &rEnd.GetNode()
                                ? rEnd.GetContentIndex()
                                : rExtent.pNode->Len());
                        if (nEnd < rExtent.nStart
                            || (nStart != nEnd && nEnd == rExtent.nStart))
                        {
                            continue;
                        }
                        SwPaM tmp(*rExtent.pNode, std::max(nStart, rExtent.nStart),
                            *rExtent.pNode, std::min(nEnd, rExtent.nEnd));
                        tmp.Normalize(bSrchForward);
                        bFound = (*fnSearch)(*rExtent.pNode, aCmpArr, tmp);
                        if (bFound)
                        {
                            *oPam = tmp;
                            break;
                        }
                    }
                }
            }
            else
            {
                bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, *oPam);
            }
            if (bFound)
            {
                // set to the values of the attribute
                rSearchPam.SetMark();
                *rSearchPam.GetPoint() = *oPam->GetPoint();
                *rSearchPam.GetMark() = *oPam->GetMark();
                break;
            }
            continue; // text attribute
        }

        if( !aOtherSet.Count() )
            continue;

        // no hard attribution, so check if node was asked for this attr before
        // (as an optimisation)
        if (!rPropsNode.HasSwAttrSet())
        {
            SwFormat* pTmpFormat = rPropsNode.GetFormatColl();
            if( !aFormatArr.insert( pTmpFormat ).second )
                continue; // collection was requested earlier
        }

        if (lcl_Search(rPropsNode, aOtherSet, bNoColls))
        {
            // FORWARD:  SPoint at the end, GetMark at the beginning of the node
            // BACKWARD: SPoint at the beginning, GetMark at the end of the node
            if (pFrame)
            {
                *rSearchPam.GetPoint() = *oPam->GetPoint();
                rSearchPam.SetMark();
                *rSearchPam.GetMark() = pFrame->MapViewToModelPos(
                    TextFrameIndex(bSrchForward ? pFrame->GetText().getLength() : 0));
            }
            else
            {
                *rSearchPam.GetPoint() = *oPam->GetPoint();
                rSearchPam.SetMark();
                if (bSrchForward)
                {
                    rSearchPam.GetPoint()->SetContent(pNode->Len());
                }
                else
                {
                    rSearchPam.GetPoint()->SetContent(0);
                }
            }
            bFound = true;
            break;
        }
    }

    // in search direction, mark precedes point, because the next iteration
    // starts at point
    if (bFound)
    {
        rSearchPam.Normalize(!bSrchForward);
    }

    return bFound;
}

namespace {

/// parameters for search for attributes
struct SwFindParaAttr : public SwFindParas
{
    bool m_bNoCollection;
    const SfxItemSet *pSet, *pReplSet;
    const i18nutil::SearchOptions2 *pSearchOpt;
    SwCursor& m_rCursor;
    SwRootFrame const* m_pLayout;
    std::unique_ptr<utl::TextSearch> pSText;

    SwFindParaAttr( const SfxItemSet& rSet, bool bNoCollection,
                    const i18nutil::SearchOptions2* pOpt, const SfxItemSet* pRSet,
                    SwCursor& rCursor, SwRootFrame const*const pLayout)
        : m_bNoCollection(bNoCollection)
        , pSet( &rSet )
        , pReplSet( pRSet )
        , pSearchOpt( pOpt )
        , m_rCursor(rCursor)
        , m_pLayout(pLayout)
    {}

    virtual ~SwFindParaAttr()   {}

    virtual int DoFind(SwPaM &, SwMoveFnCollection const &, const SwPaM &, bool bInReadOnly,
                       std::unique_ptr<SvxSearchItem>& xSearchItem) override;
    virtual bool IsReplaceMode() const override;
};

}

int SwFindParaAttr::DoFind(SwPaM & rCursor, SwMoveFnCollection const & fnMove,
        const SwPaM & rRegion, bool bInReadOnly,
        std::unique_ptr<SvxSearchItem>& xSearchItem)
{
    // replace string (only if text given and search is not parameterized)?
    bool bReplaceText = pSearchOpt && ( !pSearchOpt->replaceString.isEmpty() ||
                                    !pSet->Count() );
    bool bReplaceAttr = pReplSet && pReplSet->Count();
    bool bMoveFirst = !bReplaceAttr;
    if( bInReadOnly && (bReplaceAttr || bReplaceText ))
        bInReadOnly = false;

    // We search for attributes, should we search for text as well?
    {
        SwPaM aRegion( *rRegion.GetMark(), *rRegion.GetPoint() );
        SwPaM* pTextRegion = &aRegion;
        SwPaM aSrchPam( *rCursor.GetPoint() );

        while( true )
        {
            if( pSet->Count() ) // any attributes?
            {
                // first attributes
                if (!FindAttrsImpl(aSrchPam, *pSet, m_bNoCollection, fnMove, aRegion, bInReadOnly, bMoveFirst, m_pLayout))
                    return FIND_NOT_FOUND;
                bMoveFirst = true;

                if( !pSearchOpt )
                    break; // ok, only attributes, so found

                pTextRegion = &aSrchPam;
            }
            else if( !pSearchOpt )
                return FIND_NOT_FOUND;

            // then search in text of it
            if( !pSText )
            {
                i18nutil::SearchOptions2 aTmp( *pSearchOpt );

                // search in selection
                aTmp.searchFlag |= (SearchFlags::REG_NOT_BEGINOFLINE |
                                    SearchFlags::REG_NOT_ENDOFLINE);

                aTmp.Locale = SvtSysLocale().GetLanguageTag().getLocale();

                pSText.reset( new utl::TextSearch( aTmp ) );
            }

            // TODO: searching for attributes in Outliner text?!

            // continue search in correct section (pTextRegion)
            if (sw::FindTextImpl(aSrchPam, *pSearchOpt, false/*bSearchInNotes*/, *pSText, fnMove, *pTextRegion, bInReadOnly, m_pLayout, xSearchItem) &&
                *aSrchPam.GetMark() != *aSrchPam.GetPoint() )
                break; // found
            else if( !pSet->Count() )
                return FIND_NOT_FOUND; // only text and nothing found

            *aRegion.GetMark() = *aSrchPam.GetPoint();
        }

        *rCursor.GetPoint() = *aSrchPam.GetPoint();
        rCursor.SetMark();
        *rCursor.GetMark() = *aSrchPam.GetMark();
    }

    if( bReplaceText )
    {
        const bool bRegExp(
                SearchAlgorithms2::REGEXP == pSearchOpt->AlgorithmType2);
        SwPosition& rSttCntPos = *rCursor.Start();
        const sal_Int32 nSttCnt = rSttCntPos.GetContentIndex();

        // add to shell-cursor-ring so that the regions will be moved eventually
        SwPaM* pPrevRing(nullptr);
        if( bRegExp )
        {
            pPrevRing = const_cast<SwPaM &>(rRegion).GetPrev();
            const_cast<SwPaM &>(rRegion).GetRingContainer().merge( m_rCursor.GetRingContainer() );
        }

        std::optional<OUString> xRepl;
        if (bRegExp)
            xRepl = sw::ReplaceBackReferences(*pSearchOpt, &rCursor, m_pLayout);
        sw::ReplaceImpl(rCursor,
                xRepl ? *xRepl : pSearchOpt->replaceString, bRegExp,
                m_rCursor.GetDoc(), m_pLayout);

        m_rCursor.SaveTableBoxContent( rCursor.GetPoint() );

        if( bRegExp )
        {
            // and remove region again
            SwPaM* p;
            SwPaM* pNext = const_cast<SwPaM*>(&rRegion);
            do {
                p = pNext;
                pNext = p->GetNext();
                p->MoveTo(const_cast<SwPaM*>(&rRegion));
            } while( p != pPrevRing );
        }
        rSttCntPos.SetContent(nSttCnt);
    }

    if( bReplaceAttr )
    {
        // is the selection still existent?
        // all searched attributes are reset to default if
        // they are not in ReplaceSet
        if( !pSet->Count() )
        {
            rCursor.GetDoc().getIDocumentContentOperations().InsertItemSet(
                    rCursor, *pReplSet, SetAttrMode::DEFAULT, m_pLayout);
        }
        else
        {
            SfxItemPool* pPool = pReplSet->GetPool();
            SfxItemSet aSet( *pPool, pReplSet->GetRanges() );

            SfxItemIter aIter( *pSet );
            const SfxPoolItem* pItem = aIter.GetCurItem();
            do
            {
                // reset all that are not set with pool defaults
                if( !IsInvalidItem( pItem ) && SfxItemState::SET !=
                    pReplSet->GetItemState( pItem->Which(), false ))
                    aSet.Put( pPool->GetUserOrPoolDefaultItem( pItem->Which() ));

                pItem = aIter.NextItem();
            } while (pItem);
            aSet.Put( *pReplSet );
            rCursor.GetDoc().getIDocumentContentOperations().InsertItemSet(
                    rCursor, aSet, SetAttrMode::DEFAULT, m_pLayout);
        }

        return FIND_NO_RING;
    }
    else
        return FIND_FOUND;
}

bool SwFindParaAttr::IsReplaceMode() const
{
    return ( pSearchOpt && !pSearchOpt->replaceString.isEmpty() ) ||
           ( pReplSet && pReplSet->Count() );
}

/// search for attributes
sal_Int32 SwCursor::FindAttrs( const SfxItemSet& rSet, bool bNoCollections,
                          SwDocPositions nStart, SwDocPositions nEnd,
                          bool& bCancel, FindRanges eFndRngs,
                          const i18nutil::SearchOptions2* pSearchOpt,
                          const SfxItemSet* pReplSet,
                          SwRootFrame const*const pLayout)
{
    // switch off OLE-notifications
    SwDoc& rDoc = GetDoc();
    Link<bool,void> aLnk( rDoc.GetOle2Link() );
    rDoc.SetOle2Link( Link<bool,void>() );

    bool bReplace = ( pSearchOpt && ( !pSearchOpt->replaceString.isEmpty() ||
                                    !rSet.Count() ) ) ||
                    (pReplSet && pReplSet->Count());
    bool const bStartUndo = rDoc.GetIDocumentUndoRedo().DoesUndo() && bReplace;
    if (bStartUndo)
    {
        rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE, nullptr );
    }

    SwFindParaAttr aSwFindParaAttr( rSet, bNoCollections, pSearchOpt,
                                    pReplSet, *this, pLayout );

    sal_Int32 nRet = FindAll( aSwFindParaAttr, nStart, nEnd, eFndRngs, bCancel );
    rDoc.SetOle2Link( aLnk );
    if( nRet && bReplace )
        rDoc.getIDocumentState().SetModified();

    if (bStartUndo)
    {
        rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE, nullptr );
    }

    return nRet;
}

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