docthemes: Save themes def. to a file when added to ColorSets
[LibreOffice.git] / sw / source / core / crsr / findtxt.cxx
blob07a19bde59b7801a95b80b5b375b14ebf4253e08
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 .
20 #include <memory>
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>
33 #include <wrtsh.hxx>
34 #include <txatritr.hxx>
35 #include <fldbas.hxx>
36 #include <fmtfld.hxx>
37 #include <txtfld.hxx>
38 #include <txtfrm.hxx>
39 #include <rootfrm.hxx>
40 #include <swcrsr.hxx>
41 #include <redline.hxx>
42 #include <doc.hxx>
43 #include <IDocumentUndoRedo.hxx>
44 #include <IDocumentState.hxx>
45 #include <IDocumentDrawModelAccess.hxx>
46 #include <IDocumentRedlineAccess.hxx>
47 #include <dcontact.hxx>
48 #include <pamtyp.hxx>
49 #include <ndtxt.hxx>
50 #include <swundo.hxx>
51 #include <UndoInsert.hxx>
52 #include <breakit.hxx>
53 #include <docsh.hxx>
54 #include <PostItMgr.hxx>
55 #include <view.hxx>
57 using namespace ::com::sun::star;
58 using namespace util;
60 namespace {
62 /// because the Find may be called on the View or the Model, we need an index
63 /// afflicted by multiple personality disorder
64 struct AmbiguousIndex
66 private:
67 sal_Int32 m_value;
69 #ifndef NDEBUG
70 enum class tags : char { Any, Frame, Model };
71 tags m_tag;
72 #endif
74 public:
75 AmbiguousIndex() : m_value(-1)
76 #ifndef NDEBUG
77 , m_tag(tags::Any)
78 #endif
80 explicit AmbiguousIndex(sal_Int32 const value
81 #ifndef NDEBUG
82 , tags const tag
83 #endif
84 ) : m_value(value)
85 #ifndef NDEBUG
86 , m_tag(tag)
87 #endif
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);
100 return m_value;
102 void SetFrameIndex(TextFrameIndex const value)
104 #ifndef NDEBUG
105 m_tag = tags::Frame;
106 #endif
107 m_value = sal_Int32(value);
109 void SetModelIndex(sal_Int32 const value)
111 #ifndef NDEBUG
112 m_tag = tags::Model;
113 #endif
114 m_value = 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
136 #ifndef NDEBUG
137 , std::max(m_tag, rOther.m_tag)
138 #endif
143 class MaybeMergedIter
145 std::optional<sw::MergedAttrIter> m_oMergedIter;
146 SwTextNode const*const m_pNode;
147 size_t m_HintIndex;
149 public:
150 MaybeMergedIter(SwTextFrame const*const pFrame, SwTextNode const*const pNode)
151 : m_pNode(pNode)
152 , m_HintIndex(0)
154 if (pFrame)
156 m_oMergedIter.emplace(*pFrame);
160 SwTextAttr const* NextAttr(SwTextNode const*& rpNode)
162 if (m_oMergedIter)
164 return m_oMergedIter->NextAttr(&rpNode);
166 if (SwpHints const*const pHints = m_pNode->GetpSwpHints())
168 if (m_HintIndex < pHints->Count())
170 rpNode = m_pNode;
171 return pHints->Get(m_HintIndex++);
174 return nullptr;
180 static OUString
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());
189 rArr.clear();
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));
204 if ( bNewHint )
206 if (pLayout)
208 nHintStart.SetFrameIndex(pNextHint
209 ? pFrame->MapModelToView(pNextHintNode, pNextHint->GetStart())
210 : TextFrameIndex(-1));
212 else
214 nHintStart.SetModelIndex(pNextHint ? pNextHint->GetStart() : -1);
218 if ( bNewSoftHyphen )
220 if (pLayout)
222 nSoftHyphen.SetFrameIndex(TextFrameIndex(bRemoveSoftHyphen
223 ? pFrame->GetText().indexOf(CHAR_SOFTHYPHEN, nSoftHyphen.GetAnyIndex())
224 : -1));
226 else
228 nSoftHyphen.SetModelIndex(bRemoveSoftHyphen
229 ? rNd.GetText().indexOf(CHAR_SOFTHYPHEN, nSoftHyphen.GetAnyIndex())
230 : -1);
234 bNewHint = false;
235 bNewSoftHyphen = false;
236 AmbiguousIndex nStt;
238 // Check if next stop is a hint.
239 if (0 <= nHintStart.GetAnyIndex()
240 && (-1 == nSoftHyphen.GetAnyIndex() || nHintStart < nSoftHyphen)
241 && nHintStart < nEnd )
243 nStt = nHintStart;
244 bNewHint = true;
246 // Check if next stop is a soft hyphen.
247 else if ( -1 != nSoftHyphen.GetAnyIndex()
248 && (-1 == nHintStart.GetAnyIndex() || nSoftHyphen < nHintStart)
249 && nSoftHyphen < nEnd)
251 nStt = nSoftHyphen;
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)
257 nStt = nSoftHyphen;
258 bNewHint = true;
259 bNewSoftHyphen = true;
261 else
262 break;
264 AmbiguousIndex nCurrent(nStt);
265 nCurrent.GetAnyIndex() -= rArr.size();
267 if ( bNewHint )
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);
298 else
300 if ( bEmpty )
301 aReplaced.push_back( nCurrent );
302 buf[nCurrent.GetAnyIndex()] = '\x7f';
305 break;
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 );
318 break;
319 default:
320 OSL_FAIL( "unknown case in lcl_CleanStr" );
321 break;
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();
342 while ( true );
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);
366 namespace sw {
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() )
377 return false;
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
385 bool bFound = false;
386 // start position in text or initial position
387 bool bFirst = true;
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 == "$";
396 if (!xSearchItem)
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())
407 continue;
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;
414 if (pLayout)
416 nTextLen.SetFrameIndex(TextFrameIndex(pFrame->GetText().getLength()));
418 else
420 nTextLen.SetModelIndex(rTextNode.GetText().getLength());
422 AmbiguousIndex nEnd;
423 if (pLayout ? FrameContainsNode(*pFrame, oPam->GetMark()->GetNodeIndex())
424 : rPtPos.GetNode() == oPam->GetMark()->GetNode())
426 if (pLayout)
428 nEnd.SetFrameIndex(pFrame->MapModelToViewPos(*oPam->GetMark()));
430 else
432 nEnd.SetModelIndex(oPam->GetMark()->GetContentIndex());
435 else
437 if (bSrchForward)
439 nEnd = nTextLen;
441 else
443 if (pLayout)
445 nEnd.SetFrameIndex(TextFrameIndex(0));
447 else
449 nEnd.SetModelIndex(0);
453 AmbiguousIndex nStart;
454 if (pLayout)
456 nStart.SetFrameIndex(pFrame->MapModelToViewPos(*oPam->GetPoint()));
458 else
460 nStart.SetModelIndex(rPtPos.GetContentIndex());
463 /* #i80135# */
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;
471 if (bSearchInNotes)
473 if (!bSrchForward)
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)
483 AmbiguousIndex aPos;
484 aPos.SetModelIndex(pTextAttr->GetStart());
485 if (pLayout)
487 aPos.SetFrameIndex(pFrame->MapModelToView(pTemp, aPos.GetModelIndex()));
489 if ((nStart <= aPos) && (aPos <= nEnd))
491 postits.emplace_back(pTextAttr, aPos);
496 if (!bSrchForward)
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;
509 if (pSdrView)
511 // If the edited object is not anchored to this node, then ignore it.
512 SdrObject* pObject = pSdrView->GetTextEditObject();
513 if (pObject)
515 if (SwFrameFormat* pFrameFormat = FindFrameFormat(pObject))
517 const SwNode* pAnchorNode = pFrameFormat->GetAnchor().GetAnchorNode();
518 if (!pAnchorNode
519 || (pLayout ? !FrameContainsNode(*pFrame, pAnchorNode->GetIndex())
520 : pAnchorNode->GetIndex() != pNode->GetIndex()))
521 pObject = nullptr;
525 if (pObject)
527 sal_uInt16 nResult
528 = pSdrView->GetTextEditOutlinerView()->StartSearchAndReplace(*xSearchItem);
529 if (!nResult)
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);
536 pWrtShell->Edit();
537 bEndedTextEdit = true;
539 else
541 bFound = true;
542 break;
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.
552 if (!bEndedTextEdit
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());
557 if (pLayout)
559 *aPaM.GetPoint() = pFrame->MapViewToModelPos(nStart.GetFrameIndex());
561 else
563 aPaM.GetPoint()->Assign(rTextNode, nStart.GetModelIndex());
565 aPaM.SetMark();
566 if (pLayout)
568 aPaM.GetMark()->Assign(
569 (pFrame->GetMergedPara() ? *pFrame->GetMergedPara()->pLastNode : rTextNode)
570 .GetIndex()
571 + 1);
573 else
575 aPaM.GetMark()->Assign(rTextNode.GetIndex() + 1);
577 if (pNode->GetDoc().getIDocumentDrawModelAccess().Search(aPaM, *xSearchItem)
578 && pSdrView)
580 if (SdrObject* pObject = pSdrView->GetTextEditObject())
582 if (SwFrameFormat* pFrameFormat = FindFrameFormat(pObject))
584 const SwNode* pAnchorNode = pFrameFormat->GetAnchor().GetAnchorNode();
585 if (pAnchorNode)
587 // Set search position to the shape's anchor point.
588 rSearchPam.GetPoint()->Assign(*pAnchorNode);
589 rSearchPam.SetMark();
590 bFound = true;
591 break;
599 // do we need to finish a note?
600 if (pPostItMgr && pPostItMgr->HasActiveSidebarWin())
602 if (bSearchInNotes)
604 if (!postits.empty())
606 if (bSrchForward)
608 postits.erase(postits.begin());
610 else
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))
618 bFound = true;
619 break;
622 else
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()))
637 if (bSrchForward)
639 if (aLoop == 0)
641 nStartInside = nStart;
643 else if (pLayout)
645 nStartInside.SetFrameIndex(postits[aLoop - 1].second.GetFrameIndex()
646 + TextFrameIndex(1));
648 else
650 nStartInside.SetModelIndex(postits[aLoop - 1].second.GetModelIndex() + 1);
652 nEndInside = static_cast<size_t>(aLoop) == postits.size()
653 ? nEnd
654 : postits[aLoop].second;
655 nTextLen = nEndInside - nStartInside;
657 else
659 nStartInside = static_cast<size_t>(aLoop) == postits.size()
660 ? nStart
661 : postits[aLoop].second;
662 if (aLoop == 0)
664 nEndInside = nEnd;
666 else if (pLayout)
668 nEndInside.SetFrameIndex(postits[aLoop - 1].second.GetFrameIndex()
669 + TextFrameIndex(1));
671 else
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);
681 if (bFound)
682 break;
683 else
685 // we should now be right in front of a note, search inside
686 if (bSrchForward ? (static_cast<size_t>(aLoop) != postits.size())
687 : (aLoop != 0))
689 const SwTextAttr* const pTextAttr
690 = bSrchForward ? postits[aLoop].first : postits[aLoop - 1].first;
691 if (pPostItMgr
692 && pPostItMgr->SearchReplace(
693 static_txtattr_cast<SwTextField const*>(pTextAttr)
694 ->GetFormatField(),
695 rSearchOpt, bSrchForward))
697 bFound = true;
698 break;
702 aLoop = bSrchForward ? aLoop + 1 : aLoop - 1;
705 else
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);
713 if (bFound)
714 break;
716 return bFound;
719 } // namespace sw
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;
738 break;
740 ++nIndex;
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())
745 return false;
748 bool bFound = false;
749 OUString sCleanStr;
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;
758 if ( bRegSearch )
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;
770 else
772 if ( 1 == rSearchOpt.searchString.getLength() &&
773 CHAR_SOFTHYPHEN == rSearchOpt.searchString.toChar() )
774 bRemoveSoftHyphens = false;
777 if( bSrchForward )
778 sCleanStr = lcl_CleanStr(*pNode, pFrame, pLayout, nStart, nEnd,
779 aFltArr, bRemoveSoftHyphens, bRemoveCommentAnchors);
780 else
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
802 if ( pScriptIter )
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(),
812 0, true)
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;
825 pScriptIter->Next();
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;
834 nEnd = nProxyEnd;
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();
851 nStart = nNew;
852 nNew = nEnd;
853 for( size_t n = 0; n < aFltArr.size() && aFltArr[ n ] < nEnd; ++n )
855 ++nNew.GetAnyIndex();
858 nEnd = nNew;
859 // if backward search, switch positions temporarily
860 if( !bSrchForward ) { std::swap(nStart, nEnd); }
862 if (pLayout)
864 *rSearchPam.GetMark() = pFrame->MapViewToModelPos(nStart.GetFrameIndex());
865 *rSearchPam.GetPoint() = pFrame->MapViewToModelPos(nEnd.GetFrameIndex());
867 else
869 rSearchPam.GetMark()->SetContent( nStart.GetModelIndex() );
870 rSearchPam.GetPoint()->SetContent( nEnd.GetModelIndex() );
873 // if backward search, switch point and mark
874 if( !bSrchForward )
875 rSearchPam.Exchange();
876 bFound = true;
877 break;
879 else
881 nEnd = nProxyEnd;
883 nStart = nEnd;
886 pScriptIter.reset();
888 if ( bFound )
889 return true;
891 if (!bChkEmptyPara && !bChkParaEnd)
892 return false;
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();
899 if (pLayout)
901 *rSearchPam.GetPoint() = pFrame->MapViewToModelPos(
902 bSrchForward ? nTextLen.GetFrameIndex() : TextFrameIndex(0));
904 else
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()))
915 return false;
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
923 return true;
926 return false;
929 namespace {
931 /// parameters for search and replace in text
932 struct SwFindParaText : public SwFindParas
934 const i18nutil::SearchOptions2& m_rSearchOpt;
935 SwCursor& m_rCursor;
936 SwRootFrame const* m_pLayout;
937 utl::TextSearch m_aSText;
938 bool m_bReplace;
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 )
945 , m_pLayout(pLayout)
946 , m_aSText(rOpt)
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 )
966 bInReadOnly = false;
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);
978 if( bRegExp )
980 pPrev = const_cast<SwPaM&>(rRegion).GetPrev();
981 const_cast<SwPaM&>(rRegion).GetRingContainer().merge( m_rCursor.GetRingContainer() );
984 std::optional<OUString> xRepl;
985 if (bRegExp)
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() );
993 if( bRegExp )
995 // and remove region again
996 SwPaM* p;
997 SwPaM* pNext(const_cast<SwPaM*>(&rRegion));
998 do {
999 p = pNext;
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)
1007 (rCursor, fnMove);
1008 (void) bRet;
1009 assert(bRet); // if join failed, next node must be SwTextNode
1011 else
1012 rCursor.Start()->SetContent(nSttCnt);
1013 return FIND_NO_RING;
1015 return bFnd ? FIND_FOUND : FIND_NOT_FOUND;
1018 bool SwFindParaText::IsReplaceMode() const
1020 return m_bReplace;
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;
1034 if (bStartUndo)
1036 rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE, nullptr );
1039 bool bSearchSel = 0 != (rSearchOpt.searchFlag & SearchFlags::REG_NOT_BEGINOFLINE);
1040 if( bSearchSel )
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();
1048 if (bStartUndo)
1050 SwRewriter rewriter(MakeUndoReplaceRewriter(
1051 nRet, rSearchOpt.searchString, rSearchOpt.replaceString));
1052 rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE, & rewriter );
1054 return nRet;
1057 namespace sw {
1059 bool ReplaceImpl(
1060 SwPaM & rCursor,
1061 OUString const& rReplacement,
1062 bool const bRegExp,
1063 SwDoc & rDoc,
1064 SwRootFrame const*const pLayout)
1066 bool bReplaced(true);
1067 IDocumentContentOperations & rIDCO(rDoc.getIDocumentContentOperations());
1068 #if 0
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()
1072 || !pLayout
1073 || !pLayout->IsHideRedlines()
1074 || sw::GetRanges(ranges, rDoc, rCursor))
1076 bReplaced = rIDCO.ReplaceRange(rCursor, rReplacement, bRegExp);
1078 else
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);
1088 #else
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())
1101 break;
1103 if (*pRedline->End() <= *rCursor.Start())
1105 ++tmp;
1106 continue;
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);
1116 else
1118 ++tmp;
1122 bReplaced &= rIDCO.ReplaceRange(rCursor, rReplacement, bRegExp);
1123 #endif
1124 return bReplaced;
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())
1139 return xRet;
1141 SwTextFrame const*const pFrame(pLayout
1142 ? static_cast<SwTextFrame const*>(pTextNode->getLayoutFrame(pLayout))
1143 : nullptr);
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 );
1152 if (bParaEnd)
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 );
1159 xRet = aReplaceStr;
1161 else
1163 AmbiguousIndex nStart;
1164 AmbiguousIndex nEnd;
1165 if (pLayout)
1167 nStart.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->Start()));
1168 nEnd.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->End()));
1170 else
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 );
1181 xRet = aReplaceStr;
1186 return xRet;
1189 } // namespace sw
1191 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */