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
& rPam
);
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())
409 const SwTextNode
& rTextNode
= *pNode
->GetTextNode();
410 SwTextFrame
const* const pFrame(
411 pLayout
? static_cast<SwTextFrame
const*>(rTextNode
.getLayoutFrame(pLayout
)) : nullptr);
412 assert(!pLayout
|| pFrame
);
413 AmbiguousIndex nTextLen
;
416 nTextLen
.SetFrameIndex(TextFrameIndex(pFrame
->GetText().getLength()));
420 nTextLen
.SetModelIndex(rTextNode
.GetText().getLength());
423 if (pLayout
? FrameContainsNode(*pFrame
, oPam
->GetMark()->GetNodeIndex())
424 : rPtPos
.GetNode() == oPam
->GetMark()->GetNode())
428 nEnd
.SetFrameIndex(pFrame
->MapModelToViewPos(*oPam
->GetMark()));
432 nEnd
.SetModelIndex(oPam
->GetMark()->GetContentIndex());
445 nEnd
.SetFrameIndex(TextFrameIndex(0));
449 nEnd
.SetModelIndex(0);
453 AmbiguousIndex nStart
;
456 nStart
.SetFrameIndex(pFrame
->MapModelToViewPos(*oPam
->GetPoint()));
460 nStart
.SetModelIndex(rPtPos
.GetContentIndex());
464 // if there are SwPostItFields inside our current node text, we
465 // split the text into separate pieces and search for text inside
466 // the pieces as well as inside the fields
467 MaybeMergedIter
iter(pLayout
? pFrame
: nullptr, pLayout
? nullptr : &rTextNode
);
469 // count PostItFields by looping over all fields
470 std::vector
<std::pair
<SwTextAttr
const*, AmbiguousIndex
>> postits
;
475 std::swap(nStart
, nEnd
);
478 SwTextNode
const* pTemp(nullptr);
479 while (SwTextAttr
const* const pTextAttr
= iter
.NextAttr(pTemp
))
481 if (pTextAttr
->Which() == RES_TXTATR_ANNOTATION
)
484 aPos
.SetModelIndex(pTextAttr
->GetStart());
487 aPos
.SetFrameIndex(pFrame
->MapModelToView(pTemp
, aPos
.GetModelIndex()));
489 if ((nStart
<= aPos
) && (aPos
<= nEnd
))
491 postits
.emplace_back(pTextAttr
, aPos
);
498 std::swap(nStart
, nEnd
);
502 SwDocShell
* const pDocShell
= pNode
->GetDoc().GetDocShell();
503 SwWrtShell
* const pWrtShell
= pDocShell
? pDocShell
->GetWrtShell() : nullptr;
504 SwPostItMgr
* const pPostItMgr
= pWrtShell
? pWrtShell
->GetPostItMgr() : nullptr;
506 // If there is an active text edit, then search there.
507 bool bEndedTextEdit
= false;
508 SdrView
* pSdrView
= pWrtShell
? pWrtShell
->GetDrawView() : nullptr;
511 // If the edited object is not anchored to this node, then ignore it.
512 SdrObject
* pObject
= pSdrView
->GetTextEditObject();
515 if (SwFrameFormat
* pFrameFormat
= FindFrameFormat(pObject
))
517 const SwNode
* pAnchorNode
= pFrameFormat
->GetAnchor().GetAnchorNode();
519 || (pLayout
? !FrameContainsNode(*pFrame
, pAnchorNode
->GetIndex())
520 : pAnchorNode
->GetIndex() != pNode
->GetIndex()))
528 = pSdrView
->GetTextEditOutlinerView()->StartSearchAndReplace(*xSearchItem
);
531 // If not found, end the text edit.
532 pSdrView
->SdrEndTextEdit();
533 const Point
aPoint(pSdrView
->GetAllMarkedRect().TopLeft());
534 pSdrView
->UnmarkAll();
535 pWrtShell
->CallSetCursor(&aPoint
, true);
537 bEndedTextEdit
= true;
547 if (comphelper::LibreOfficeKit::isActive())
549 // Writer and editeng selections are not supported in parallel.
550 SvxSearchItem
* pSearchItem
= SwView::GetSearchItem();
551 // If we just finished search in shape text, don't attempt to do that again.
553 && !(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(
569 (pFrame
->GetMergedPara() ? *pFrame
->GetMergedPara()->pLastNode
: rTextNode
)
575 aPaM
.GetMark()->Assign(rTextNode
.GetIndex() + 1);
577 if (pNode
->GetDoc().getIDocumentDrawModelAccess().Search(aPaM
, *xSearchItem
)
580 if (SdrObject
* pObject
= pSdrView
->GetTextEditObject())
582 if (SwFrameFormat
* pFrameFormat
= FindFrameFormat(pObject
))
584 const SwNode
* pAnchorNode
= pFrameFormat
->GetAnchor().GetAnchorNode();
587 // Set search position to the shape's anchor point.
588 rSearchPam
.GetPoint()->Assign(*pAnchorNode
);
589 rSearchPam
.SetMark();
599 // do we need to finish a note?
600 if (pPostItMgr
&& pPostItMgr
->HasActiveSidebarWin())
604 if (!postits
.empty())
608 postits
.erase(postits
.begin());
612 postits
.pop_back(); // hope that's the right one?
615 //search inside, finish and put focus back into the doc
616 if (pPostItMgr
->FinishSearchReplace(rSearchOpt
, bSrchForward
))
624 pPostItMgr
->SetActiveSidebarWin(nullptr);
628 if (!postits
.empty())
630 // now we have to split
631 AmbiguousIndex nStartInside
;
632 AmbiguousIndex nEndInside
;
633 sal_Int32 aLoop
= bSrchForward
? 0 : postits
.size();
635 while ((0 <= aLoop
) && (o3tl::make_unsigned(aLoop
) <= postits
.size()))
641 nStartInside
= nStart
;
645 nStartInside
.SetFrameIndex(postits
[aLoop
- 1].second
.GetFrameIndex()
646 + TextFrameIndex(1));
650 nStartInside
.SetModelIndex(postits
[aLoop
- 1].second
.GetModelIndex() + 1);
652 nEndInside
= static_cast<size_t>(aLoop
) == postits
.size()
654 : postits
[aLoop
].second
;
655 nTextLen
= nEndInside
- nStartInside
;
659 nStartInside
= static_cast<size_t>(aLoop
) == postits
.size()
661 : postits
[aLoop
].second
;
668 nEndInside
.SetFrameIndex(postits
[aLoop
- 1].second
.GetFrameIndex()
669 + TextFrameIndex(1));
673 nEndInside
.SetModelIndex(postits
[aLoop
- 1].second
.GetModelIndex() + 1);
675 nTextLen
= nStartInside
- nEndInside
;
677 // search inside the text between a note
678 bFound
= DoSearch(rSearchPam
, rSearchOpt
, rSText
, fnMove
, bSrchForward
, bRegSearch
,
679 bChkEmptyPara
, bChkParaEnd
, nStartInside
, nEndInside
, nTextLen
,
680 pNode
->GetTextNode(), pFrame
, pLayout
, *oPam
);
685 // we should now be right in front of a note, search inside
686 if (bSrchForward
? (static_cast<size_t>(aLoop
) != postits
.size())
689 const SwTextAttr
* const pTextAttr
690 = bSrchForward
? postits
[aLoop
].first
: postits
[aLoop
- 1].first
;
692 && pPostItMgr
->SearchReplace(
693 static_txtattr_cast
<SwTextField
const*>(pTextAttr
)
695 rSearchOpt
, bSrchForward
))
702 aLoop
= bSrchForward
? aLoop
+ 1 : aLoop
- 1;
707 // if there is no SwPostItField inside or searching inside notes
708 // is disabled, we search the whole length just like before
709 bFound
= DoSearch(rSearchPam
, rSearchOpt
, rSText
, fnMove
, bSrchForward
, bRegSearch
,
710 bChkEmptyPara
, bChkParaEnd
, nStart
, nEnd
, nTextLen
,
711 pNode
->GetTextNode(), pFrame
, pLayout
, *oPam
);
721 bool DoSearch(SwPaM
& rSearchPam
,
722 const i18nutil::SearchOptions2
& rSearchOpt
, utl::TextSearch
& rSText
,
723 SwMoveFnCollection
const & fnMove
, bool bSrchForward
, bool bRegSearch
,
724 bool bChkEmptyPara
, bool bChkParaEnd
,
725 AmbiguousIndex
& nStart
, AmbiguousIndex
& nEnd
, AmbiguousIndex
const nTextLen
,
726 SwTextNode
const*const pNode
, SwTextFrame
const*const pFrame
,
727 SwRootFrame
const*const pLayout
, SwPaM
& rPam
)
729 if (bRegSearch
&& rSearchOpt
.searchString
.endsWith("$"))
731 bool bAlwaysSearchingForEndOfPara
= true;
732 sal_Int32 nIndex
= 0;
733 while ((nIndex
= rSearchOpt
.searchString
.indexOf("|", nIndex
)) != -1)
735 if (!nIndex
|| rSearchOpt
.searchString
[nIndex
- 1] != '$')
737 bAlwaysSearchingForEndOfPara
= false;
742 // when searching for something at the end of the paragraph, the para end must be in range
743 const AmbiguousIndex
& rParaEnd
= bSrchForward
? nEnd
: nStart
;
744 if (bAlwaysSearchingForEndOfPara
&& nTextLen
.GetAnyIndex() != rParaEnd
.GetAnyIndex())
750 std::vector
<AmbiguousIndex
> aFltArr
;
751 LanguageType eLastLang
= LANGUAGE_SYSTEM
;
752 // if the search string contains a soft hyphen,
753 // we don't strip them from the text:
754 bool bRemoveSoftHyphens
= true;
755 // if the search string contains a comment, we don't strip them from the text
756 const bool bRemoveCommentAnchors
= rSearchOpt
.searchString
.indexOf( CH_TXTATR_INWORD
) == -1;
760 if ( -1 != rSearchOpt
.searchString
.indexOf("\\xAD")
761 || -1 != rSearchOpt
.searchString
.indexOf("\\x{00AD}")
762 || -1 != rSearchOpt
.searchString
.indexOf("\\u00AD")
763 || -1 != rSearchOpt
.searchString
.indexOf("\\u00ad")
764 || -1 != rSearchOpt
.searchString
.indexOf("\\U000000AD")
765 || -1 != rSearchOpt
.searchString
.indexOf("\\N{SOFT HYPHEN}"))
767 bRemoveSoftHyphens
= false;
772 if ( 1 == rSearchOpt
.searchString
.getLength() &&
773 CHAR_SOFTHYPHEN
== rSearchOpt
.searchString
.toChar() )
774 bRemoveSoftHyphens
= false;
778 sCleanStr
= lcl_CleanStr(*pNode
, pFrame
, pLayout
, nStart
, nEnd
,
779 aFltArr
, bRemoveSoftHyphens
, bRemoveCommentAnchors
);
781 sCleanStr
= lcl_CleanStr(*pNode
, pFrame
, pLayout
, nEnd
, nStart
,
782 aFltArr
, bRemoveSoftHyphens
, bRemoveCommentAnchors
);
784 std::unique_ptr
<SwScriptIterator
> pScriptIter
;
785 sal_uInt16 nSearchScript
= 0;
786 sal_uInt16 nCurrScript
= 0;
788 if (SearchAlgorithms2::APPROXIMATE
== rSearchOpt
.AlgorithmType2
)
790 pScriptIter
.reset(new SwScriptIterator(sCleanStr
, nStart
.GetAnyIndex(), bSrchForward
));
791 nSearchScript
= g_pBreakIt
->GetRealScriptOfText( rSearchOpt
.searchString
, 0 );
794 const AmbiguousIndex nStringEnd
= nEnd
;
795 bool bZeroMatch
= false; // zero-length match, i.e. only $ anchor as regex
796 while ( ((bSrchForward
&& nStart
< nStringEnd
) ||
797 (!bSrchForward
&& nStringEnd
< nStart
)) && !bZeroMatch
)
799 // SearchAlgorithms_APPROXIMATE works on a per word base so we have to
800 // provide the text searcher with the correct locale, because it uses
801 // the break-iterator
804 nEnd
.GetAnyIndex() = pScriptIter
->GetScriptChgPos();
805 nCurrScript
= pScriptIter
->GetCurrScript();
806 if ( nSearchScript
== nCurrScript
)
808 const LanguageType eCurrLang
= pLayout
809 ? pFrame
->GetLangOfChar(bSrchForward
810 ? nStart
.GetFrameIndex()
811 : nEnd
.GetFrameIndex(),
813 : pNode
->GetLang(bSrchForward
814 ? nStart
.GetModelIndex()
815 : nEnd
.GetModelIndex());
817 if ( eCurrLang
!= eLastLang
)
819 const lang::Locale
aLocale(
820 g_pBreakIt
->GetLocale( eCurrLang
) );
821 rSText
.SetLocale(rSearchOpt
, aLocale
);
822 eLastLang
= eCurrLang
;
827 AmbiguousIndex nProxyStart
= nStart
;
828 AmbiguousIndex nProxyEnd
= nEnd
;
829 if( nSearchScript
== nCurrScript
&&
830 (rSText
.*fnMove
.fnSearch
)( sCleanStr
, &nProxyStart
.GetAnyIndex(), &nProxyEnd
.GetAnyIndex(), nullptr) &&
831 !(bZeroMatch
= (nProxyStart
== nProxyEnd
)))
833 nStart
= nProxyStart
;
835 // set section correctly
836 *rSearchPam
.GetPoint() = *rPam
.GetPoint();
837 rSearchPam
.SetMark();
839 // adjust start and end
840 if( !aFltArr
.empty() )
842 // if backward search, switch positions temporarily
843 if (!bSrchForward
) { std::swap(nStart
, nEnd
); }
845 AmbiguousIndex nNew
= nStart
;
846 for (size_t n
= 0; n
< aFltArr
.size() && aFltArr
[ n
] <= nStart
; ++n
)
848 ++nNew
.GetAnyIndex();
853 for( size_t n
= 0; n
< aFltArr
.size() && aFltArr
[ n
] < nEnd
; ++n
)
855 ++nNew
.GetAnyIndex();
859 // if backward search, switch positions temporarily
860 if( !bSrchForward
) { std::swap(nStart
, nEnd
); }
864 *rSearchPam
.GetMark() = pFrame
->MapViewToModelPos(nStart
.GetFrameIndex());
865 *rSearchPam
.GetPoint() = pFrame
->MapViewToModelPos(nEnd
.GetFrameIndex());
869 rSearchPam
.GetMark()->SetContent( nStart
.GetModelIndex() );
870 rSearchPam
.GetPoint()->SetContent( nEnd
.GetModelIndex() );
873 // if backward search, switch point and mark
875 rSearchPam
.Exchange();
891 if (!bChkEmptyPara
&& !bChkParaEnd
)
894 if (bChkEmptyPara
&& bSrchForward
&& nTextLen
.GetAnyIndex())
895 return false; // the length is not zero - there is content here
897 // move to the end (or start) of the paragraph
898 *rSearchPam
.GetPoint() = *rPam
.GetPoint();
901 *rSearchPam
.GetPoint() = pFrame
->MapViewToModelPos(
902 bSrchForward
? nTextLen
.GetFrameIndex() : TextFrameIndex(0));
906 rSearchPam
.GetPoint()->SetContent(bSrchForward
? nTextLen
.GetModelIndex() : 0);
908 rSearchPam
.SetMark();
910 if (!rSearchPam
.Move(fnMove
, GoInContent
))
911 return false; // at start or end of the document
913 // selection must not be outside of the search area
914 if (!rPam
.ContainsPosition(*rSearchPam
.GetPoint()))
917 if (SwNodeOffset(1) == abs(rSearchPam
.GetPoint()->GetNodeIndex() -
918 rSearchPam
.GetMark()->GetNodeIndex()))
920 if (bChkEmptyPara
&& !bSrchForward
&& rSearchPam
.GetPoint()->GetContentIndex())
921 return false; // the length is not zero - there is content here
931 /// parameters for search and replace in text
932 struct SwFindParaText
: public SwFindParas
934 const i18nutil::SearchOptions2
& m_rSearchOpt
;
936 SwRootFrame
const* m_pLayout
;
937 utl::TextSearch m_aSText
;
939 bool m_bSearchInNotes
;
941 SwFindParaText(const i18nutil::SearchOptions2
& rOpt
, bool bSearchInNotes
,
942 bool bRepl
, SwCursor
& rCursor
, SwRootFrame
const*const pLayout
)
943 : m_rSearchOpt( rOpt
)
944 , m_rCursor( rCursor
)
947 , m_bReplace( bRepl
)
948 , m_bSearchInNotes( bSearchInNotes
)
950 virtual int DoFind(SwPaM
&, SwMoveFnCollection
const &, const SwPaM
&, bool bInReadOnly
, std::unique_ptr
<SvxSearchItem
>& xSearchItem
) override
;
951 virtual bool IsReplaceMode() const override
;
952 virtual ~SwFindParaText();
957 SwFindParaText::~SwFindParaText()
961 int SwFindParaText::DoFind(SwPaM
& rCursor
, SwMoveFnCollection
const & fnMove
,
962 const SwPaM
& rRegion
, bool bInReadOnly
,
963 std::unique_ptr
<SvxSearchItem
>& xSearchItem
)
965 if( bInReadOnly
&& m_bReplace
)
968 const bool bFnd
= sw::FindTextImpl(rCursor
, m_rSearchOpt
, m_bSearchInNotes
,
969 m_aSText
, fnMove
, rRegion
, bInReadOnly
, m_pLayout
, xSearchItem
);
971 if( bFnd
&& m_bReplace
) // replace string
973 // use replace method in SwDoc
974 const bool bRegExp(SearchAlgorithms2::REGEXP
== m_rSearchOpt
.AlgorithmType2
);
975 const sal_Int32 nSttCnt
= rCursor
.Start()->GetContentIndex();
976 // add to shell-cursor-ring so that the regions will be moved eventually
977 SwPaM
* pPrev(nullptr);
980 pPrev
= const_cast<SwPaM
&>(rRegion
).GetPrev();
981 const_cast<SwPaM
&>(rRegion
).GetRingContainer().merge( m_rCursor
.GetRingContainer() );
984 std::optional
<OUString
> xRepl
;
986 xRepl
= sw::ReplaceBackReferences(m_rSearchOpt
, &rCursor
, m_pLayout
);
987 bool const bReplaced
= sw::ReplaceImpl(rCursor
,
988 xRepl
? *xRepl
: m_rSearchOpt
.replaceString
,
989 bRegExp
, m_rCursor
.GetDoc(), m_pLayout
);
991 m_rCursor
.SaveTableBoxContent( rCursor
.GetPoint() );
995 // and remove region again
997 SwPaM
* pNext(const_cast<SwPaM
*>(&rRegion
));
1000 pNext
= p
->GetNext();
1001 p
->MoveTo(const_cast<SwPaM
*>(&rRegion
));
1002 } while( p
!= pPrev
);
1004 if (bRegExp
&& !bReplaced
)
1005 { // fdo#80715 avoid infinite loop if join failed
1006 bool bRet
= ((&fnMoveForward
== &fnMove
) ? &GoNextPara
: &GoPrevPara
)
1009 assert(bRet
); // if join failed, next node must be SwTextNode
1012 rCursor
.Start()->SetContent(nSttCnt
);
1013 return FIND_NO_RING
;
1015 return bFnd
? FIND_FOUND
: FIND_NOT_FOUND
;
1018 bool SwFindParaText::IsReplaceMode() const
1023 sal_Int32
SwCursor::Find_Text( const i18nutil::SearchOptions2
& rSearchOpt
, bool bSearchInNotes
,
1024 SwDocPositions nStart
, SwDocPositions nEnd
,
1025 bool& bCancel
, FindRanges eFndRngs
, bool bReplace
,
1026 SwRootFrame
const*const pLayout
)
1028 // switch off OLE-notifications
1029 SwDoc
& rDoc
= GetDoc();
1030 Link
<bool,void> aLnk( rDoc
.GetOle2Link() );
1031 rDoc
.SetOle2Link( Link
<bool,void>() );
1033 bool const bStartUndo
= rDoc
.GetIDocumentUndoRedo().DoesUndo() && bReplace
;
1036 rDoc
.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE
, nullptr );
1039 bool bSearchSel
= 0 != (rSearchOpt
.searchFlag
& SearchFlags::REG_NOT_BEGINOFLINE
);
1041 eFndRngs
= static_cast<FindRanges
>(eFndRngs
| FindRanges::InSel
);
1042 SwFindParaText
aSwFindParaText(rSearchOpt
, bSearchInNotes
, bReplace
, *this, pLayout
);
1043 sal_Int32 nRet
= FindAll( aSwFindParaText
, nStart
, nEnd
, eFndRngs
, bCancel
);
1044 rDoc
.SetOle2Link( aLnk
);
1045 if( nRet
&& bReplace
)
1046 rDoc
.getIDocumentState().SetModified();
1050 SwRewriter
rewriter(MakeUndoReplaceRewriter(
1051 nRet
, rSearchOpt
.searchString
, rSearchOpt
.replaceString
));
1052 rDoc
.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE
, & rewriter
);
1061 OUString
const& rReplacement
,
1064 SwRootFrame
const*const pLayout
)
1066 bool bReplaced(true);
1067 IDocumentContentOperations
& rIDCO(rDoc
.getIDocumentContentOperations());
1069 // FIXME there's some problem with multiple redlines here on Undo
1070 std::vector
<std::shared_ptr
<SwUnoCursor
>> ranges
;
1071 if (rDoc
.getIDocumentRedlineAccess().IsRedlineOn()
1073 || !pLayout
->IsHideRedlines()
1074 || sw::GetRanges(ranges
, rDoc
, rCursor
))
1076 bReplaced
= rIDCO
.ReplaceRange(rCursor
, rReplacement
, bRegExp
);
1080 assert(!ranges
.empty());
1081 assert(ranges
.front()->GetPoint()->GetNode() == ranges
.front()->GetMark()->GetNode());
1082 bReplaced
= rIDCO
.ReplaceRange(*ranges
.front(), rReplacement
, bRegExp
);
1083 for (auto it
= ranges
.begin() + 1; it
!= ranges
.end(); ++it
)
1085 bReplaced
&= rIDCO
.DeleteAndJoin(**it
);
1089 IDocumentRedlineAccess
const& rIDRA(rDoc
.getIDocumentRedlineAccess());
1090 if (pLayout
&& pLayout
->IsHideRedlines()
1091 && !rIDRA
.IsRedlineOn() // otherwise: ReplaceRange will handle it
1092 && (rIDRA
.GetRedlineFlags() & RedlineFlags::ShowDelete
)) // otherwise: ReplaceRange will DeleteRedline()
1094 SwRedlineTable::size_type tmp
;
1095 rIDRA
.GetRedline(*rCursor
.Start(), &tmp
);
1096 while (tmp
< rIDRA
.GetRedlineTable().size())
1098 SwRangeRedline
const*const pRedline(rIDRA
.GetRedlineTable()[tmp
]);
1099 if (*rCursor
.End() <= *pRedline
->Start())
1103 if (*pRedline
->End() <= *rCursor
.Start())
1108 if (pRedline
->GetType() == RedlineType::Delete
)
1110 assert(*pRedline
->Start() != *pRedline
->End());
1111 // search in hidden layout can't overlap redlines
1112 assert(*rCursor
.Start() <= *pRedline
->Start() && *pRedline
->End() <= *rCursor
.End());
1113 SwPaM
pam(*pRedline
, nullptr);
1114 bReplaced
&= rIDCO
.DeleteAndJoin(pam
);
1122 bReplaced
&= rIDCO
.ReplaceRange(rCursor
, rReplacement
, bRegExp
);
1127 std::optional
<OUString
> ReplaceBackReferences(const i18nutil::SearchOptions2
& rSearchOpt
,
1128 SwPaM
*const pPam
, SwRootFrame
const*const pLayout
)
1130 std::optional
<OUString
> xRet
;
1131 if( pPam
&& pPam
->HasMark() &&
1132 SearchAlgorithms2::REGEXP
== rSearchOpt
.AlgorithmType2
)
1134 SwContentNode
const*const pTextNode
= pPam
->GetPointContentNode();
1135 SwContentNode
const*const pMarkTextNode
= pPam
->GetMarkContentNode();
1136 if (!pTextNode
|| !pTextNode
->IsTextNode()
1137 || !pMarkTextNode
|| !pMarkTextNode
->IsTextNode())
1141 SwTextFrame
const*const pFrame(pLayout
1142 ? static_cast<SwTextFrame
const*>(pTextNode
->getLayoutFrame(pLayout
))
1144 const bool bParaEnd
= rSearchOpt
.searchString
== "$" || rSearchOpt
.searchString
== "^$" || rSearchOpt
.searchString
== "$^";
1145 if (bParaEnd
|| (pLayout
1146 ? sw::FrameContainsNode(*pFrame
, pPam
->GetMark()->GetNodeIndex())
1147 : pTextNode
== pMarkTextNode
))
1149 utl::TextSearch
aSText(rSearchOpt
);
1150 SearchResult aResult
;
1151 OUString
aReplaceStr( rSearchOpt
.replaceString
);
1154 static constexpr OUString
aStr(u
"\\n"_ustr
);
1155 aResult
.subRegExpressions
= 1;
1156 aResult
.startOffset
= { 0 };
1157 aResult
.endOffset
= { aStr
.getLength() };
1158 utl::TextSearch::ReplaceBackReferences( aReplaceStr
, aStr
, aResult
);
1163 AmbiguousIndex nStart
;
1164 AmbiguousIndex nEnd
;
1167 nStart
.SetFrameIndex(pFrame
->MapModelToViewPos(*pPam
->Start()));
1168 nEnd
.SetFrameIndex(pFrame
->MapModelToViewPos(*pPam
->End()));
1172 nStart
.SetModelIndex(pPam
->Start()->GetContentIndex());
1173 nEnd
.SetModelIndex(pPam
->End()->GetContentIndex());
1175 std::vector
<AmbiguousIndex
> aFltArr
;
1176 OUString
const aStr
= lcl_CleanStr(*pTextNode
->GetTextNode(), pFrame
, pLayout
,
1177 nStart
, nEnd
, aFltArr
, false, false);
1178 if (aSText
.SearchForward(aStr
, &nStart
.GetAnyIndex(), &nEnd
.GetAnyIndex(), &aResult
))
1180 utl::TextSearch::ReplaceBackReferences( aReplaceStr
, aStr
, aResult
);
1191 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */