summaryrefslogtreecommitdiffstats
path: root/sw/source/core/crsr/findtxt.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/crsr/findtxt.cxx')
-rw-r--r--sw/source/core/crsr/findtxt.cxx1172
1 files changed, 1172 insertions, 0 deletions
diff --git a/sw/source/core/crsr/findtxt.cxx b/sw/source/core/crsr/findtxt.cxx
new file mode 100644
index 0000000000..5f63f8f136
--- /dev/null
+++ b/sw/source/core/crsr/findtxt.cxx
@@ -0,0 +1,1172 @@
+/* -*- 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* pPam);
+
+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() )
+ {
+ 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 ? &*oPam : nullptr );
+ 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 ? &*oPam : nullptr );
+ }
+ 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* pPam)
+{
+ bool bFound = false;
+ SwPosition& rPtPos = *pPam->GetPoint();
+ 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() = *pPam->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;
+ else if ((bChkEmptyPara && !nStart.GetAnyIndex() && !nTextLen.GetAnyIndex())
+ || bChkParaEnd)
+ {
+ *rSearchPam.GetPoint() = *pPam->GetPoint();
+ if (pLayout)
+ {
+ *rSearchPam.GetPoint() = pFrame->MapViewToModelPos(
+ bChkParaEnd ? nTextLen.GetFrameIndex() : TextFrameIndex(0));
+ }
+ else
+ {
+ rSearchPam.GetPoint()->SetContent( bChkParaEnd ? nTextLen.GetModelIndex() : 0 );
+ }
+ rSearchPam.SetMark();
+ const SwNode *const pSttNd = bSrchForward
+ ? &rSearchPam.GetPoint()->GetNode() // end of the frame
+ : &rPtPos.GetNode(); // keep the bug as-is for now...
+ /* FIXME: this condition does not work for !bSrchForward backward
+ * search, it probably never did. (pSttNd != &rNdIdx.GetNode())
+ * is never true in this case. */
+ if( (bSrchForward || pSttNd != &rPtPos.GetNode()) &&
+ rSearchPam.Move(fnMoveForward, GoInContent) &&
+ (!bSrchForward || pSttNd != &rSearchPam.GetPoint()->GetNode()) &&
+ SwNodeOffset(1) == abs(rSearchPam.GetPoint()->GetNodeIndex() -
+ rSearchPam.GetMark()->GetNodeIndex()))
+ {
+ // if backward search, switch point and mark
+ if( !bSrchForward )
+ rSearchPam.Exchange();
+ return true;
+ }
+ }
+ return bFound;
+}
+
+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() };
+ aSText.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))
+ {
+ aSText.ReplaceBackReferences( aReplaceStr, aStr, aResult );
+ xRet = aReplaceStr;
+ }
+ }
+ }
+ }
+ return xRet;
+}
+
+} // namespace sw
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */