1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
22 #include <com/sun/star/util/SearchFlags.hpp>
23 #include <com/sun/star/util/SearchResult.hpp>
24 #include <comphelper/lok.hxx>
25 #include <o3tl/safeint.hxx>
26 #include <rtl/ustrbuf.hxx>
27 #include <svx/svdview.hxx>
28 #include <svl/srchitem.hxx>
29 #include <sfx2/sfxsids.hrc>
30 #include <editeng/outliner.hxx>
31 #include <osl/diagnose.h>
34 #include <txatritr.hxx>
39 #include <rootfrm.hxx>
41 #include <redline.hxx>
43 #include <IDocumentUndoRedo.hxx>
44 #include <IDocumentState.hxx>
45 #include <IDocumentDrawModelAccess.hxx>
46 #include <IDocumentRedlineAccess.hxx>
47 #include <dcontact.hxx>
51 #include <UndoInsert.hxx>
52 #include <breakit.hxx>
54 #include <PostItMgr.hxx>
57 using namespace ::com::sun::star
;
62 /// because the Find may be called on the View or the Model, we need an index
63 /// afflicted by multiple personality disorder
70 enum class tags
: char { Any
, Frame
, Model
};
75 AmbiguousIndex() : m_value(-1)
80 explicit AmbiguousIndex(sal_Int32
const value
90 sal_Int32
& GetAnyIndex() { return m_value
; } ///< for arithmetic
91 sal_Int32
const& GetAnyIndex() const { return m_value
; } ///< for arithmetic
92 TextFrameIndex
GetFrameIndex() const
94 assert(m_tag
!= tags::Model
);
95 return TextFrameIndex(m_value
);
97 sal_Int32
GetModelIndex() const
99 assert(m_tag
!= tags::Frame
);
102 void SetFrameIndex(TextFrameIndex
const value
)
107 m_value
= sal_Int32(value
);
109 void SetModelIndex(sal_Int32
const value
)
117 bool operator ==(AmbiguousIndex
const& rOther
) const
119 assert(m_tag
== tags::Any
|| rOther
.m_tag
== tags::Any
|| m_tag
== rOther
.m_tag
);
120 return m_value
== rOther
.m_value
;
122 bool operator <=(AmbiguousIndex
const& rOther
) const
124 assert(m_tag
== tags::Any
|| rOther
.m_tag
== tags::Any
|| m_tag
== rOther
.m_tag
);
125 return m_value
<= rOther
.m_value
;
127 bool operator < (AmbiguousIndex
const& rOther
) const
129 assert(m_tag
== tags::Any
|| rOther
.m_tag
== tags::Any
|| m_tag
== rOther
.m_tag
);
130 return m_value
< rOther
.m_value
;
132 AmbiguousIndex
operator - (AmbiguousIndex
const& rOther
) const
134 assert(m_tag
== tags::Any
|| rOther
.m_tag
== tags::Any
|| m_tag
== rOther
.m_tag
);
135 return AmbiguousIndex(m_value
- rOther
.m_value
137 , std::max(m_tag
, rOther
.m_tag
)
143 class MaybeMergedIter
145 std::optional
<sw::MergedAttrIter
> m_oMergedIter
;
146 SwTextNode
const*const m_pNode
;
150 MaybeMergedIter(SwTextFrame
const*const pFrame
, SwTextNode
const*const pNode
)
156 m_oMergedIter
.emplace(*pFrame
);
160 SwTextAttr
const* NextAttr(SwTextNode
const*& rpNode
)
164 return m_oMergedIter
->NextAttr(&rpNode
);
166 if (SwpHints
const*const pHints
= m_pNode
->GetpSwpHints())
168 if (m_HintIndex
< pHints
->Count())
171 return pHints
->Get(m_HintIndex
++);
181 lcl_CleanStr(const SwTextNode
& rNd
,
182 SwTextFrame
const*const pFrame
,
183 SwRootFrame
const*const pLayout
,
184 AmbiguousIndex
const nStart
, AmbiguousIndex
& rEnd
,
185 std::vector
<AmbiguousIndex
> &rArr
,
186 bool const bRemoveSoftHyphen
, bool const bRemoveCommentAnchors
)
188 OUStringBuffer
buf(pLayout
? pFrame
->GetText() : rNd
.GetText());
191 MaybeMergedIter
iter(pLayout
? pFrame
: nullptr, pLayout
? nullptr : &rNd
);
193 AmbiguousIndex nSoftHyphen
= nStart
;
194 AmbiguousIndex nHintStart
;
195 bool bNewHint
= true;
196 bool bNewSoftHyphen
= true;
197 const AmbiguousIndex nEnd
= rEnd
;
198 std::vector
<AmbiguousIndex
> aReplaced
;
199 SwTextNode
const* pNextHintNode(nullptr);
200 SwTextAttr
const* pNextHint(iter
.NextAttr(pNextHintNode
));
208 nHintStart
.SetFrameIndex(pNextHint
209 ? pFrame
->MapModelToView(pNextHintNode
, pNextHint
->GetStart())
210 : TextFrameIndex(-1));
214 nHintStart
.SetModelIndex(pNextHint
? pNextHint
->GetStart() : -1);
218 if ( bNewSoftHyphen
)
222 nSoftHyphen
.SetFrameIndex(TextFrameIndex(bRemoveSoftHyphen
223 ? pFrame
->GetText().indexOf(CHAR_SOFTHYPHEN
, nSoftHyphen
.GetAnyIndex())
228 nSoftHyphen
.SetModelIndex(bRemoveSoftHyphen
229 ? rNd
.GetText().indexOf(CHAR_SOFTHYPHEN
, nSoftHyphen
.GetAnyIndex())
235 bNewSoftHyphen
= false;
238 // Check if next stop is a hint.
239 if (0 <= nHintStart
.GetAnyIndex()
240 && (-1 == nSoftHyphen
.GetAnyIndex() || nHintStart
< nSoftHyphen
)
241 && nHintStart
< nEnd
)
246 // Check if next stop is a soft hyphen.
247 else if ( -1 != nSoftHyphen
.GetAnyIndex()
248 && (-1 == nHintStart
.GetAnyIndex() || nSoftHyphen
< nHintStart
)
249 && nSoftHyphen
< nEnd
)
252 bNewSoftHyphen
= true;
254 // If nSoftHyphen == nHintStart, the current hint *must* be a hint with an end.
255 else if (-1 != nSoftHyphen
.GetAnyIndex() && nSoftHyphen
== nHintStart
)
259 bNewSoftHyphen
= true;
264 AmbiguousIndex
nCurrent(nStt
);
265 nCurrent
.GetAnyIndex() -= rArr
.size();
269 if (pNextHint
&& pNextHint
->HasDummyChar() && (nStart
<= nStt
))
271 switch (pNextHint
->Which())
273 case RES_TXTATR_FLYCNT
:
274 case RES_TXTATR_FIELD
:
275 case RES_TXTATR_REFMARK
:
276 case RES_TXTATR_TOXMARK
:
277 case RES_TXTATR_META
:
278 case RES_TXTATR_METAFIELD
:
280 // (1998) they are desired as separators and
281 // belong not any longer to a word.
282 // they should also be ignored at a
283 // beginning/end of a sentence if blank. Those are
284 // simply removed if first. If at the end, we keep the
285 // replacement and remove afterwards all at a string's
286 // end (might be normal 0x7f).
287 const bool bEmpty
= pNextHint
->Which() != RES_TXTATR_FIELD
288 || (static_txtattr_cast
<SwTextField
const*>(pNextHint
)->GetFormatField().GetField()->ExpandField(true, pLayout
).isEmpty());
289 if ( bEmpty
&& nStart
== nCurrent
)
291 rArr
.push_back( nCurrent
);
292 if (rEnd
.GetAnyIndex() > nCurrent
.GetAnyIndex())
294 --rEnd
.GetAnyIndex();
296 buf
.remove(nCurrent
.GetAnyIndex(), 1);
301 aReplaced
.push_back( nCurrent
);
302 buf
[nCurrent
.GetAnyIndex()] = '\x7f';
306 case RES_TXTATR_ANNOTATION
:
308 if( bRemoveCommentAnchors
)
310 rArr
.push_back( nCurrent
);
311 if (rEnd
.GetAnyIndex() > nCurrent
.GetAnyIndex())
313 --rEnd
.GetAnyIndex();
315 buf
.remove( nCurrent
.GetAnyIndex(), 1 );
320 OSL_FAIL( "unknown case in lcl_CleanStr" );
324 pNextHint
= iter
.NextAttr(pNextHintNode
);
327 if ( bNewSoftHyphen
)
329 rArr
.push_back( nCurrent
);
331 // If the soft hyphen to be removed is past the end of the range we're searching in,
332 // don't adjust the end.
333 if (rEnd
.GetAnyIndex() > nCurrent
.GetAnyIndex())
335 --rEnd
.GetAnyIndex();
338 buf
.remove(nCurrent
.GetAnyIndex(), 1);
339 ++nSoftHyphen
.GetAnyIndex();
344 for (auto i
= aReplaced
.size(); i
; )
346 const AmbiguousIndex nTmp
= aReplaced
[ --i
];
347 if (nTmp
.GetAnyIndex() == buf
.getLength() - 1)
349 buf
.truncate(nTmp
.GetAnyIndex());
350 rArr
.push_back( nTmp
);
351 --rEnd
.GetAnyIndex();
355 return buf
.makeStringAndClear();
358 static bool DoSearch(SwPaM
& rSearchPam
,
359 const i18nutil::SearchOptions2
& rSearchOpt
, utl::TextSearch
& rSText
,
360 SwMoveFnCollection
const & fnMove
,
361 bool bSrchForward
, bool bRegSearch
, bool bChkEmptyPara
, bool bChkParaEnd
,
362 AmbiguousIndex
& nStart
, AmbiguousIndex
& nEnd
, AmbiguousIndex nTextLen
,
363 SwTextNode
const* pNode
, SwTextFrame
const* pTextFrame
,
364 SwRootFrame
const* pLayout
, SwPaM
* pPam
);
368 // @param xSearchItem allocate in parent so we can do so outside the calling loop
369 bool FindTextImpl(SwPaM
& rSearchPam
,
370 const i18nutil::SearchOptions2
& rSearchOpt
, bool bSearchInNotes
,
371 utl::TextSearch
& rSText
,
372 SwMoveFnCollection
const & fnMove
, const SwPaM
& rRegion
,
373 bool bInReadOnly
, SwRootFrame
const*const pLayout
,
374 std::unique_ptr
<SvxSearchItem
>& xSearchItem
)
376 if( rSearchOpt
.searchString
.isEmpty() )
379 std::optional
<SwPaM
> oPam
;
380 sw::MakeRegion(fnMove
, rRegion
, oPam
);
381 const bool bSrchForward
= &fnMove
== &fnMoveForward
;
382 SwPosition
& rPtPos
= *oPam
->GetPoint();
384 // If bFound is true then the string was found and is between nStart and nEnd
386 // start position in text or initial position
388 SwContentNode
* pNode
;
390 const bool bRegSearch
= SearchAlgorithms2::REGEXP
== rSearchOpt
.AlgorithmType2
;
391 const bool bChkEmptyPara
= bRegSearch
&& 2 == rSearchOpt
.searchString
.getLength() &&
392 ( rSearchOpt
.searchString
== "^$" ||
393 rSearchOpt
.searchString
== "$^" );
394 const bool bChkParaEnd
= bRegSearch
&& rSearchOpt
.searchString
== "$";
398 xSearchItem
.reset(new SvxSearchItem(SID_SEARCH_ITEM
)); // this is a very expensive operation (calling configmgr etc.)
399 xSearchItem
->SetSearchOptions(rSearchOpt
);
400 xSearchItem
->SetBackward(!bSrchForward
);
403 // LanguageType eLastLang = 0;
404 while (nullptr != (pNode
= ::GetNode(*oPam
, bFirst
, fnMove
, bInReadOnly
, pLayout
)))
406 if( pNode
->IsTextNode() )
408 SwTextNode
& rTextNode
= *pNode
->GetTextNode();
409 SwTextFrame
const*const pFrame(pLayout
410 ? static_cast<SwTextFrame
const*>(rTextNode
.getLayoutFrame(pLayout
))
412 assert(!pLayout
|| pFrame
);
413 AmbiguousIndex nTextLen
;
416 nTextLen
.SetFrameIndex(TextFrameIndex(pFrame
->GetText().getLength()));
420 nTextLen
.SetModelIndex(rTextNode
.GetText().getLength());
424 ? FrameContainsNode(*pFrame
, oPam
->GetMark()->GetNodeIndex())
425 : rPtPos
.GetNode() == oPam
->GetMark()->GetNode())
429 nEnd
.SetFrameIndex(pFrame
->MapModelToViewPos(*oPam
->GetMark()));
433 nEnd
.SetModelIndex(oPam
->GetMark()->GetContentIndex());
446 nEnd
.SetFrameIndex(TextFrameIndex(0));
450 nEnd
.SetModelIndex(0);
454 AmbiguousIndex nStart
;
457 nStart
.SetFrameIndex(pFrame
->MapModelToViewPos(*oPam
->GetPoint()));
461 nStart
.SetModelIndex(rPtPos
.GetContentIndex());
465 // if there are SwPostItFields inside our current node text, we
466 // split the text into separate pieces and search for text inside
467 // the pieces as well as inside the fields
468 MaybeMergedIter
iter(pLayout
? pFrame
: nullptr, pLayout
? nullptr : &rTextNode
);
470 // count PostItFields by looping over all fields
471 std::vector
<std::pair
<SwTextAttr
const*, AmbiguousIndex
>> postits
;
476 std::swap(nStart
, nEnd
);
479 SwTextNode
const* pTemp(nullptr);
480 while (SwTextAttr
const*const pTextAttr
= iter
.NextAttr(pTemp
))
482 if ( pTextAttr
->Which()==RES_TXTATR_ANNOTATION
)
485 aPos
.SetModelIndex(pTextAttr
->GetStart());
488 aPos
.SetFrameIndex(pFrame
->MapModelToView(pTemp
, aPos
.GetModelIndex()));
490 if ((nStart
<= aPos
) && (aPos
<= nEnd
))
492 postits
.emplace_back(pTextAttr
, aPos
);
499 std::swap(nStart
, nEnd
);
504 SwDocShell
*const pDocShell
= pNode
->GetDoc().GetDocShell();
505 SwWrtShell
*const pWrtShell
= pDocShell
? pDocShell
->GetWrtShell() : nullptr;
506 SwPostItMgr
*const pPostItMgr
= pWrtShell
? pWrtShell
->GetPostItMgr() : nullptr;
508 // If there is an active text edit, then search there.
509 bool bEndedTextEdit
= false;
510 SdrView
* pSdrView
= pWrtShell
? pWrtShell
->GetDrawView() : nullptr;
513 // If the edited object is not anchored to this node, then ignore it.
514 SdrObject
* pObject
= pSdrView
->GetTextEditObject();
517 if (SwFrameFormat
* pFrameFormat
= FindFrameFormat(pObject
))
519 const SwNode
* pAnchorNode
= pFrameFormat
->GetAnchor().GetAnchorNode();
520 if (!pAnchorNode
|| (pLayout
521 ? !FrameContainsNode(*pFrame
, pAnchorNode
->GetIndex())
522 : pAnchorNode
->GetIndex() != pNode
->GetIndex()))
529 sal_uInt16 nResult
= pSdrView
->GetTextEditOutlinerView()->StartSearchAndReplace(*xSearchItem
);
532 // If not found, end the text edit.
533 pSdrView
->SdrEndTextEdit();
534 const Point
aPoint(pSdrView
->GetAllMarkedRect().TopLeft());
535 pSdrView
->UnmarkAll();
536 pWrtShell
->CallSetCursor(&aPoint
, true);
538 bEndedTextEdit
= true;
548 if (comphelper::LibreOfficeKit::isActive())
550 // Writer and editeng selections are not supported in parallel.
551 SvxSearchItem
* pSearchItem
= SwView::GetSearchItem();
552 // If we just finished search in shape text, don't attempt to do that again.
553 if (!bEndedTextEdit
&& !(pSearchItem
&& pSearchItem
->GetCommand() == SvxSearchCmd::FIND_ALL
))
555 // If there are any shapes anchored to this node, search there.
556 SwPaM
aPaM(pNode
->GetDoc().GetNodes().GetEndOfContent());
559 *aPaM
.GetPoint() = pFrame
->MapViewToModelPos(nStart
.GetFrameIndex());
563 aPaM
.GetPoint()->Assign(rTextNode
, nStart
.GetModelIndex());
568 aPaM
.GetMark()->Assign( (pFrame
->GetMergedPara()
569 ? *pFrame
->GetMergedPara()->pLastNode
575 aPaM
.GetMark()->Assign( rTextNode
.GetIndex() + 1 );
577 if (pNode
->GetDoc().getIDocumentDrawModelAccess().Search(aPaM
, *xSearchItem
) && pSdrView
)
579 if (SdrObject
* pObject
= pSdrView
->GetTextEditObject())
581 if (SwFrameFormat
* pFrameFormat
= FindFrameFormat(pObject
))
583 const SwNode
* pAnchorNode
= pFrameFormat
->GetAnchor().GetAnchorNode();
586 // Set search position to the shape's anchor point.
587 rSearchPam
.GetPoint()->Assign(*pAnchorNode
);
588 rSearchPam
.SetMark();
598 // do we need to finish a note?
599 if (pPostItMgr
&& pPostItMgr
->HasActiveSidebarWin())
603 if (!postits
.empty())
607 postits
.erase(postits
.begin());
611 postits
.pop_back(); // hope that's the right one?
614 //search inside, finish and put focus back into the doc
615 if (pPostItMgr
->FinishSearchReplace(rSearchOpt
,bSrchForward
))
623 pPostItMgr
->SetActiveSidebarWin(nullptr);
627 if (!postits
.empty())
629 // now we have to split
630 AmbiguousIndex nStartInside
;
631 AmbiguousIndex nEndInside
;
632 sal_Int32 aLoop
= bSrchForward
? 0 : postits
.size();
634 while ((0 <= aLoop
) && (o3tl::make_unsigned(aLoop
) <= postits
.size()))
640 nStartInside
= nStart
;
644 nStartInside
.SetFrameIndex(postits
[aLoop
- 1].second
.GetFrameIndex() + TextFrameIndex(1));
648 nStartInside
.SetModelIndex(postits
[aLoop
- 1].second
.GetModelIndex() + 1);
650 nEndInside
= static_cast<size_t>(aLoop
) == postits
.size()
652 : postits
[aLoop
].second
;
653 nTextLen
= nEndInside
- nStartInside
;
657 nStartInside
= static_cast<size_t>(aLoop
) == postits
.size()
659 : postits
[aLoop
].second
;
666 nEndInside
.SetFrameIndex(postits
[aLoop
- 1].second
.GetFrameIndex() + TextFrameIndex(1));
670 nEndInside
.SetModelIndex(postits
[aLoop
- 1].second
.GetModelIndex() + 1);
672 nTextLen
= nStartInside
- nEndInside
;
674 // search inside the text between a note
675 bFound
= DoSearch( rSearchPam
,
676 rSearchOpt
, rSText
, fnMove
, bSrchForward
,
677 bRegSearch
, bChkEmptyPara
, bChkParaEnd
,
678 nStartInside
, nEndInside
, nTextLen
,
679 pNode
->GetTextNode(), pFrame
, pLayout
,
680 oPam
? &*oPam
: nullptr );
685 // we should now be right in front of a note, search inside
687 ? (static_cast<size_t>(aLoop
) != postits
.size())
690 const SwTextAttr
*const pTextAttr
= bSrchForward
691 ? postits
[aLoop
].first
692 : postits
[aLoop
- 1].first
;
693 if (pPostItMgr
&& pPostItMgr
->SearchReplace(
694 static_txtattr_cast
<SwTextField
const*>(pTextAttr
)->GetFormatField(),rSearchOpt
,bSrchForward
))
701 aLoop
= bSrchForward
? aLoop
+1 : aLoop
-1;
706 // if there is no SwPostItField inside or searching inside notes
707 // is disabled, we search the whole length just like before
708 bFound
= DoSearch( rSearchPam
,
709 rSearchOpt
, rSText
, fnMove
, bSrchForward
,
710 bRegSearch
, bChkEmptyPara
, bChkParaEnd
,
711 nStart
, nEnd
, nTextLen
,
712 pNode
->GetTextNode(), pFrame
, pLayout
,
713 oPam
? &*oPam
: nullptr );
724 bool DoSearch(SwPaM
& rSearchPam
,
725 const i18nutil::SearchOptions2
& rSearchOpt
, utl::TextSearch
& rSText
,
726 SwMoveFnCollection
const & fnMove
, bool bSrchForward
, bool bRegSearch
,
727 bool bChkEmptyPara
, bool bChkParaEnd
,
728 AmbiguousIndex
& nStart
, AmbiguousIndex
& nEnd
, AmbiguousIndex
const nTextLen
,
729 SwTextNode
const*const pNode
, SwTextFrame
const*const pFrame
,
730 SwRootFrame
const*const pLayout
, SwPaM
* pPam
)
733 SwPosition
& rPtPos
= *pPam
->GetPoint();
735 std::vector
<AmbiguousIndex
> aFltArr
;
736 LanguageType eLastLang
= LANGUAGE_SYSTEM
;
737 // if the search string contains a soft hyphen,
738 // we don't strip them from the text:
739 bool bRemoveSoftHyphens
= true;
740 // if the search string contains a comment, we don't strip them from the text
741 const bool bRemoveCommentAnchors
= rSearchOpt
.searchString
.indexOf( CH_TXTATR_INWORD
) == -1;
745 if ( -1 != rSearchOpt
.searchString
.indexOf("\\xAD")
746 || -1 != rSearchOpt
.searchString
.indexOf("\\x{00AD}")
747 || -1 != rSearchOpt
.searchString
.indexOf("\\u00AD")
748 || -1 != rSearchOpt
.searchString
.indexOf("\\u00ad")
749 || -1 != rSearchOpt
.searchString
.indexOf("\\U000000AD")
750 || -1 != rSearchOpt
.searchString
.indexOf("\\N{SOFT HYPHEN}"))
752 bRemoveSoftHyphens
= false;
757 if ( 1 == rSearchOpt
.searchString
.getLength() &&
758 CHAR_SOFTHYPHEN
== rSearchOpt
.searchString
.toChar() )
759 bRemoveSoftHyphens
= false;
763 sCleanStr
= lcl_CleanStr(*pNode
, pFrame
, pLayout
, nStart
, nEnd
,
764 aFltArr
, bRemoveSoftHyphens
, bRemoveCommentAnchors
);
766 sCleanStr
= lcl_CleanStr(*pNode
, pFrame
, pLayout
, nEnd
, nStart
,
767 aFltArr
, bRemoveSoftHyphens
, bRemoveCommentAnchors
);
769 std::unique_ptr
<SwScriptIterator
> pScriptIter
;
770 sal_uInt16 nSearchScript
= 0;
771 sal_uInt16 nCurrScript
= 0;
773 if (SearchAlgorithms2::APPROXIMATE
== rSearchOpt
.AlgorithmType2
)
775 pScriptIter
.reset(new SwScriptIterator(sCleanStr
, nStart
.GetAnyIndex(), bSrchForward
));
776 nSearchScript
= g_pBreakIt
->GetRealScriptOfText( rSearchOpt
.searchString
, 0 );
779 const AmbiguousIndex nStringEnd
= nEnd
;
780 bool bZeroMatch
= false; // zero-length match, i.e. only $ anchor as regex
781 while ( ((bSrchForward
&& nStart
< nStringEnd
) ||
782 (!bSrchForward
&& nStringEnd
< nStart
)) && !bZeroMatch
)
784 // SearchAlgorithms_APPROXIMATE works on a per word base so we have to
785 // provide the text searcher with the correct locale, because it uses
786 // the break-iterator
789 nEnd
.GetAnyIndex() = pScriptIter
->GetScriptChgPos();
790 nCurrScript
= pScriptIter
->GetCurrScript();
791 if ( nSearchScript
== nCurrScript
)
793 const LanguageType eCurrLang
= pLayout
794 ? pFrame
->GetLangOfChar(bSrchForward
795 ? nStart
.GetFrameIndex()
796 : nEnd
.GetFrameIndex(),
798 : pNode
->GetLang(bSrchForward
799 ? nStart
.GetModelIndex()
800 : nEnd
.GetModelIndex());
802 if ( eCurrLang
!= eLastLang
)
804 const lang::Locale
aLocale(
805 g_pBreakIt
->GetLocale( eCurrLang
) );
806 rSText
.SetLocale( utl::TextSearch::UpgradeToSearchOptions2( rSearchOpt
), aLocale
);
807 eLastLang
= eCurrLang
;
812 AmbiguousIndex nProxyStart
= nStart
;
813 AmbiguousIndex nProxyEnd
= nEnd
;
814 if( nSearchScript
== nCurrScript
&&
815 (rSText
.*fnMove
.fnSearch
)( sCleanStr
, &nProxyStart
.GetAnyIndex(), &nProxyEnd
.GetAnyIndex(), nullptr) &&
816 !(bZeroMatch
= (nProxyStart
== nProxyEnd
)))
818 nStart
= nProxyStart
;
820 // set section correctly
821 *rSearchPam
.GetPoint() = *pPam
->GetPoint();
822 rSearchPam
.SetMark();
824 // adjust start and end
825 if( !aFltArr
.empty() )
827 // if backward search, switch positions temporarily
828 if (!bSrchForward
) { std::swap(nStart
, nEnd
); }
830 AmbiguousIndex nNew
= nStart
;
831 for (size_t n
= 0; n
< aFltArr
.size() && aFltArr
[ n
] <= nStart
; ++n
)
833 ++nNew
.GetAnyIndex();
838 for( size_t n
= 0; n
< aFltArr
.size() && aFltArr
[ n
] < nEnd
; ++n
)
840 ++nNew
.GetAnyIndex();
844 // if backward search, switch positions temporarily
845 if( !bSrchForward
) { std::swap(nStart
, nEnd
); }
849 *rSearchPam
.GetMark() = pFrame
->MapViewToModelPos(nStart
.GetFrameIndex());
850 *rSearchPam
.GetPoint() = pFrame
->MapViewToModelPos(nEnd
.GetFrameIndex());
854 rSearchPam
.GetMark()->SetContent( nStart
.GetModelIndex() );
855 rSearchPam
.GetPoint()->SetContent( nEnd
.GetModelIndex() );
858 // if backward search, switch point and mark
860 rSearchPam
.Exchange();
875 else if ((bChkEmptyPara
&& !nStart
.GetAnyIndex() && !nTextLen
.GetAnyIndex())
878 *rSearchPam
.GetPoint() = *pPam
->GetPoint();
881 *rSearchPam
.GetPoint() = pFrame
->MapViewToModelPos(
882 bChkParaEnd
? nTextLen
.GetFrameIndex() : TextFrameIndex(0));
886 rSearchPam
.GetPoint()->SetContent( bChkParaEnd
? nTextLen
.GetModelIndex() : 0 );
888 rSearchPam
.SetMark();
889 const SwNode
*const pSttNd
= bSrchForward
890 ? &rSearchPam
.GetPoint()->GetNode() // end of the frame
891 : &rPtPos
.GetNode(); // keep the bug as-is for now...
892 /* FIXME: this condition does not work for !bSrchForward backward
893 * search, it probably never did. (pSttNd != &rNdIdx.GetNode())
894 * is never true in this case. */
895 if( (bSrchForward
|| pSttNd
!= &rPtPos
.GetNode()) &&
896 rSearchPam
.Move(fnMoveForward
, GoInContent
) &&
897 (!bSrchForward
|| pSttNd
!= &rSearchPam
.GetPoint()->GetNode()) &&
898 SwNodeOffset(1) == abs(rSearchPam
.GetPoint()->GetNodeIndex() -
899 rSearchPam
.GetMark()->GetNodeIndex()))
901 // if backward search, switch point and mark
903 rSearchPam
.Exchange();
912 /// parameters for search and replace in text
913 struct SwFindParaText
: public SwFindParas
915 const i18nutil::SearchOptions2
& m_rSearchOpt
;
917 SwRootFrame
const* m_pLayout
;
918 utl::TextSearch m_aSText
;
920 bool m_bSearchInNotes
;
922 SwFindParaText(const i18nutil::SearchOptions2
& rOpt
, bool bSearchInNotes
,
923 bool bRepl
, SwCursor
& rCursor
, SwRootFrame
const*const pLayout
)
924 : m_rSearchOpt( rOpt
)
925 , m_rCursor( rCursor
)
927 , m_aSText( utl::TextSearch::UpgradeToSearchOptions2(rOpt
) )
928 , m_bReplace( bRepl
)
929 , m_bSearchInNotes( bSearchInNotes
)
931 virtual int DoFind(SwPaM
&, SwMoveFnCollection
const &, const SwPaM
&, bool bInReadOnly
, std::unique_ptr
<SvxSearchItem
>& xSearchItem
) override
;
932 virtual bool IsReplaceMode() const override
;
933 virtual ~SwFindParaText();
938 SwFindParaText::~SwFindParaText()
942 int SwFindParaText::DoFind(SwPaM
& rCursor
, SwMoveFnCollection
const & fnMove
,
943 const SwPaM
& rRegion
, bool bInReadOnly
,
944 std::unique_ptr
<SvxSearchItem
>& xSearchItem
)
946 if( bInReadOnly
&& m_bReplace
)
949 const bool bFnd
= sw::FindTextImpl(rCursor
, m_rSearchOpt
, m_bSearchInNotes
,
950 m_aSText
, fnMove
, rRegion
, bInReadOnly
, m_pLayout
, xSearchItem
);
952 if( bFnd
&& m_bReplace
) // replace string
954 // use replace method in SwDoc
955 const bool bRegExp(SearchAlgorithms2::REGEXP
== m_rSearchOpt
.AlgorithmType2
);
956 const sal_Int32 nSttCnt
= rCursor
.Start()->GetContentIndex();
957 // add to shell-cursor-ring so that the regions will be moved eventually
958 SwPaM
* pPrev(nullptr);
961 pPrev
= const_cast<SwPaM
&>(rRegion
).GetPrev();
962 const_cast<SwPaM
&>(rRegion
).GetRingContainer().merge( m_rCursor
.GetRingContainer() );
965 std::optional
<OUString
> xRepl
;
967 xRepl
= sw::ReplaceBackReferences(m_rSearchOpt
, &rCursor
, m_pLayout
);
968 bool const bReplaced
= sw::ReplaceImpl(rCursor
,
969 xRepl
? *xRepl
: m_rSearchOpt
.replaceString
,
970 bRegExp
, m_rCursor
.GetDoc(), m_pLayout
);
972 m_rCursor
.SaveTableBoxContent( rCursor
.GetPoint() );
976 // and remove region again
978 SwPaM
* pNext(const_cast<SwPaM
*>(&rRegion
));
981 pNext
= p
->GetNext();
982 p
->MoveTo(const_cast<SwPaM
*>(&rRegion
));
983 } while( p
!= pPrev
);
985 if (bRegExp
&& !bReplaced
)
986 { // fdo#80715 avoid infinite loop if join failed
987 bool bRet
= ((&fnMoveForward
== &fnMove
) ? &GoNextPara
: &GoPrevPara
)
990 assert(bRet
); // if join failed, next node must be SwTextNode
993 rCursor
.Start()->SetContent(nSttCnt
);
996 return bFnd
? FIND_FOUND
: FIND_NOT_FOUND
;
999 bool SwFindParaText::IsReplaceMode() const
1004 sal_Int32
SwCursor::Find_Text( const i18nutil::SearchOptions2
& rSearchOpt
, bool bSearchInNotes
,
1005 SwDocPositions nStart
, SwDocPositions nEnd
,
1006 bool& bCancel
, FindRanges eFndRngs
, bool bReplace
,
1007 SwRootFrame
const*const pLayout
)
1009 // switch off OLE-notifications
1010 SwDoc
& rDoc
= GetDoc();
1011 Link
<bool,void> aLnk( rDoc
.GetOle2Link() );
1012 rDoc
.SetOle2Link( Link
<bool,void>() );
1014 bool const bStartUndo
= rDoc
.GetIDocumentUndoRedo().DoesUndo() && bReplace
;
1017 rDoc
.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE
, nullptr );
1020 bool bSearchSel
= 0 != (rSearchOpt
.searchFlag
& SearchFlags::REG_NOT_BEGINOFLINE
);
1022 eFndRngs
= static_cast<FindRanges
>(eFndRngs
| FindRanges::InSel
);
1023 SwFindParaText
aSwFindParaText(rSearchOpt
, bSearchInNotes
, bReplace
, *this, pLayout
);
1024 sal_Int32 nRet
= FindAll( aSwFindParaText
, nStart
, nEnd
, eFndRngs
, bCancel
);
1025 rDoc
.SetOle2Link( aLnk
);
1026 if( nRet
&& bReplace
)
1027 rDoc
.getIDocumentState().SetModified();
1031 SwRewriter
rewriter(MakeUndoReplaceRewriter(
1032 nRet
, rSearchOpt
.searchString
, rSearchOpt
.replaceString
));
1033 rDoc
.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE
, & rewriter
);
1042 OUString
const& rReplacement
,
1045 SwRootFrame
const*const pLayout
)
1047 bool bReplaced(true);
1048 IDocumentContentOperations
& rIDCO(rDoc
.getIDocumentContentOperations());
1050 // FIXME there's some problem with multiple redlines here on Undo
1051 std::vector
<std::shared_ptr
<SwUnoCursor
>> ranges
;
1052 if (rDoc
.getIDocumentRedlineAccess().IsRedlineOn()
1054 || !pLayout
->IsHideRedlines()
1055 || sw::GetRanges(ranges
, rDoc
, rCursor
))
1057 bReplaced
= rIDCO
.ReplaceRange(rCursor
, rReplacement
, bRegExp
);
1061 assert(!ranges
.empty());
1062 assert(ranges
.front()->GetPoint()->GetNode() == ranges
.front()->GetMark()->GetNode());
1063 bReplaced
= rIDCO
.ReplaceRange(*ranges
.front(), rReplacement
, bRegExp
);
1064 for (auto it
= ranges
.begin() + 1; it
!= ranges
.end(); ++it
)
1066 bReplaced
&= rIDCO
.DeleteAndJoin(**it
);
1070 IDocumentRedlineAccess
const& rIDRA(rDoc
.getIDocumentRedlineAccess());
1071 if (pLayout
&& pLayout
->IsHideRedlines()
1072 && !rIDRA
.IsRedlineOn() // otherwise: ReplaceRange will handle it
1073 && (rIDRA
.GetRedlineFlags() & RedlineFlags::ShowDelete
)) // otherwise: ReplaceRange will DeleteRedline()
1075 SwRedlineTable::size_type tmp
;
1076 rIDRA
.GetRedline(*rCursor
.Start(), &tmp
);
1077 while (tmp
< rIDRA
.GetRedlineTable().size())
1079 SwRangeRedline
const*const pRedline(rIDRA
.GetRedlineTable()[tmp
]);
1080 if (*rCursor
.End() <= *pRedline
->Start())
1084 if (*pRedline
->End() <= *rCursor
.Start())
1089 if (pRedline
->GetType() == RedlineType::Delete
)
1091 assert(*pRedline
->Start() != *pRedline
->End());
1092 // search in hidden layout can't overlap redlines
1093 assert(*rCursor
.Start() <= *pRedline
->Start() && *pRedline
->End() <= *rCursor
.End());
1094 SwPaM
pam(*pRedline
, nullptr);
1095 bReplaced
&= rIDCO
.DeleteAndJoin(pam
);
1103 bReplaced
&= rIDCO
.ReplaceRange(rCursor
, rReplacement
, bRegExp
);
1108 std::optional
<OUString
> ReplaceBackReferences(const i18nutil::SearchOptions2
& rSearchOpt
,
1109 SwPaM
*const pPam
, SwRootFrame
const*const pLayout
)
1111 std::optional
<OUString
> xRet
;
1112 if( pPam
&& pPam
->HasMark() &&
1113 SearchAlgorithms2::REGEXP
== rSearchOpt
.AlgorithmType2
)
1115 SwContentNode
const*const pTextNode
= pPam
->GetPointContentNode();
1116 SwContentNode
const*const pMarkTextNode
= pPam
->GetMarkContentNode();
1117 if (!pTextNode
|| !pTextNode
->IsTextNode()
1118 || !pMarkTextNode
|| !pMarkTextNode
->IsTextNode())
1122 SwTextFrame
const*const pFrame(pLayout
1123 ? static_cast<SwTextFrame
const*>(pTextNode
->getLayoutFrame(pLayout
))
1125 const bool bParaEnd
= rSearchOpt
.searchString
== "$" || rSearchOpt
.searchString
== "^$" || rSearchOpt
.searchString
== "$^";
1126 if (bParaEnd
|| (pLayout
1127 ? sw::FrameContainsNode(*pFrame
, pPam
->GetMark()->GetNodeIndex())
1128 : pTextNode
== pMarkTextNode
))
1130 utl::TextSearch
aSText( utl::TextSearch::UpgradeToSearchOptions2( rSearchOpt
) );
1131 SearchResult aResult
;
1132 OUString
aReplaceStr( rSearchOpt
.replaceString
);
1135 OUString
const aStr("\\n");
1136 aResult
.subRegExpressions
= 1;
1137 aResult
.startOffset
= { 0 };
1138 aResult
.endOffset
= { aStr
.getLength() };
1139 aSText
.ReplaceBackReferences( aReplaceStr
, aStr
, aResult
);
1144 AmbiguousIndex nStart
;
1145 AmbiguousIndex nEnd
;
1148 nStart
.SetFrameIndex(pFrame
->MapModelToViewPos(*pPam
->Start()));
1149 nEnd
.SetFrameIndex(pFrame
->MapModelToViewPos(*pPam
->End()));
1153 nStart
.SetModelIndex(pPam
->Start()->GetContentIndex());
1154 nEnd
.SetModelIndex(pPam
->End()->GetContentIndex());
1156 std::vector
<AmbiguousIndex
> aFltArr
;
1157 OUString
const aStr
= lcl_CleanStr(*pTextNode
->GetTextNode(), pFrame
, pLayout
,
1158 nStart
, nEnd
, aFltArr
, false, false);
1159 if (aSText
.SearchForward(aStr
, &nStart
.GetAnyIndex(), &nEnd
.GetAnyIndex(), &aResult
))
1161 aSText
.ReplaceBackReferences( aReplaceStr
, aStr
, aResult
);
1172 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */