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::unique_ptr
<SwPaM
> pPam
= sw::MakeRegion(fnMove
, rRegion
);
380 const bool bSrchForward
= &fnMove
== &fnMoveForward
;
381 SwNodeIndex
& rNdIdx
= pPam
->GetPoint()->nNode
;
382 SwIndex
& rContentIdx
= pPam
->GetPoint()->nContent
;
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(*pPam
, 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
, pPam
->GetMark()->nNode
.GetIndex())
425 : rNdIdx
== pPam
->GetMark()->nNode
)
429 nEnd
.SetFrameIndex(pFrame
->MapModelToViewPos(*pPam
->GetMark()));
433 nEnd
.SetModelIndex(pPam
->GetMark()->nContent
.GetIndex());
446 nEnd
.SetFrameIndex(TextFrameIndex(0));
450 nEnd
.SetModelIndex(0);
454 AmbiguousIndex nStart
;
457 nStart
.SetFrameIndex(pFrame
->MapModelToViewPos(*pPam
->GetPoint()));
461 nStart
.SetModelIndex(rContentIdx
.GetIndex());
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 SwPosition
* pPosition
= pFrameFormat
->GetAnchor().GetContentAnchor();
520 if (!pPosition
|| (pLayout
521 ? !FrameContainsNode(*pFrame
, pPosition
->nNode
.GetIndex())
522 : pPosition
->nNode
.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()->nNode
= rTextNode
;
564 aPaM
.GetPoint()->nContent
.Assign(
565 aPaM
.GetPoint()->nNode
.GetNode().GetTextNode(),
566 nStart
.GetModelIndex());
571 aPaM
.GetMark()->nNode
= (pFrame
->GetMergedPara()
572 ? *pFrame
->GetMergedPara()->pLastNode
578 aPaM
.GetMark()->nNode
= rTextNode
.GetIndex() + 1;
580 aPaM
.GetMark()->nContent
.Assign(aPaM
.GetMark()->nNode
.GetNode().GetTextNode(), 0);
581 if (pNode
->GetDoc().getIDocumentDrawModelAccess().Search(aPaM
, *xSearchItem
) && pSdrView
)
583 if (SdrObject
* pObject
= pSdrView
->GetTextEditObject())
585 if (SwFrameFormat
* pFrameFormat
= FindFrameFormat(pObject
))
587 const SwPosition
* pPosition
= pFrameFormat
->GetAnchor().GetContentAnchor();
590 // Set search position to the shape's anchor point.
591 *rSearchPam
.GetPoint() = *pPosition
;
592 rSearchPam
.GetPoint()->nContent
.Assign(pPosition
->nNode
.GetNode().GetContentNode(), 0);
593 rSearchPam
.SetMark();
603 // do we need to finish a note?
604 if (pPostItMgr
&& pPostItMgr
->HasActiveSidebarWin())
608 if (!postits
.empty())
612 postits
.erase(postits
.begin());
616 postits
.pop_back(); // hope that's the right one?
619 //search inside, finish and put focus back into the doc
620 if (pPostItMgr
->FinishSearchReplace(rSearchOpt
,bSrchForward
))
628 pPostItMgr
->SetActiveSidebarWin(nullptr);
632 if (!postits
.empty())
634 // now we have to split
635 AmbiguousIndex nStartInside
;
636 AmbiguousIndex nEndInside
;
637 sal_Int32 aLoop
= bSrchForward
? 0 : postits
.size();
639 while ((0 <= aLoop
) && (o3tl::make_unsigned(aLoop
) <= postits
.size()))
645 nStartInside
= nStart
;
649 nStartInside
.SetFrameIndex(postits
[aLoop
- 1].second
.GetFrameIndex() + TextFrameIndex(1));
653 nStartInside
.SetModelIndex(postits
[aLoop
- 1].second
.GetModelIndex() + 1);
655 nEndInside
= static_cast<size_t>(aLoop
) == postits
.size()
657 : postits
[aLoop
].second
;
658 nTextLen
= nEndInside
- nStartInside
;
662 nStartInside
= static_cast<size_t>(aLoop
) == postits
.size()
664 : postits
[aLoop
].second
;
671 nEndInside
.SetFrameIndex(postits
[aLoop
- 1].second
.GetFrameIndex() + TextFrameIndex(1));
675 nEndInside
.SetModelIndex(postits
[aLoop
- 1].second
.GetModelIndex() + 1);
677 nTextLen
= nStartInside
- nEndInside
;
679 // search inside the text between a note
680 bFound
= DoSearch( rSearchPam
,
681 rSearchOpt
, rSText
, fnMove
, bSrchForward
,
682 bRegSearch
, bChkEmptyPara
, bChkParaEnd
,
683 nStartInside
, nEndInside
, nTextLen
,
684 pNode
->GetTextNode(), pFrame
, pLayout
,
690 // we should now be right in front of a note, search inside
692 ? (static_cast<size_t>(aLoop
) != postits
.size())
695 const SwTextAttr
*const pTextAttr
= bSrchForward
696 ? postits
[aLoop
].first
697 : postits
[aLoop
- 1].first
;
698 if (pPostItMgr
&& pPostItMgr
->SearchReplace(
699 static_txtattr_cast
<SwTextField
const*>(pTextAttr
)->GetFormatField(),rSearchOpt
,bSrchForward
))
706 aLoop
= bSrchForward
? aLoop
+1 : aLoop
-1;
711 // if there is no SwPostItField inside or searching inside notes
712 // is disabled, we search the whole length just like before
713 bFound
= DoSearch( rSearchPam
,
714 rSearchOpt
, rSText
, fnMove
, bSrchForward
,
715 bRegSearch
, bChkEmptyPara
, bChkParaEnd
,
716 nStart
, nEnd
, nTextLen
,
717 pNode
->GetTextNode(), pFrame
, pLayout
,
729 bool DoSearch(SwPaM
& rSearchPam
,
730 const i18nutil::SearchOptions2
& rSearchOpt
, utl::TextSearch
& rSText
,
731 SwMoveFnCollection
const & fnMove
, bool bSrchForward
, bool bRegSearch
,
732 bool bChkEmptyPara
, bool bChkParaEnd
,
733 AmbiguousIndex
& nStart
, AmbiguousIndex
& nEnd
, AmbiguousIndex
const nTextLen
,
734 SwTextNode
const*const pNode
, SwTextFrame
const*const pFrame
,
735 SwRootFrame
const*const pLayout
, SwPaM
* pPam
)
739 std::vector
<AmbiguousIndex
> aFltArr
;
740 LanguageType eLastLang
= LANGUAGE_SYSTEM
;
741 // if the search string contains a soft hyphen,
742 // we don't strip them from the text:
743 bool bRemoveSoftHyphens
= true;
744 // if the search string contains a comment, we don't strip them from the text
745 const bool bRemoveCommentAnchors
= rSearchOpt
.searchString
.indexOf( CH_TXTATR_INWORD
) == -1;
749 if ( -1 != rSearchOpt
.searchString
.indexOf("\\xAD")
750 || -1 != rSearchOpt
.searchString
.indexOf("\\x{00AD}")
751 || -1 != rSearchOpt
.searchString
.indexOf("\\u00AD")
752 || -1 != rSearchOpt
.searchString
.indexOf("\\U000000AD")
753 || -1 != rSearchOpt
.searchString
.indexOf("\\N{SOFT HYPHEN}"))
755 bRemoveSoftHyphens
= false;
760 if ( 1 == rSearchOpt
.searchString
.getLength() &&
761 CHAR_SOFTHYPHEN
== rSearchOpt
.searchString
.toChar() )
762 bRemoveSoftHyphens
= false;
766 sCleanStr
= lcl_CleanStr(*pNode
, pFrame
, pLayout
, nStart
, nEnd
,
767 aFltArr
, bRemoveSoftHyphens
, bRemoveCommentAnchors
);
769 sCleanStr
= lcl_CleanStr(*pNode
, pFrame
, pLayout
, nEnd
, nStart
,
770 aFltArr
, bRemoveSoftHyphens
, bRemoveCommentAnchors
);
772 std::unique_ptr
<SwScriptIterator
> pScriptIter
;
773 sal_uInt16 nSearchScript
= 0;
774 sal_uInt16 nCurrScript
= 0;
776 if (SearchAlgorithms2::APPROXIMATE
== rSearchOpt
.AlgorithmType2
)
778 pScriptIter
.reset(new SwScriptIterator(sCleanStr
, nStart
.GetAnyIndex(), bSrchForward
));
779 nSearchScript
= g_pBreakIt
->GetRealScriptOfText( rSearchOpt
.searchString
, 0 );
782 const AmbiguousIndex nStringEnd
= nEnd
;
783 bool bZeroMatch
= false; // zero-length match, i.e. only $ anchor as regex
784 while ( ((bSrchForward
&& nStart
< nStringEnd
) ||
785 (!bSrchForward
&& nStringEnd
< nStart
)) && !bZeroMatch
)
787 // SearchAlgorithms_APPROXIMATE works on a per word base so we have to
788 // provide the text searcher with the correct locale, because it uses
789 // the break-iterator
792 nEnd
.GetAnyIndex() = pScriptIter
->GetScriptChgPos();
793 nCurrScript
= pScriptIter
->GetCurrScript();
794 if ( nSearchScript
== nCurrScript
)
796 const LanguageType eCurrLang
= pLayout
797 ? pFrame
->GetLangOfChar(bSrchForward
798 ? nStart
.GetFrameIndex()
799 : nEnd
.GetFrameIndex(),
801 : pNode
->GetLang(bSrchForward
802 ? nStart
.GetModelIndex()
803 : nEnd
.GetModelIndex());
805 if ( eCurrLang
!= eLastLang
)
807 const lang::Locale
aLocale(
808 g_pBreakIt
->GetLocale( eCurrLang
) );
809 rSText
.SetLocale( utl::TextSearch::UpgradeToSearchOptions2( rSearchOpt
), aLocale
);
810 eLastLang
= eCurrLang
;
815 AmbiguousIndex nProxyStart
= nStart
;
816 AmbiguousIndex nProxyEnd
= nEnd
;
817 if( nSearchScript
== nCurrScript
&&
818 (rSText
.*fnMove
.fnSearch
)( sCleanStr
, &nProxyStart
.GetAnyIndex(), &nProxyEnd
.GetAnyIndex(), nullptr) &&
819 !(bZeroMatch
= (nProxyStart
== nProxyEnd
)))
821 nStart
= nProxyStart
;
823 // set section correctly
824 *rSearchPam
.GetPoint() = *pPam
->GetPoint();
825 rSearchPam
.SetMark();
827 // adjust start and end
828 if( !aFltArr
.empty() )
830 // if backward search, switch positions temporarily
831 if (!bSrchForward
) { std::swap(nStart
, nEnd
); }
833 AmbiguousIndex nNew
= nStart
;
834 for (size_t n
= 0; n
< aFltArr
.size() && aFltArr
[ n
] <= nStart
; ++n
)
836 ++nNew
.GetAnyIndex();
841 for( size_t n
= 0; n
< aFltArr
.size() && aFltArr
[ n
] < nEnd
; ++n
)
843 ++nNew
.GetAnyIndex();
847 // if backward search, switch positions temporarily
848 if( !bSrchForward
) { std::swap(nStart
, nEnd
); }
852 *rSearchPam
.GetMark() = pFrame
->MapViewToModelPos(nStart
.GetFrameIndex());
853 *rSearchPam
.GetPoint() = pFrame
->MapViewToModelPos(nEnd
.GetFrameIndex());
857 rSearchPam
.GetMark()->nContent
= nStart
.GetModelIndex();
858 rSearchPam
.GetPoint()->nContent
= nEnd
.GetModelIndex();
861 // if backward search, switch point and mark
863 rSearchPam
.Exchange();
879 if (!bChkEmptyPara
&& !bChkParaEnd
)
882 if (bChkEmptyPara
&& bSrchForward
&& nTextLen
.GetAnyIndex())
883 return false; // the length is not zero - there is content here
885 // move to the end (or start) of the paragraph
886 *rSearchPam
.GetPoint() = *pPam
->GetPoint();
889 *rSearchPam
.GetPoint() = pFrame
->MapViewToModelPos(
890 bSrchForward
? nTextLen
.GetFrameIndex() : TextFrameIndex(0));
894 rSearchPam
.GetPoint()->nContent
= bSrchForward
? nTextLen
.GetModelIndex() : 0;
896 rSearchPam
.SetMark();
898 if (!rSearchPam
.Move(fnMove
, GoInContent
))
899 return false; // at start or end of the document
901 // selection must not be outside of the search area
902 if (!pPam
->ContainsPosition(*rSearchPam
.GetPoint()))
905 if (SwNodeOffset(1) == abs(rSearchPam
.GetPoint()->nNode
.GetIndex() -
906 rSearchPam
.GetMark()->nNode
.GetIndex()))
908 if (bChkEmptyPara
&& !bSrchForward
&& rSearchPam
.GetPoint()->nContent
.GetIndex())
909 return false; // the length is not zero - there is content here
919 /// parameters for search and replace in text
920 struct SwFindParaText
: public SwFindParas
922 const i18nutil::SearchOptions2
& m_rSearchOpt
;
924 SwRootFrame
const* m_pLayout
;
925 utl::TextSearch m_aSText
;
927 bool m_bSearchInNotes
;
929 SwFindParaText(const i18nutil::SearchOptions2
& rOpt
, bool bSearchInNotes
,
930 bool bRepl
, SwCursor
& rCursor
, SwRootFrame
const*const pLayout
)
931 : m_rSearchOpt( rOpt
)
932 , m_rCursor( rCursor
)
934 , m_aSText( utl::TextSearch::UpgradeToSearchOptions2(rOpt
) )
935 , m_bReplace( bRepl
)
936 , m_bSearchInNotes( bSearchInNotes
)
938 virtual int DoFind(SwPaM
&, SwMoveFnCollection
const &, const SwPaM
&, bool bInReadOnly
, std::unique_ptr
<SvxSearchItem
>& xSearchItem
) override
;
939 virtual bool IsReplaceMode() const override
;
940 virtual ~SwFindParaText();
945 SwFindParaText::~SwFindParaText()
949 int SwFindParaText::DoFind(SwPaM
& rCursor
, SwMoveFnCollection
const & fnMove
,
950 const SwPaM
& rRegion
, bool bInReadOnly
,
951 std::unique_ptr
<SvxSearchItem
>& xSearchItem
)
953 if( bInReadOnly
&& m_bReplace
)
956 const bool bFnd
= sw::FindTextImpl(rCursor
, m_rSearchOpt
, m_bSearchInNotes
,
957 m_aSText
, fnMove
, rRegion
, bInReadOnly
, m_pLayout
, xSearchItem
);
959 if( bFnd
&& m_bReplace
) // replace string
961 // use replace method in SwDoc
962 const bool bRegExp(SearchAlgorithms2::REGEXP
== m_rSearchOpt
.AlgorithmType2
);
963 SwIndex
& rSttCntIdx
= rCursor
.Start()->nContent
;
964 const sal_Int32 nSttCnt
= rSttCntIdx
.GetIndex();
965 // add to shell-cursor-ring so that the regions will be moved eventually
966 SwPaM
* pPrev(nullptr);
969 pPrev
= const_cast<SwPaM
&>(rRegion
).GetPrev();
970 const_cast<SwPaM
&>(rRegion
).GetRingContainer().merge( m_rCursor
.GetRingContainer() );
973 std::optional
<OUString
> xRepl
;
975 xRepl
= sw::ReplaceBackReferences(m_rSearchOpt
, &rCursor
, m_pLayout
);
976 bool const bReplaced
= sw::ReplaceImpl(rCursor
,
977 xRepl
? *xRepl
: m_rSearchOpt
.replaceString
,
978 bRegExp
, m_rCursor
.GetDoc(), m_pLayout
);
980 m_rCursor
.SaveTableBoxContent( rCursor
.GetPoint() );
984 // and remove region again
986 SwPaM
* pNext(const_cast<SwPaM
*>(&rRegion
));
989 pNext
= p
->GetNext();
990 p
->MoveTo(const_cast<SwPaM
*>(&rRegion
));
991 } while( p
!= pPrev
);
993 if (bRegExp
&& !bReplaced
)
994 { // fdo#80715 avoid infinite loop if join failed
995 bool bRet
= ((&fnMoveForward
== &fnMove
) ? &GoNextPara
: &GoPrevPara
)
998 assert(bRet
); // if join failed, next node must be SwTextNode
1001 rCursor
.Start()->nContent
= nSttCnt
;
1002 return FIND_NO_RING
;
1004 return bFnd
? FIND_FOUND
: FIND_NOT_FOUND
;
1007 bool SwFindParaText::IsReplaceMode() const
1012 sal_uLong
SwCursor::Find_Text( const i18nutil::SearchOptions2
& rSearchOpt
, bool bSearchInNotes
,
1013 SwDocPositions nStart
, SwDocPositions nEnd
,
1014 bool& bCancel
, FindRanges eFndRngs
, bool bReplace
,
1015 SwRootFrame
const*const pLayout
)
1017 // switch off OLE-notifications
1018 SwDoc
& rDoc
= GetDoc();
1019 Link
<bool,void> aLnk( rDoc
.GetOle2Link() );
1020 rDoc
.SetOle2Link( Link
<bool,void>() );
1022 bool const bStartUndo
= rDoc
.GetIDocumentUndoRedo().DoesUndo() && bReplace
;
1025 rDoc
.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE
, nullptr );
1028 bool bSearchSel
= 0 != (rSearchOpt
.searchFlag
& SearchFlags::REG_NOT_BEGINOFLINE
);
1030 eFndRngs
= static_cast<FindRanges
>(eFndRngs
| FindRanges::InSel
);
1031 SwFindParaText
aSwFindParaText(rSearchOpt
, bSearchInNotes
, bReplace
, *this, pLayout
);
1032 sal_uLong nRet
= FindAll( aSwFindParaText
, nStart
, nEnd
, eFndRngs
, bCancel
);
1033 rDoc
.SetOle2Link( aLnk
);
1034 if( nRet
&& bReplace
)
1035 rDoc
.getIDocumentState().SetModified();
1039 SwRewriter
rewriter(MakeUndoReplaceRewriter(
1040 nRet
, rSearchOpt
.searchString
, rSearchOpt
.replaceString
));
1041 rDoc
.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE
, & rewriter
);
1050 OUString
const& rReplacement
,
1053 SwRootFrame
const*const pLayout
)
1055 bool bReplaced(true);
1056 IDocumentContentOperations
& rIDCO(rDoc
.getIDocumentContentOperations());
1058 // FIXME there's some problem with multiple redlines here on Undo
1059 std::vector
<std::shared_ptr
<SwUnoCursor
>> ranges
;
1060 if (rDoc
.getIDocumentRedlineAccess().IsRedlineOn()
1062 || !pLayout
->IsHideRedlines()
1063 || sw::GetRanges(ranges
, rDoc
, rCursor
))
1065 bReplaced
= rIDCO
.ReplaceRange(rCursor
, rReplacement
, bRegExp
);
1069 assert(!ranges
.empty());
1070 assert(ranges
.front()->GetPoint()->nNode
== ranges
.front()->GetMark()->nNode
);
1071 bReplaced
= rIDCO
.ReplaceRange(*ranges
.front(), rReplacement
, bRegExp
);
1072 for (auto it
= ranges
.begin() + 1; it
!= ranges
.end(); ++it
)
1074 bReplaced
&= rIDCO
.DeleteAndJoin(**it
);
1078 IDocumentRedlineAccess
const& rIDRA(rDoc
.getIDocumentRedlineAccess());
1079 if (pLayout
&& pLayout
->IsHideRedlines()
1080 && !rIDRA
.IsRedlineOn() // otherwise: ReplaceRange will handle it
1081 && (rIDRA
.GetRedlineFlags() & RedlineFlags::ShowDelete
)) // otherwise: ReplaceRange will DeleteRedline()
1083 SwRedlineTable::size_type tmp
;
1084 rIDRA
.GetRedline(*rCursor
.Start(), &tmp
);
1085 while (tmp
< rIDRA
.GetRedlineTable().size())
1087 SwRangeRedline
const*const pRedline(rIDRA
.GetRedlineTable()[tmp
]);
1088 if (*rCursor
.End() <= *pRedline
->Start())
1092 if (*pRedline
->End() <= *rCursor
.Start())
1097 if (pRedline
->GetType() == RedlineType::Delete
)
1099 assert(*pRedline
->Start() != *pRedline
->End());
1100 // search in hidden layout can't overlap redlines
1101 assert(*rCursor
.Start() <= *pRedline
->Start() && *pRedline
->End() <= *rCursor
.End());
1102 SwPaM
pam(*pRedline
, nullptr);
1103 bReplaced
&= rIDCO
.DeleteAndJoin(pam
);
1111 bReplaced
&= rIDCO
.ReplaceRange(rCursor
, rReplacement
, bRegExp
);
1116 std::optional
<OUString
> ReplaceBackReferences(const i18nutil::SearchOptions2
& rSearchOpt
,
1117 SwPaM
*const pPam
, SwRootFrame
const*const pLayout
)
1119 std::optional
<OUString
> xRet
;
1120 if( pPam
&& pPam
->HasMark() &&
1121 SearchAlgorithms2::REGEXP
== rSearchOpt
.AlgorithmType2
)
1123 SwContentNode
const*const pTextNode
= pPam
->GetContentNode();
1124 SwContentNode
const*const pMarkTextNode
= pPam
->GetContentNode(false);
1125 if (!pTextNode
|| !pTextNode
->IsTextNode()
1126 || !pMarkTextNode
|| !pMarkTextNode
->IsTextNode())
1130 SwTextFrame
const*const pFrame(pLayout
1131 ? static_cast<SwTextFrame
const*>(pTextNode
->getLayoutFrame(pLayout
))
1133 const bool bParaEnd
= rSearchOpt
.searchString
== "$" || rSearchOpt
.searchString
== "^$" || rSearchOpt
.searchString
== "$^";
1134 if (bParaEnd
|| (pLayout
1135 ? sw::FrameContainsNode(*pFrame
, pPam
->GetMark()->nNode
.GetIndex())
1136 : pTextNode
== pMarkTextNode
))
1138 utl::TextSearch
aSText( utl::TextSearch::UpgradeToSearchOptions2( rSearchOpt
) );
1139 SearchResult aResult
;
1140 OUString
aReplaceStr( rSearchOpt
.replaceString
);
1143 OUString
const aStr("\\n");
1144 aResult
.subRegExpressions
= 1;
1145 aResult
.startOffset
= { 0 };
1146 aResult
.endOffset
= { aStr
.getLength() };
1147 aSText
.ReplaceBackReferences( aReplaceStr
, aStr
, aResult
);
1152 AmbiguousIndex nStart
;
1153 AmbiguousIndex nEnd
;
1156 nStart
.SetFrameIndex(pFrame
->MapModelToViewPos(*pPam
->Start()));
1157 nEnd
.SetFrameIndex(pFrame
->MapModelToViewPos(*pPam
->End()));
1161 nStart
.SetModelIndex(pPam
->Start()->nContent
.GetIndex());
1162 nEnd
.SetModelIndex(pPam
->End()->nContent
.GetIndex());
1164 std::vector
<AmbiguousIndex
> aFltArr
;
1165 OUString
const aStr
= lcl_CleanStr(*pTextNode
->GetTextNode(), pFrame
, pLayout
,
1166 nStart
, nEnd
, aFltArr
, false, false);
1167 if (aSText
.SearchForward(aStr
, &nStart
.GetAnyIndex(), &nEnd
.GetAnyIndex(), &aResult
))
1169 aSText
.ReplaceBackReferences( aReplaceStr
, aStr
, aResult
);
1180 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */