/* -*- 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 <memory>
 
#include <com/sun/star/util/SearchFlags.hpp>
#include <com/sun/star/util/SearchResult.hpp>
#include <comphelper/lok.hxx>
#include <o3tl/safeint.hxx>
#include <rtl/ustrbuf.hxx>
#include <svx/svdview.hxx>
#include <svl/srchitem.hxx>
#include <sfx2/sfxsids.hrc>
#include <editeng/outliner.hxx>
#include <osl/diagnose.h>
 
#include <wrtsh.hxx>
#include <txatritr.hxx>
#include <fldbas.hxx>
#include <fmtfld.hxx>
#include <txtfld.hxx>
#include <txtfrm.hxx>
#include <rootfrm.hxx>
#include <swcrsr.hxx>
#include <redline.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentState.hxx>
#include <IDocumentDrawModelAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <dcontact.hxx>
#include <pamtyp.hxx>
#include <ndtxt.hxx>
#include <swundo.hxx>
#include <UndoInsert.hxx>
#include <breakit.hxx>
#include <docsh.hxx>
#include <PostItMgr.hxx>
#include <view.hxx>
 
using namespace ::com::sun::star;
using namespace util;
 
namespace {
 
/// because the Find may be called on the View or the Model, we need an index
/// afflicted by multiple personality disorder
struct AmbiguousIndex
{
private:
    sal_Int32 m_value;
 
#ifndef NDEBUG
    enum class tags : char { Any, Frame, Model };
    tags m_tag;
#endif
 
public:
    AmbiguousIndex() : m_value(-1)
#ifndef NDEBUG
           , m_tag(tags::Any)
#endif
    {}
    explicit AmbiguousIndex(sal_Int32 const value
#ifndef NDEBUG
            , tags const tag
#endif
        )   : m_value(value)
#ifndef NDEBUG
            , m_tag(tag)
#endif
    {}
 
    sal_Int32 & GetAnyIndex() { return m_value; } ///< for arithmetic
    sal_Int32 const& GetAnyIndex() const { return m_value; } ///< for arithmetic
    TextFrameIndex GetFrameIndex() const
    {
        assert(m_tag != tags::Model);
        return TextFrameIndex(m_value);
    }
    sal_Int32 GetModelIndex() const
    {
        assert(m_tag != tags::Frame);
        return m_value;
    }
    void SetFrameIndex(TextFrameIndex const value)
    {
#ifndef NDEBUG
        m_tag = tags::Frame;
#endif
        m_value = sal_Int32(value);
    }
    void SetModelIndex(sal_Int32 const value)
    {
#ifndef NDEBUG
        m_tag = tags::Model;
#endif
        m_value = value;
    }
 
    bool operator ==(AmbiguousIndex const& rOther) const
    {
        assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag);
        return m_value == rOther.m_value;
    }
    bool operator <=(AmbiguousIndex const& rOther) const
    {
        assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag);
        return m_value <= rOther.m_value;
    }
    bool operator < (AmbiguousIndex const& rOther) const
    {
        assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag);
        return m_value <  rOther.m_value;
    }
    AmbiguousIndex operator - (AmbiguousIndex const& rOther) const
    {
        assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag);
        return AmbiguousIndex(m_value - rOther.m_value
#ifndef NDEBUG
                , std::max(m_tag, rOther.m_tag)
#endif
            );
    }
};
 
class MaybeMergedIter
{
    std::optional<sw::MergedAttrIter> m_oMergedIter;
    SwTextNode const*const m_pNode;
    size_t m_HintIndex;
 
public:
    MaybeMergedIter(SwTextFrame const*const pFrame, SwTextNode const*const pNode)
        : m_pNode(pNode)
        , m_HintIndex(0)
    {
        if (pFrame)
        {
            m_oMergedIter.emplace(*pFrame);
        }
    }
 
    SwTextAttr const* NextAttr(SwTextNode const*& rpNode)
    {
        if (m_oMergedIter)
        {
            return m_oMergedIter->NextAttr(&rpNode);
        }
        if (SwpHints const*const pHints = m_pNode->GetpSwpHints())
        {
            if (m_HintIndex < pHints->Count())
            {
                rpNode = m_pNode;
                return pHints->Get(m_HintIndex++);
            }
        }
        return nullptr;
    }
};
 
}
 
static OUString
lcl_CleanStr(const SwTextNode& rNd,
             SwTextFrame const*const pFrame,
             SwRootFrame const*const pLayout,
             AmbiguousIndex const nStart, AmbiguousIndex & rEnd,
             std::vector<AmbiguousIndex> &rArr,
             bool const bRemoveSoftHyphen, bool const bRemoveCommentAnchors)
{
    OUStringBuffer buf(pLayout ? pFrame->GetText() : rNd.GetText());
    rArr.clear();
 
    MaybeMergedIter iter(pLayout ? pFrame : nullptr, pLayout ? nullptr : &rNd);
 
    AmbiguousIndex nSoftHyphen = nStart;
    AmbiguousIndex nHintStart;
    bool bNewHint       = true;
    bool bNewSoftHyphen = true;
    const AmbiguousIndex nEnd = rEnd;
    std::vector<AmbiguousIndex> aReplaced;
    SwTextNode const* pNextHintNode(nullptr);
    SwTextAttr const* pNextHint(iter.NextAttr(pNextHintNode));
 
    do
    {
        if ( bNewHint )
        {
            if (pLayout)
            {
                nHintStart.SetFrameIndex(pNextHint
                        ? pFrame->MapModelToView(pNextHintNode, pNextHint->GetStart())
                        : TextFrameIndex(-1));
            }
            else
            {
                nHintStart.SetModelIndex(pNextHint ? pNextHint->GetStart() : -1);
            }
        }
 
        if ( bNewSoftHyphen )
        {
            if (pLayout)
            {
                nSoftHyphen.SetFrameIndex(TextFrameIndex(bRemoveSoftHyphen
                    ? pFrame->GetText().indexOf(CHAR_SOFTHYPHEN, nSoftHyphen.GetAnyIndex())
                    : -1));
            }
            else
            {
                nSoftHyphen.SetModelIndex(bRemoveSoftHyphen
                    ? rNd.GetText().indexOf(CHAR_SOFTHYPHEN, nSoftHyphen.GetAnyIndex())
                    : -1);
            }
        }
 
        bNewHint       = false;
        bNewSoftHyphen = false;
        AmbiguousIndex nStt;
 
        // Check if next stop is a hint.
        if (0 <= nHintStart.GetAnyIndex()
            && (-1 == nSoftHyphen.GetAnyIndex() || nHintStart < nSoftHyphen)
            && nHintStart < nEnd )
        {
            nStt = nHintStart;
            bNewHint = true;
        }
        // Check if next stop is a soft hyphen.
        else if (   -1 != nSoftHyphen.GetAnyIndex()
                 && (-1 == nHintStart.GetAnyIndex() || nSoftHyphen < nHintStart)
                 && nSoftHyphen < nEnd)
        {
            nStt = nSoftHyphen;
            bNewSoftHyphen = true;
        }
        // If nSoftHyphen == nHintStart, the current hint *must* be a hint with an end.
        else if (-1 != nSoftHyphen.GetAnyIndex() && nSoftHyphen == nHintStart)
        {
            nStt = nSoftHyphen;
            bNewHint = true;
            bNewSoftHyphen = true;
        }
        else
            break;
 
        AmbiguousIndex nCurrent(nStt);
        nCurrent.GetAnyIndex() -= rArr.size();
 
        if ( bNewHint )
        {
            if (pNextHint && pNextHint->HasDummyChar() && (nStart <= nStt))
            {
                switch (pNextHint->Which())
                {
                case RES_TXTATR_FLYCNT:
                case RES_TXTATR_FIELD:
                case RES_TXTATR_REFMARK:
                case RES_TXTATR_TOXMARK:
                case RES_TXTATR_META:
                case RES_TXTATR_METAFIELD:
                    {
                        // (1998) they are desired as separators and
                        // belong not any longer to a word.
                        // they should also be ignored at a
                        // beginning/end of a sentence if blank. Those are
                        // simply removed if first. If at the end, we keep the
                        // replacement and remove afterwards all at a string's
                        // end (might be normal 0x7f).
                        const bool bEmpty = pNextHint->Which() != RES_TXTATR_FIELD
                            || (static_txtattr_cast<SwTextField const*>(pNextHint)->GetFormatField().GetField()->ExpandField(true, pLayout).isEmpty());
                        if ( bEmpty && nStart == nCurrent )
                        {
                            rArr.push_back( nCurrent );
                            if (rEnd.GetAnyIndex() > nCurrent.GetAnyIndex())
                            {
                                --rEnd.GetAnyIndex();
                            }
                            buf.remove(nCurrent.GetAnyIndex(), 1);
                        }
                        else
                        {
                            if ( bEmpty )
                                aReplaced.push_back( nCurrent );
                            buf[nCurrent.GetAnyIndex()] = '\x7f';
                        }
                    }
                    break;
                case RES_TXTATR_ANNOTATION:
                    {
                        if( bRemoveCommentAnchors )
                        {
                            rArr.push_back( nCurrent );
                            if (rEnd.GetAnyIndex() > nCurrent.GetAnyIndex())
                            {
                                --rEnd.GetAnyIndex();
                            }
                            buf.remove( nCurrent.GetAnyIndex(), 1 );
                        }
                    }
                    break;
                default:
                    OSL_FAIL( "unknown case in lcl_CleanStr" );
                    break;
                }
            }
            pNextHint = iter.NextAttr(pNextHintNode);
        }
 
        if ( bNewSoftHyphen )
        {
            rArr.push_back( nCurrent );
 
            // If the soft hyphen to be removed is past the end of the range we're searching in,
            // don't adjust the end.
            if (rEnd.GetAnyIndex() > nCurrent.GetAnyIndex())
            {
                --rEnd.GetAnyIndex();
            }
 
            buf.remove(nCurrent.GetAnyIndex(), 1);
            ++nSoftHyphen.GetAnyIndex();
        }
    }
    while ( true );
 
    for (auto i = aReplaced.size(); i; )
    {
        const AmbiguousIndex nTmp = aReplaced[ --i ];
        if (nTmp.GetAnyIndex() == buf.getLength() - 1)
        {
            buf.truncate(nTmp.GetAnyIndex());
            rArr.push_back( nTmp );
            --rEnd.GetAnyIndex();
        }
    }
 
    return buf.makeStringAndClear();
}
 
static bool DoSearch(SwPaM & rSearchPam,
    const i18nutil::SearchOptions2& rSearchOpt, utl::TextSearch& rSText,
    SwMoveFnCollection const & fnMove,
    bool bSrchForward, bool bRegSearch, bool bChkEmptyPara, bool bChkParaEnd,
    AmbiguousIndex & nStart, AmbiguousIndex & nEnd, AmbiguousIndex nTextLen,
    SwTextNode const* pNode, SwTextFrame const* pTextFrame,
    SwRootFrame const* pLayout, SwPaM& rPam);
 
namespace sw {
 
// @param xSearchItem allocate in parent so we can do so outside the calling loop
bool FindTextImpl(SwPaM & rSearchPam,
        const i18nutil::SearchOptions2& rSearchOpt, bool bSearchInNotes,
        utl::TextSearch& rSText,
        SwMoveFnCollection const & fnMove, const SwPaM & rRegion,
        bool bInReadOnly, SwRootFrame const*const pLayout,
        std::unique_ptr<SvxSearchItem>& xSearchItem)
{
    if( rSearchOpt.searchString.isEmpty() )
        return false;
 
    std::optional<SwPaM> oPam;
    sw::MakeRegion(fnMove, rRegion, oPam);
    const bool bSrchForward = &fnMove == &fnMoveForward;
    SwPosition& rPtPos = *oPam->GetPoint();
 
    // If bFound is true then the string was found and is between nStart and nEnd
    bool bFound = false;
    // start position in text or initial position
    bool bFirst = true;
    SwContentNode * pNode;
 
    const bool bRegSearch = SearchAlgorithms2::REGEXP == rSearchOpt.AlgorithmType2;
    const bool bChkEmptyPara = bRegSearch && 2 == rSearchOpt.searchString.getLength() &&
                        ( rSearchOpt.searchString == "^$" ||
                          rSearchOpt.searchString == "$^" );
    const bool bChkParaEnd = bRegSearch && rSearchOpt.searchString == "$";
 
    if (!xSearchItem)
    {
        xSearchItem.reset(new SvxSearchItem(SID_SEARCH_ITEM)); // this is a very expensive operation (calling configmgr etc.)
        xSearchItem->SetSearchOptions(rSearchOpt);
        xSearchItem->SetBackward(!bSrchForward);
    }
 
    // LanguageType eLastLang = 0;
    while (nullptr != (pNode = ::GetNode(*oPam, bFirst, fnMove, bInReadOnly, pLayout)))
    {
        if (!pNode->IsTextNode())
            continue;
 
        const SwTextNode& rTextNode = *pNode->GetTextNode();
        SwTextFrame const* const pFrame(
            pLayout ? static_cast<SwTextFrame const*>(rTextNode.getLayoutFrame(pLayout)) : nullptr);
        assert(!pLayout || pFrame);
        AmbiguousIndex nTextLen;
        if (pLayout)
        {
            nTextLen.SetFrameIndex(TextFrameIndex(pFrame->GetText().getLength()));
        }
        else
        {
            nTextLen.SetModelIndex(rTextNode.GetText().getLength());
        }
        AmbiguousIndex nEnd;
        if (pLayout ? FrameContainsNode(*pFrame, oPam->GetMark()->GetNodeIndex())
                    : rPtPos.GetNode() == oPam->GetMark()->GetNode())
        {
            if (pLayout)
            {
                nEnd.SetFrameIndex(pFrame->MapModelToViewPos(*oPam->GetMark()));
            }
            else
            {
                nEnd.SetModelIndex(oPam->GetMark()->GetContentIndex());
            }
        }
        else
        {
            if (bSrchForward)
            {
                nEnd = nTextLen;
            }
            else
            {
                if (pLayout)
                {
                    nEnd.SetFrameIndex(TextFrameIndex(0));
                }
                else
                {
                    nEnd.SetModelIndex(0);
                }
            }
        }
        AmbiguousIndex nStart;
        if (pLayout)
        {
            nStart.SetFrameIndex(pFrame->MapModelToViewPos(*oPam->GetPoint()));
        }
        else
        {
            nStart.SetModelIndex(rPtPos.GetContentIndex());
        }
 
        /* #i80135# */
        // if there are SwPostItFields inside our current node text, we
        // split the text into separate pieces and search for text inside
        // the pieces as well as inside the fields
        MaybeMergedIter iter(pLayout ? pFrame : nullptr, pLayout ? nullptr : &rTextNode);
 
        // count PostItFields by looping over all fields
        std::vector<std::pair<SwTextAttr const*, AmbiguousIndex>> postits;
        if (bSearchInNotes)
        {
            if (!bSrchForward)
            {
                std::swap(nStart, nEnd);
            }
 
            SwTextNode const* pTemp(nullptr);
            while (SwTextAttr const* const pTextAttr = iter.NextAttr(pTemp))
            {
                if (pTextAttr->Which() == RES_TXTATR_ANNOTATION)
                {
                    AmbiguousIndex aPos;
                    aPos.SetModelIndex(pTextAttr->GetStart());
                    if (pLayout)
                    {
                        aPos.SetFrameIndex(pFrame->MapModelToView(pTemp, aPos.GetModelIndex()));
                    }
                    if ((nStart <= aPos) && (aPos <= nEnd))
                    {
                        postits.emplace_back(pTextAttr, aPos);
                    }
                }
            }
 
            if (!bSrchForward)
            {
                std::swap(nStart, nEnd);
            }
        }
 
        SwDocShell* const pDocShell = pNode->GetDoc().GetDocShell();
        SwWrtShell* const pWrtShell = pDocShell ? pDocShell->GetWrtShell() : nullptr;
        SwPostItMgr* const pPostItMgr = pWrtShell ? pWrtShell->GetPostItMgr() : nullptr;
 
        // If there is an active text edit, then search there.
        bool bEndedTextEdit = false;
        SdrView* pSdrView = pWrtShell ? pWrtShell->GetDrawView() : nullptr;
        if (pSdrView)
        {
            // If the edited object is not anchored to this node, then ignore it.
            SdrObject* pObject = pSdrView->GetTextEditObject();
            if (pObject)
            {
                if (SwFrameFormat* pFrameFormat = FindFrameFormat(pObject))
                {
                    const SwNode* pAnchorNode = pFrameFormat->GetAnchor().GetAnchorNode();
                    if (!pAnchorNode
                        || (pLayout ? !FrameContainsNode(*pFrame, pAnchorNode->GetIndex())
                                    : pAnchorNode->GetIndex() != pNode->GetIndex()))
                        pObject = nullptr;
                }
            }
 
            if (pObject)
            {
                sal_uInt16 nResult
                    = pSdrView->GetTextEditOutlinerView()->StartSearchAndReplace(*xSearchItem);
                if (!nResult)
                {
                    // If not found, end the text edit.
                    pSdrView->SdrEndTextEdit();
                    const Point aPoint(pSdrView->GetAllMarkedRect().TopLeft());
                    pSdrView->UnmarkAll();
                    pWrtShell->CallSetCursor(&aPoint, true);
                    pWrtShell->Edit();
                    bEndedTextEdit = true;
                }
                else
                {
                    bFound = true;
                    break;
                }
            }
        }
 
        if (comphelper::LibreOfficeKit::isActive())
        {
            // Writer and editeng selections are not supported in parallel.
            SvxSearchItem* pSearchItem = SwView::GetSearchItem();
            // If we just finished search in shape text, don't attempt to do that again.
            if (!bEndedTextEdit
                && !(pSearchItem && pSearchItem->GetCommand() == SvxSearchCmd::FIND_ALL))
            {
                // If there are any shapes anchored to this node, search there.
                SwPaM aPaM(pNode->GetDoc().GetNodes().GetEndOfContent());
                if (pLayout)
                {
                    *aPaM.GetPoint() = pFrame->MapViewToModelPos(nStart.GetFrameIndex());
                }
                else
                {
                    aPaM.GetPoint()->Assign(rTextNode, nStart.GetModelIndex());
                }
                aPaM.SetMark();
                if (pLayout)
                {
                    aPaM.GetMark()->Assign(
                        (pFrame->GetMergedPara() ? *pFrame->GetMergedPara()->pLastNode : rTextNode)
                            .GetIndex()
                        + 1);
                }
                else
                {
                    aPaM.GetMark()->Assign(rTextNode.GetIndex() + 1);
                }
                if (pNode->GetDoc().getIDocumentDrawModelAccess().Search(aPaM, *xSearchItem)
                    && pSdrView)
                {
                    if (SdrObject* pObject = pSdrView->GetTextEditObject())
                    {
                        if (SwFrameFormat* pFrameFormat = FindFrameFormat(pObject))
                        {
                            const SwNode* pAnchorNode = pFrameFormat->GetAnchor().GetAnchorNode();
                            if (pAnchorNode)
                            {
                                // Set search position to the shape's anchor point.
                                rSearchPam.GetPoint()->Assign(*pAnchorNode);
                                rSearchPam.SetMark();
                                bFound = true;
                                break;
                            }
                        }
                    }
                }
            }
        }
 
        // do we need to finish a note?
        if (pPostItMgr && pPostItMgr->HasActiveSidebarWin())
        {
            if (bSearchInNotes)
            {
                if (!postits.empty())
                {
                    if (bSrchForward)
                    {
                        postits.erase(postits.begin());
                    }
                    else
                    {
                        postits.pop_back(); // hope that's the right one?
                    }
                }
                //search inside, finish and put focus back into the doc
                if (pPostItMgr->FinishSearchReplace(rSearchOpt, bSrchForward))
                {
                    bFound = true;
                    break;
                }
            }
            else
            {
                pPostItMgr->SetActiveSidebarWin(nullptr);
            }
        }
 
        if (!postits.empty())
        {
            // now we have to split
            AmbiguousIndex nStartInside;
            AmbiguousIndex nEndInside;
            sal_Int32 aLoop = bSrchForward ? 0 : postits.size();
 
            while ((0 <= aLoop) && (o3tl::make_unsigned(aLoop) <= postits.size()))
            {
                if (bSrchForward)
                {
                    if (aLoop == 0)
                    {
                        nStartInside = nStart;
                    }
                    else if (pLayout)
                    {
                        nStartInside.SetFrameIndex(postits[aLoop - 1].second.GetFrameIndex()
                                                   + TextFrameIndex(1));
                    }
                    else
                    {
                        nStartInside.SetModelIndex(postits[aLoop - 1].second.GetModelIndex() + 1);
                    }
                    nEndInside = static_cast<size_t>(aLoop) == postits.size()
                                     ? nEnd
                                     : postits[aLoop].second;
                    nTextLen = nEndInside - nStartInside;
                }
                else
                {
                    nStartInside = static_cast<size_t>(aLoop) == postits.size()
                                       ? nStart
                                       : postits[aLoop].second;
                    if (aLoop == 0)
                    {
                        nEndInside = nEnd;
                    }
                    else if (pLayout)
                    {
                        nEndInside.SetFrameIndex(postits[aLoop - 1].second.GetFrameIndex()
                                                 + TextFrameIndex(1));
                    }
                    else
                    {
                        nEndInside.SetModelIndex(postits[aLoop - 1].second.GetModelIndex() + 1);
                    }
                    nTextLen = nStartInside - nEndInside;
                }
                // search inside the text between a note
                bFound = DoSearch(rSearchPam, rSearchOpt, rSText, fnMove, bSrchForward, bRegSearch,
                                  bChkEmptyPara, bChkParaEnd, nStartInside, nEndInside, nTextLen,
                                  pNode->GetTextNode(), pFrame, pLayout, *oPam);
                if (bFound)
                    break;
                else
                {
                    // we should now be right in front of a note, search inside
                    if (bSrchForward ? (static_cast<size_t>(aLoop) != postits.size())
                                     : (aLoop != 0))
                    {
                        const SwTextAttr* const pTextAttr
                            = bSrchForward ? postits[aLoop].first : postits[aLoop - 1].first;
                        if (pPostItMgr
                            && pPostItMgr->SearchReplace(
                                   static_txtattr_cast<SwTextField const*>(pTextAttr)
                                       ->GetFormatField(),
                                   rSearchOpt, bSrchForward))
                        {
                            bFound = true;
                            break;
                        }
                    }
                }
                aLoop = bSrchForward ? aLoop + 1 : aLoop - 1;
            }
        }
        else
        {
            // if there is no SwPostItField inside or searching inside notes
            // is disabled, we search the whole length just like before
            bFound = DoSearch(rSearchPam, rSearchOpt, rSText, fnMove, bSrchForward, bRegSearch,
                              bChkEmptyPara, bChkParaEnd, nStart, nEnd, nTextLen,
                              pNode->GetTextNode(), pFrame, pLayout, *oPam);
        }
        if (bFound)
            break;
    }
    return bFound;
}
 
} // namespace sw
 
bool DoSearch(SwPaM & rSearchPam,
        const i18nutil::SearchOptions2& rSearchOpt, utl::TextSearch& rSText,
                      SwMoveFnCollection const & fnMove, bool bSrchForward, bool bRegSearch,
                      bool bChkEmptyPara, bool bChkParaEnd,
        AmbiguousIndex & nStart, AmbiguousIndex & nEnd, AmbiguousIndex const nTextLen,
        SwTextNode const*const pNode, SwTextFrame const*const pFrame,
        SwRootFrame const*const pLayout, SwPaM& rPam)
{
    if (bRegSearch && rSearchOpt.searchString.endsWith("$"))
    {
        bool bAlwaysSearchingForEndOfPara = true;
        sal_Int32 nIndex = 0;
        while ((nIndex = rSearchOpt.searchString.indexOf("|", nIndex)) != -1)
        {
            if (!nIndex || rSearchOpt.searchString[nIndex - 1] != '$')
            {
                bAlwaysSearchingForEndOfPara = false;
                break;
            }
            ++nIndex;
        }
        // when searching for something at the end of the paragraph, the para end must be in range
        const AmbiguousIndex& rParaEnd = bSrchForward ? nEnd : nStart;
        if (bAlwaysSearchingForEndOfPara && nTextLen.GetAnyIndex() != rParaEnd.GetAnyIndex())
            return false;
    }
 
    bool bFound = false;
    OUString sCleanStr;
    std::vector<AmbiguousIndex> aFltArr;
    LanguageType eLastLang = LANGUAGE_SYSTEM;
    // if the search string contains a soft hyphen,
    // we don't strip them from the text:
    bool bRemoveSoftHyphens = true;
    // if the search string contains a comment, we don't strip them from the text
    const bool bRemoveCommentAnchors = rSearchOpt.searchString.indexOf( CH_TXTATR_INWORD ) == -1;
 
    if ( bRegSearch )
    {
        if (   -1 != rSearchOpt.searchString.indexOf("\\xAD")
            || -1 != rSearchOpt.searchString.indexOf("\\x{00AD}")
            || -1 != rSearchOpt.searchString.indexOf("\\u00AD")
            || -1 != rSearchOpt.searchString.indexOf("\\u00ad")
            || -1 != rSearchOpt.searchString.indexOf("\\U000000AD")
            || -1 != rSearchOpt.searchString.indexOf("\\N{SOFT HYPHEN}"))
        {
             bRemoveSoftHyphens = false;
        }
    }
    else
    {
        if ( 1 == rSearchOpt.searchString.getLength() &&
             CHAR_SOFTHYPHEN == rSearchOpt.searchString.toChar() )
             bRemoveSoftHyphens = false;
    }
 
    if( bSrchForward )
        sCleanStr = lcl_CleanStr(*pNode, pFrame, pLayout, nStart, nEnd,
                        aFltArr, bRemoveSoftHyphens, bRemoveCommentAnchors);
    else
        sCleanStr = lcl_CleanStr(*pNode, pFrame, pLayout, nEnd, nStart,
                        aFltArr, bRemoveSoftHyphens, bRemoveCommentAnchors);
 
    std::unique_ptr<SwScriptIterator> pScriptIter;
    sal_uInt16 nSearchScript = 0;
    sal_uInt16 nCurrScript = 0;
 
    if (SearchAlgorithms2::APPROXIMATE == rSearchOpt.AlgorithmType2)
    {
        pScriptIter.reset(new SwScriptIterator(sCleanStr, nStart.GetAnyIndex(), bSrchForward));
        nSearchScript = g_pBreakIt->GetRealScriptOfText( rSearchOpt.searchString, 0 );
    }
 
    const AmbiguousIndex nStringEnd = nEnd;
    bool bZeroMatch = false;    // zero-length match, i.e. only $ anchor as regex
    while ( ((bSrchForward && nStart < nStringEnd) ||
            (!bSrchForward && nStringEnd < nStart)) && !bZeroMatch )
    {
        // SearchAlgorithms_APPROXIMATE works on a per word base so we have to
        // provide the text searcher with the correct locale, because it uses
        // the break-iterator
        if ( pScriptIter )
        {
            nEnd.GetAnyIndex() = pScriptIter->GetScriptChgPos();
            nCurrScript = pScriptIter->GetCurrScript();
            if ( nSearchScript == nCurrScript )
            {
                const LanguageType eCurrLang = pLayout
                        ? pFrame->GetLangOfChar(bSrchForward
                                ? nStart.GetFrameIndex()
                                : nEnd.GetFrameIndex(),
                            0, true)
                        : pNode->GetLang(bSrchForward
                                ? nStart.GetModelIndex()
                                : nEnd.GetModelIndex());
 
                if ( eCurrLang != eLastLang )
                {
                    const lang::Locale aLocale(
                            g_pBreakIt->GetLocale( eCurrLang ) );
                    rSText.SetLocale(rSearchOpt, aLocale);
                    eLastLang = eCurrLang;
                }
            }
            pScriptIter->Next();
        }
        AmbiguousIndex nProxyStart = nStart;
        AmbiguousIndex nProxyEnd = nEnd;
        if( nSearchScript == nCurrScript &&
                (rSText.*fnMove.fnSearch)( sCleanStr, &nProxyStart.GetAnyIndex(), &nProxyEnd.GetAnyIndex(), nullptr) &&
                !(bZeroMatch = (nProxyStart == nProxyEnd)))
        {
            nStart = nProxyStart;
            nEnd = nProxyEnd;
            // set section correctly
            *rSearchPam.GetPoint() = *rPam.GetPoint();
            rSearchPam.SetMark();
 
            // adjust start and end
            if( !aFltArr.empty() )
            {
                // if backward search, switch positions temporarily
                if (!bSrchForward) { std::swap(nStart, nEnd); }
 
                AmbiguousIndex nNew = nStart;
                for (size_t n = 0; n < aFltArr.size() && aFltArr[ n ] <= nStart; ++n )
                {
                    ++nNew.GetAnyIndex();
                }
 
                nStart = nNew;
                nNew = nEnd;
                for( size_t n = 0; n < aFltArr.size() && aFltArr[ n ] < nEnd; ++n )
                {
                    ++nNew.GetAnyIndex();
                }
 
                nEnd = nNew;
                // if backward search, switch positions temporarily
                if( !bSrchForward ) { std::swap(nStart, nEnd); }
            }
            if (pLayout)
            {
                *rSearchPam.GetMark() = pFrame->MapViewToModelPos(nStart.GetFrameIndex());
                *rSearchPam.GetPoint() = pFrame->MapViewToModelPos(nEnd.GetFrameIndex());
            }
            else
            {
                rSearchPam.GetMark()->SetContent( nStart.GetModelIndex() );
                rSearchPam.GetPoint()->SetContent( nEnd.GetModelIndex() );
            }
 
            // if backward search, switch point and mark
            if( !bSrchForward )
                rSearchPam.Exchange();
            bFound = true;
            break;
        }
        else
        {
            nEnd = nProxyEnd;
        }
        nStart = nEnd;
    }
 
    pScriptIter.reset();
 
    if ( bFound )
        return true;
 
    if (!bChkEmptyPara && !bChkParaEnd)
        return false;
 
    if (bChkEmptyPara && bSrchForward && nTextLen.GetAnyIndex())
        return false; // the length is not zero - there is content here
 
    // move to the end (or start) of the paragraph
    *rSearchPam.GetPoint() = *rPam.GetPoint();
    if (pLayout)
    {
        *rSearchPam.GetPoint() = pFrame->MapViewToModelPos(
            bSrchForward ? nTextLen.GetFrameIndex() : TextFrameIndex(0));
    }
    else
    {
        rSearchPam.GetPoint()->SetContent(bSrchForward ? nTextLen.GetModelIndex() : 0);
    }
    rSearchPam.SetMark();
 
    if (!rSearchPam.Move(fnMove, GoInContent))
        return false; // at start or end of the document
 
    // selection must not be outside of the search area
    if (!rPam.ContainsPosition(*rSearchPam.GetPoint()))
        return false;
 
    if (SwNodeOffset(1) == abs(rSearchPam.GetPoint()->GetNodeIndex() -
                               rSearchPam.GetMark()->GetNodeIndex()))
    {
        if (bChkEmptyPara && !bSrchForward && rSearchPam.GetPoint()->GetContentIndex())
            return false; // the length is not zero - there is content here
 
        return true;
    }
 
    return false;
}
 
namespace {
 
/// parameters for search and replace in text
struct SwFindParaText : public SwFindParas
{
    const i18nutil::SearchOptions2& m_rSearchOpt;
    SwCursor& m_rCursor;
    SwRootFrame const* m_pLayout;
    utl::TextSearch m_aSText;
    bool m_bReplace;
    bool m_bSearchInNotes;
 
    SwFindParaText(const i18nutil::SearchOptions2& rOpt, bool bSearchInNotes,
            bool bRepl, SwCursor& rCursor, SwRootFrame const*const pLayout)
        : m_rSearchOpt( rOpt )
        , m_rCursor( rCursor )
        , m_pLayout(pLayout)
        , m_aSText(rOpt)
        , m_bReplace( bRepl )
        , m_bSearchInNotes( bSearchInNotes )
    {}
    virtual int DoFind(SwPaM &, SwMoveFnCollection const &, const SwPaM &, bool bInReadOnly, std::unique_ptr<SvxSearchItem>& xSearchItem) override;
    virtual bool IsReplaceMode() const override;
    virtual ~SwFindParaText();
};
 
}
 
SwFindParaText::~SwFindParaText()
{
}
 
int SwFindParaText::DoFind(SwPaM & rCursor, SwMoveFnCollection const & fnMove,
                          const SwPaM & rRegion, bool bInReadOnly,
                          std::unique_ptr<SvxSearchItem>& xSearchItem)
{
    if( bInReadOnly && m_bReplace )
        bInReadOnly = false;
 
    const bool bFnd = sw::FindTextImpl(rCursor, m_rSearchOpt, m_bSearchInNotes,
            m_aSText, fnMove, rRegion, bInReadOnly, m_pLayout, xSearchItem);
 
    if( bFnd && m_bReplace ) // replace string
    {
        // use replace method in SwDoc
        const bool bRegExp(SearchAlgorithms2::REGEXP == m_rSearchOpt.AlgorithmType2);
        const sal_Int32 nSttCnt = rCursor.Start()->GetContentIndex();
        // add to shell-cursor-ring so that the regions will be moved eventually
        SwPaM* pPrev(nullptr);
        if( bRegExp )
        {
            pPrev = const_cast<SwPaM&>(rRegion).GetPrev();
            const_cast<SwPaM&>(rRegion).GetRingContainer().merge( m_rCursor.GetRingContainer() );
        }
 
        std::optional<OUString> xRepl;
        if (bRegExp)
            xRepl = sw::ReplaceBackReferences(m_rSearchOpt, &rCursor, m_pLayout);
        bool const bReplaced = sw::ReplaceImpl(rCursor,
                xRepl ? *xRepl : m_rSearchOpt.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 != pPrev );
        }
        if (bRegExp && !bReplaced)
        {   // fdo#80715 avoid infinite loop if join failed
            bool bRet = ((&fnMoveForward == &fnMove) ? &GoNextPara : &GoPrevPara)
                (rCursor, fnMove);
            (void) bRet;
            assert(bRet); // if join failed, next node must be SwTextNode
        }
        else
            rCursor.Start()->SetContent(nSttCnt);
        return FIND_NO_RING;
    }
    return bFnd ? FIND_FOUND : FIND_NOT_FOUND;
}
 
bool SwFindParaText::IsReplaceMode() const
{
    return m_bReplace;
}
 
sal_Int32 SwCursor::Find_Text( const i18nutil::SearchOptions2& rSearchOpt, bool bSearchInNotes,
                          SwDocPositions nStart, SwDocPositions nEnd,
                          bool& bCancel, FindRanges eFndRngs, bool bReplace,
                          SwRootFrame const*const pLayout)
{
    // switch off OLE-notifications
    SwDoc& rDoc = GetDoc();
    Link<bool,void> aLnk( rDoc.GetOle2Link() );
    rDoc.SetOle2Link( Link<bool,void>() );
 
    bool const bStartUndo = rDoc.GetIDocumentUndoRedo().DoesUndo() && bReplace;
    if (bStartUndo)
    {
        rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE, nullptr );
    }
 
    bool bSearchSel = 0 != (rSearchOpt.searchFlag & SearchFlags::REG_NOT_BEGINOFLINE);
    if( bSearchSel )
        eFndRngs = static_cast<FindRanges>(eFndRngs | FindRanges::InSel);
    SwFindParaText aSwFindParaText(rSearchOpt, bSearchInNotes, bReplace, *this, pLayout);
    sal_Int32 nRet = FindAll( aSwFindParaText, nStart, nEnd, eFndRngs, bCancel );
    rDoc.SetOle2Link( aLnk );
    if( nRet && bReplace )
        rDoc.getIDocumentState().SetModified();
 
    if (bStartUndo)
    {
        SwRewriter rewriter(MakeUndoReplaceRewriter(
                nRet, rSearchOpt.searchString, rSearchOpt.replaceString));
        rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE, & rewriter );
    }
    return nRet;
}
 
namespace sw {
 
bool ReplaceImpl(
        SwPaM & rCursor,
        OUString const& rReplacement,
        bool const bRegExp,
        SwDoc & rDoc,
        SwRootFrame const*const pLayout)
{
    bool bReplaced(true);
    IDocumentContentOperations & rIDCO(rDoc.getIDocumentContentOperations());
#if 0
    // FIXME there's some problem with multiple redlines here on Undo
    std::vector<std::shared_ptr<SwUnoCursor>> ranges;
    if (rDoc.getIDocumentRedlineAccess().IsRedlineOn()
        || !pLayout
        || !pLayout->IsHideRedlines()
        || sw::GetRanges(ranges, rDoc, rCursor))
    {
        bReplaced = rIDCO.ReplaceRange(rCursor, rReplacement, bRegExp);
    }
    else
    {
        assert(!ranges.empty());
        assert(ranges.front()->GetPoint()->GetNode() == ranges.front()->GetMark()->GetNode());
        bReplaced = rIDCO.ReplaceRange(*ranges.front(), rReplacement, bRegExp);
        for (auto it = ranges.begin() + 1; it != ranges.end(); ++it)
        {
            bReplaced &= rIDCO.DeleteAndJoin(**it);
        }
    }
#else
    IDocumentRedlineAccess const& rIDRA(rDoc.getIDocumentRedlineAccess());
    if (pLayout && pLayout->IsHideRedlines()
        && !rIDRA.IsRedlineOn() // otherwise: ReplaceRange will handle it
        && (rIDRA.GetRedlineFlags() & RedlineFlags::ShowDelete)) // otherwise: ReplaceRange will DeleteRedline()
    {
        SwRedlineTable::size_type tmp;
        rIDRA.GetRedline(*rCursor.Start(), &tmp);
        while (tmp < rIDRA.GetRedlineTable().size())
        {
            SwRangeRedline const*const pRedline(rIDRA.GetRedlineTable()[tmp]);
            if (*rCursor.End() <= *pRedline->Start())
            {
                break;
            }
            if (*pRedline->End() <= *rCursor.Start())
            {
                ++tmp;
                continue;
            }
            if (pRedline->GetType() == RedlineType::Delete)
            {
                assert(*pRedline->Start() != *pRedline->End());
                // search in hidden layout can't overlap redlines
                assert(*rCursor.Start() <= *pRedline->Start() && *pRedline->End() <= *rCursor.End());
                SwPaM pam(*pRedline, nullptr);
                bReplaced &= rIDCO.DeleteAndJoin(pam);
            }
            else
            {
                ++tmp;
            }
        }
    }
    bReplaced &= rIDCO.ReplaceRange(rCursor, rReplacement, bRegExp);
#endif
    return bReplaced;
}
 
std::optional<OUString> ReplaceBackReferences(const i18nutil::SearchOptions2& rSearchOpt,
        SwPaM *const pPam, SwRootFrame const*const pLayout)
{
    std::optional<OUString> xRet;
    if( pPam && pPam->HasMark() &&
        SearchAlgorithms2::REGEXP == rSearchOpt.AlgorithmType2 )
    {
        SwContentNode const*const pTextNode = pPam->GetPointContentNode();
        SwContentNode const*const pMarkTextNode = pPam->GetMarkContentNode();
        if (!pTextNode || !pTextNode->IsTextNode()
            || !pMarkTextNode || !pMarkTextNode->IsTextNode())
        {
            return xRet;
        }
        SwTextFrame const*const pFrame(pLayout
            ? static_cast<SwTextFrame const*>(pTextNode->getLayoutFrame(pLayout))
            : nullptr);
        const bool bParaEnd = rSearchOpt.searchString == "$" || rSearchOpt.searchString == "^$" || rSearchOpt.searchString == "$^";
        if (bParaEnd || (pLayout
                ? sw::FrameContainsNode(*pFrame, pPam->GetMark()->GetNodeIndex())
                : pTextNode == pMarkTextNode))
        {
            utl::TextSearch aSText(rSearchOpt);
            SearchResult aResult;
            OUString aReplaceStr( rSearchOpt.replaceString );
            if (bParaEnd)
            {
                static constexpr OUString aStr(u"\\n"_ustr);
                aResult.subRegExpressions = 1;
                aResult.startOffset = { 0 };
                aResult.endOffset = { aStr.getLength() };
                utl::TextSearch::ReplaceBackReferences( aReplaceStr, aStr, aResult );
                xRet = aReplaceStr;
            }
            else
            {
                AmbiguousIndex nStart;
                AmbiguousIndex nEnd;
                if (pLayout)
                {
                    nStart.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->Start()));
                    nEnd.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->End()));
                }
                else
                {
                    nStart.SetModelIndex(pPam->Start()->GetContentIndex());
                    nEnd.SetModelIndex(pPam->End()->GetContentIndex());
                }
                std::vector<AmbiguousIndex> aFltArr;
                OUString const aStr = lcl_CleanStr(*pTextNode->GetTextNode(), pFrame, pLayout,
                                                   nStart, nEnd, aFltArr, false, false);
                if (aSText.SearchForward(aStr, &nStart.GetAnyIndex(), &nEnd.GetAnyIndex(), &aResult))
                {
                    utl::TextSearch::ReplaceBackReferences( aReplaceStr, aStr, aResult );
                    xRet = aReplaceStr;
                }
            }
        }
    }
    return xRet;
}
 
} // namespace sw
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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

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

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

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