/* -*- 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.