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 .
20 #include <com/sun/star/lang/Locale.hpp>
21 #include <com/sun/star/util/SearchAlgorithms2.hpp>
22 #include <com/sun/star/util/SearchFlags.hpp>
23 #include <i18nlangtag/languagetag.hxx>
24 #include <i18nutil/searchopt.hxx>
25 #include <osl/diagnose.h>
26 #include <unotools/syslocale.hxx>
27 #include <hintids.hxx>
28 #include <svl/itemiter.hxx>
29 #include <svl/srchitem.hxx>
30 #include <svl/whiter.hxx>
31 #include <editeng/colritem.hxx>
32 #include <editeng/fontitem.hxx>
33 #include <fmtpdsc.hxx>
34 #include <txatbase.hxx>
35 #include <charfmt.hxx>
38 #include <IDocumentUndoRedo.hxx>
39 #include <IDocumentState.hxx>
50 using namespace ::com::sun::star
;
51 using namespace ::com::sun::star::lang
;
52 using namespace ::com::sun::star::util
;
54 // Special case for SvxFontItem: only compare the name
55 static bool CmpAttr( const SfxPoolItem
& rItem1
, const SfxPoolItem
& rItem2
)
57 switch( rItem1
.Which() )
60 return rItem1
.StaticWhichCast(RES_CHRATR_FONT
).GetFamilyName() == rItem2
.StaticWhichCast(RES_CHRATR_FONT
).GetFamilyName();
62 case RES_CHRATR_COLOR
:
63 return rItem1
.StaticWhichCast(RES_CHRATR_COLOR
).GetValue().IsRGBEqual(rItem2
.StaticWhichCast(RES_CHRATR_COLOR
).GetValue());
65 ::std::optional
<sal_uInt16
> const oNumOffset1
= rItem1
.StaticWhichCast(RES_PAGEDESC
).GetNumOffset();
66 ::std::optional
<sal_uInt16
> const oNumOffset2
= rItem2
.StaticWhichCast(RES_PAGEDESC
).GetNumOffset();
68 if (oNumOffset1
!= oNumOffset2
)
71 return rItem1
.StaticWhichCast(RES_PAGEDESC
).GetPageDesc() == rItem2
.StaticWhichCast(RES_PAGEDESC
).GetPageDesc();
73 return rItem1
== rItem2
;
76 const SwTextAttr
* GetFrwrdTextHint( const SwpHints
& rHtsArr
, size_t& rPos
,
77 sal_Int32 nContentPos
)
79 while( rPos
< rHtsArr
.Count() )
81 const SwTextAttr
*pTextHt
= rHtsArr
.Get( rPos
++ );
82 // the start of an attribute has to be in the section
83 if( pTextHt
->GetStart() >= nContentPos
)
84 return pTextHt
; // valid text attribute
86 return nullptr; // invalid text attribute
89 const SwTextAttr
* GetBkwrdTextHint( const SwpHints
& rHtsArr
, size_t& rPos
,
90 sal_Int32 nContentPos
)
94 const SwTextAttr
*pTextHt
= rHtsArr
.Get( --rPos
);
95 // the start of an attribute has to be in the section
96 if( pTextHt
->GetStart() < nContentPos
)
97 return pTextHt
; // valid text attribute
99 return nullptr; // invalid text attribute
102 static void lcl_SetAttrPam( SwPaM
& rPam
, sal_Int32 nStart
, const sal_Int32
* pEnd
,
103 const bool bSaveMark
)
105 sal_Int32 nContentPos
;
107 nContentPos
= rPam
.GetMark()->GetContentIndex();
109 nContentPos
= rPam
.GetPoint()->GetContentIndex();
110 bool bTstEnd
= rPam
.GetPoint()->GetNode() == rPam
.GetMark()->GetNode();
112 rPam
.GetPoint()->SetContent( nStart
);
113 rPam
.SetMark(); // Point == GetMark
115 // Point points to end of search area or end of attribute
118 if( bTstEnd
&& *pEnd
> nContentPos
)
119 rPam
.GetPoint()->SetContent(nContentPos
);
121 rPam
.GetPoint()->SetContent(*pEnd
);
125 // TODO: provide documentation
126 /** search for a text attribute
128 This function searches in a text node for a given attribute.
129 If that is found then the SwPaM contains the section that surrounds the
130 attribute (w.r.t. the search area).
132 @param rTextNd Text node to search in.
136 @return Returns <true> if found, <false> otherwise.
138 static bool lcl_SearchAttr( const SwTextNode
& rTextNd
, SwPaM
& rPam
,
139 const SfxPoolItem
& rCmpItem
,
140 SwMoveFnCollection
const & fnMove
)
142 if ( !rTextNd
.HasHints() )
145 const SwTextAttr
*pTextHt
= nullptr;
146 bool bForward
= &fnMove
== &fnMoveForward
;
147 size_t nPos
= bForward
? 0 : rTextNd
.GetSwpHints().Count();
148 sal_Int32 nContentPos
= rPam
.GetPoint()->GetContentIndex();
150 while( nullptr != ( pTextHt
=(*fnMove
.fnGetHint
)(rTextNd
.GetSwpHints(),nPos
,nContentPos
)))
151 if (pTextHt
->Which() == rCmpItem
.Which())
153 lcl_SetAttrPam( rPam
, pTextHt
->GetStart(), pTextHt
->End(), bForward
);
161 /// search for multiple text attributes
168 SwSrchChrAttr(): nWhich(0), nStt(0), nEnd(0) {}
170 SwSrchChrAttr( const SfxPoolItem
& rItem
,
171 sal_Int32 nStart
, sal_Int32 nAnyEnd
)
172 : nWhich( rItem
.Which() ), nStt( nStart
), nEnd( nAnyEnd
)
178 SwSrchChrAttr
*m_pFindArr
, *m_pStackArr
;
179 sal_Int32 m_nNodeStart
;
180 sal_Int32 m_nNodeEnd
;
181 sal_uInt16 m_nArrStart
, m_nArrLen
;
182 sal_uInt16 m_nFound
, m_nStackCount
;
183 SfxItemSet m_aComapeSet
;
188 SwAttrCheckArr( const SfxItemSet
& rSet
, bool bForward
, bool bNoCollections
);
191 void SetNewSet( const SwTextNode
& rTextNd
, const SwPaM
& rPam
);
193 /// how many attributes are there in total?
194 sal_uInt16
Count() const { return m_aComapeSet
.Count(); }
195 bool Found() const { return m_nFound
== m_aComapeSet
.Count(); }
198 sal_Int32
Start() const;
199 sal_Int32
End() const;
201 sal_Int32
GetNdStt() const { return m_nNodeStart
; }
202 sal_Int32
GetNdEnd() const { return m_nNodeEnd
; }
204 bool SetAttrFwd( const SwTextAttr
& rAttr
);
205 bool SetAttrBwd( const SwTextAttr
& rAttr
);
210 SwAttrCheckArr::SwAttrCheckArr( const SfxItemSet
& rSet
, bool bFwd
,
211 bool bNoCollections
)
216 , m_aComapeSet( *rSet
.GetPool(), svl::Items
<RES_CHRATR_BEGIN
, RES_TXTATR_END
-1> )
217 , m_bNoColls(bNoCollections
)
220 m_aComapeSet
.Put( rSet
, false );
222 // determine area of Fnd/Stack array (Min/Max)
223 SfxItemIter
aIter( m_aComapeSet
);
224 m_nArrStart
= m_aComapeSet
.GetWhichByPos( aIter
.GetFirstPos() );
225 m_nArrLen
= m_aComapeSet
.GetWhichByPos( aIter
.GetLastPos() ) - m_nArrStart
+1;
227 char* pFndChar
= new char[ m_nArrLen
* sizeof(SwSrchChrAttr
) ];
228 char* pStackChar
= new char[ m_nArrLen
* sizeof(SwSrchChrAttr
) ];
230 m_pFindArr
= reinterpret_cast<SwSrchChrAttr
*>(pFndChar
);
231 m_pStackArr
= reinterpret_cast<SwSrchChrAttr
*>(pStackChar
);
234 SwAttrCheckArr::~SwAttrCheckArr()
236 delete[] reinterpret_cast<char*>(m_pFindArr
);
237 delete[] reinterpret_cast<char*>(m_pStackArr
);
240 void SwAttrCheckArr::SetNewSet( const SwTextNode
& rTextNd
, const SwPaM
& rPam
)
242 std::fill(m_pFindArr
, m_pFindArr
+ m_nArrLen
, SwSrchChrAttr());
243 std::fill(m_pStackArr
, m_pStackArr
+ m_nArrLen
, SwSrchChrAttr());
249 m_nNodeStart
= rPam
.GetPoint()->GetContentIndex();
250 m_nNodeEnd
= rPam
.GetPoint()->GetNode() == rPam
.GetMark()->GetNode()
251 ? rPam
.GetMark()->GetContentIndex()
252 : rTextNd
.GetText().getLength();
256 m_nNodeEnd
= rPam
.GetPoint()->GetContentIndex();
257 m_nNodeStart
= rPam
.GetPoint()->GetNode() == rPam
.GetMark()->GetNode()
258 ? rPam
.GetMark()->GetContentIndex()
262 if( m_bNoColls
&& !rTextNd
.HasSwAttrSet() )
265 const SfxItemSet
& rSet
= rTextNd
.GetSwAttrSet();
267 SfxItemIter
aIter( m_aComapeSet
);
268 const SfxPoolItem
* pItem
= aIter
.GetCurItem();
269 const SfxPoolItem
* pFndItem
;
274 if( IsInvalidItem( pItem
) )
276 nWhich
= m_aComapeSet
.GetWhichByPos( aIter
.GetCurPos() );
277 if( RES_TXTATR_END
<= nWhich
)
278 break; // end of text attributes
280 if( SfxItemState::SET
== rSet
.GetItemState( nWhich
, !m_bNoColls
, &pFndItem
)
281 && !CmpAttr( *pFndItem
, rSet
.GetPool()->GetDefaultItem( nWhich
) ))
283 m_pFindArr
[ nWhich
- m_nArrStart
] =
284 SwSrchChrAttr( *pFndItem
, m_nNodeStart
, m_nNodeEnd
);
290 nWhich
= pItem
->Which();
291 if( RES_TXTATR_END
<= nWhich
)
292 break; // end of text attributes
294 if( CmpAttr( rSet
.Get( nWhich
, !m_bNoColls
), *pItem
) )
296 m_pFindArr
[ nWhich
- m_nArrStart
] =
297 SwSrchChrAttr( *pItem
, m_nNodeStart
, m_nNodeEnd
);
302 pItem
= aIter
.NextItem();
307 lcl_IsAttributeIgnorable(sal_Int32
const nNdStart
, sal_Int32
const nNdEnd
,
308 SwSrchChrAttr
const& rTmp
)
310 // #i115528#: if there is a paragraph attribute, it has been added by the
311 // SwAttrCheckArr ctor, and nFound is 1.
312 // if the paragraph is entirely covered by hints that override the paragraph
313 // attribute, then this function must find an attribute to decrement nFound!
314 // so check for an empty search range, let attributes that start/end there
315 // cover it, and hope for the best...
316 return ((nNdEnd
== nNdStart
)
317 ? ((rTmp
.nEnd
< nNdStart
) || (nNdEnd
< rTmp
.nStt
))
318 : ((rTmp
.nEnd
<= nNdStart
) || (nNdEnd
<= rTmp
.nStt
)));
321 bool SwAttrCheckArr::SetAttrFwd( const SwTextAttr
& rAttr
)
323 SwSrchChrAttr
aTmp( rAttr
.GetAttr(), rAttr
.GetStart(), rAttr
.GetAnyEnd() );
325 // ignore all attributes not in search range
326 if (lcl_IsAttributeIgnorable(m_nNodeStart
, m_nNodeEnd
, aTmp
))
331 const SfxPoolItem
* pItem
;
332 // here we explicitly also search in character templates
333 sal_uInt16 nWhch
= rAttr
.Which();
334 std::optional
<SfxWhichIter
> oIter
;
335 const SfxPoolItem
* pTmpItem
= nullptr;
336 const SfxItemSet
* pSet
= nullptr;
337 if( RES_TXTATR_CHARFMT
== nWhch
|| RES_TXTATR_AUTOFMT
== nWhch
)
339 if( m_bNoColls
&& RES_TXTATR_CHARFMT
== nWhch
)
342 pSet
= CharFormat::GetItemSet( rAttr
.GetAttr() );
345 oIter
.emplace( *pSet
);
346 nWhch
= oIter
->FirstWhich();
348 SfxItemState::SET
!= oIter
->GetItemState( true, &pTmpItem
) )
349 nWhch
= oIter
->NextWhich();
355 pTmpItem
= &rAttr
.GetAttr();
359 SfxItemState eState
= m_aComapeSet
.GetItemState( nWhch
, false, &pItem
);
360 if( SfxItemState::DONTCARE
== eState
|| SfxItemState::SET
== eState
)
365 // first delete all up to start position that are already invalid
366 SwSrchChrAttr
* pArrPtr
;
368 for( pArrPtr
= m_pFindArr
, n
= 0; n
< m_nArrLen
;
370 if( pArrPtr
->nWhich
&& pArrPtr
->nEnd
<= aTmp
.nStt
)
372 pArrPtr
->nWhich
= 0; // deleted
376 // delete all up to start position that are already invalid and
377 // move all "open" ones (= stick out over start position) from stack
380 for( pArrPtr
= m_pStackArr
, n
=0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
382 if( !pArrPtr
->nWhich
)
385 if( pArrPtr
->nEnd
<= aTmp
.nStt
)
387 pArrPtr
->nWhich
= 0; // deleted
388 if( !--m_nStackCount
)
391 else if( pArrPtr
->nStt
<= aTmp
.nStt
)
393 pCmp
= &m_pFindArr
[ n
];
396 if( pCmp
->nEnd
< pArrPtr
->nEnd
) // extend
397 pCmp
->nEnd
= pArrPtr
->nEnd
;
405 if( !--m_nStackCount
)
410 bool bContinue
= false;
412 if( SfxItemState::DONTCARE
== eState
)
414 // Will the attribute become valid?
415 if( !CmpAttr( m_aComapeSet
.GetPool()->GetDefaultItem( nWhch
),
418 // search attribute and extend if needed
419 pCmp
= &m_pFindArr
[ nWhch
- m_nArrStart
];
422 *pCmp
= aTmp
; // not found, insert
425 else if( pCmp
->nEnd
< aTmp
.nEnd
) // extend?
426 pCmp
->nEnd
= aTmp
.nEnd
;
431 // Will the attribute become valid?
432 else if( CmpAttr( *pItem
, *pTmpItem
) )
434 m_pFindArr
[ nWhch
- m_nArrStart
] = aTmp
;
439 // then is has to go on the stack
442 pCmp
= &m_pFindArr
[ nWhch
- m_nArrStart
];
445 // exists on stack, only if it is even bigger
446 if( pCmp
->nEnd
> aTmp
.nEnd
)
448 OSL_ENSURE( !m_pStackArr
[ nWhch
- m_nArrStart
].nWhich
,
449 "slot on stack is still in use" );
451 if( aTmp
.nStt
<= pCmp
->nStt
)
452 pCmp
->nStt
= aTmp
.nEnd
;
454 pCmp
->nEnd
= aTmp
.nStt
;
456 m_pStackArr
[ nWhch
- m_nArrStart
] = *pCmp
;
466 assert(pSet
&& "otherwise no oIter");
467 nWhch
= oIter
->NextWhich();
469 SfxItemState::SET
!= oIter
->GetItemState( true, &pTmpItem
) )
470 nWhch
= oIter
->NextWhich();
481 bool SwAttrCheckArr::SetAttrBwd( const SwTextAttr
& rAttr
)
483 SwSrchChrAttr
aTmp( rAttr
.GetAttr(), rAttr
.GetStart(), rAttr
.GetAnyEnd() );
485 // ignore all attributes not in search range
486 if (lcl_IsAttributeIgnorable(m_nNodeStart
, m_nNodeEnd
, aTmp
))
491 const SfxPoolItem
* pItem
;
492 // here we explicitly also search in character templates
493 sal_uInt16 nWhch
= rAttr
.Which();
494 std::optional
<SfxWhichIter
> oIter
;
495 const SfxPoolItem
* pTmpItem
= nullptr;
496 const SfxItemSet
* pSet
= nullptr;
497 if( RES_TXTATR_CHARFMT
== nWhch
|| RES_TXTATR_AUTOFMT
== nWhch
)
499 if( m_bNoColls
&& RES_TXTATR_CHARFMT
== nWhch
)
502 pSet
= CharFormat::GetItemSet( rAttr
.GetAttr() );
505 oIter
.emplace( *pSet
);
506 nWhch
= oIter
->FirstWhich();
508 SfxItemState::SET
!= oIter
->GetItemState( true, &pTmpItem
) )
509 nWhch
= oIter
->NextWhich();
515 pTmpItem
= &rAttr
.GetAttr();
519 SfxItemState eState
= m_aComapeSet
.GetItemState( nWhch
, false, &pItem
);
520 if( SfxItemState::DONTCARE
== eState
|| SfxItemState::SET
== eState
)
525 // first delete all up to start position that are already invalid
526 SwSrchChrAttr
* pArrPtr
;
528 for( pArrPtr
= m_pFindArr
, n
= 0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
529 if( pArrPtr
->nWhich
&& pArrPtr
->nStt
>= aTmp
.nEnd
)
531 pArrPtr
->nWhich
= 0; // deleted
535 // delete all up to start position that are already invalid and
536 // move all "open" ones (= stick out over start position) from stack
539 for( pArrPtr
= m_pStackArr
, n
= 0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
541 if( !pArrPtr
->nWhich
)
544 if( pArrPtr
->nStt
>= aTmp
.nEnd
)
546 pArrPtr
->nWhich
= 0; // deleted
547 if( !--m_nStackCount
)
550 else if( pArrPtr
->nEnd
>= aTmp
.nEnd
)
552 pCmp
= &m_pFindArr
[ n
];
555 if( pCmp
->nStt
> pArrPtr
->nStt
) // extend
556 pCmp
->nStt
= pArrPtr
->nStt
;
564 if( !--m_nStackCount
)
569 bool bContinue
= false;
570 if( SfxItemState::DONTCARE
== eState
)
572 // Will the attribute become valid?
573 if( !CmpAttr( m_aComapeSet
.GetPool()->GetDefaultItem( nWhch
),
576 // search attribute and extend if needed
577 pCmp
= &m_pFindArr
[ nWhch
- m_nArrStart
];
580 *pCmp
= aTmp
; // not found, insert
583 else if( pCmp
->nStt
> aTmp
.nStt
) // extend?
584 pCmp
->nStt
= aTmp
.nStt
;
589 // Will the attribute become valid?
590 else if( CmpAttr( *pItem
, *pTmpItem
))
592 m_pFindArr
[ nWhch
- m_nArrStart
] = aTmp
;
597 // then is has to go on the stack
600 pCmp
= &m_pFindArr
[ nWhch
- m_nArrStart
];
603 // exists on stack, only if it is even bigger
604 if( pCmp
->nStt
< aTmp
.nStt
)
606 OSL_ENSURE( !m_pStackArr
[ nWhch
- m_nArrStart
].nWhich
,
607 "slot on stack is still in use" );
609 if( aTmp
.nEnd
<= pCmp
->nEnd
)
610 pCmp
->nEnd
= aTmp
.nStt
;
612 pCmp
->nStt
= aTmp
.nEnd
;
614 m_pStackArr
[ nWhch
- m_nArrStart
] = *pCmp
;
624 assert(pSet
&& "otherwise no oIter");
625 nWhch
= oIter
->NextWhich();
627 SfxItemState::SET
!= oIter
->GetItemState( true, &pTmpItem
) )
628 nWhch
= oIter
->NextWhich();
639 sal_Int32
SwAttrCheckArr::Start() const
641 sal_Int32 nStart
= m_nNodeStart
;
642 SwSrchChrAttr
* pArrPtr
= m_pFindArr
;
643 for( sal_uInt16 n
= 0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
644 if( pArrPtr
->nWhich
&& pArrPtr
->nStt
> nStart
)
645 nStart
= pArrPtr
->nStt
;
650 sal_Int32
SwAttrCheckArr::End() const
652 SwSrchChrAttr
* pArrPtr
= m_pFindArr
;
653 sal_Int32 nEnd
= m_nNodeEnd
;
654 for( sal_uInt16 n
= 0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
655 if( pArrPtr
->nWhich
&& pArrPtr
->nEnd
< nEnd
)
656 nEnd
= pArrPtr
->nEnd
;
661 bool SwAttrCheckArr::CheckStack()
667 const sal_Int32 nSttPos
= Start();
668 const sal_Int32 nEndPos
= End();
669 SwSrchChrAttr
* pArrPtr
;
670 for( pArrPtr
= m_pStackArr
, n
= 0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
672 if( !pArrPtr
->nWhich
)
675 if( m_bForward
? pArrPtr
->nEnd
<= nSttPos
: pArrPtr
->nStt
>= nEndPos
)
677 pArrPtr
->nWhich
= 0; // deleted
678 if( !--m_nStackCount
)
679 return m_nFound
== m_aComapeSet
.Count();
681 else if( m_bForward
? pArrPtr
->nStt
< nEndPos
: pArrPtr
->nEnd
> nSttPos
)
683 // move all "open" ones (= stick out over start position) into FndSet
684 OSL_ENSURE( !m_pFindArr
[ n
].nWhich
, "slot in array is already in use" );
685 m_pFindArr
[ n
] = *pArrPtr
;
688 if( !--m_nStackCount
)
689 return m_nFound
== m_aComapeSet
.Count();
692 return m_nFound
== m_aComapeSet
.Count();
695 static bool lcl_SearchForward( const SwTextNode
& rTextNd
, SwAttrCheckArr
& rCmpArr
,
699 rCmpArr
.SetNewSet( rTextNd
, rPam
);
700 if( !rTextNd
.HasHints() )
702 if( !rCmpArr
.Found() )
704 nEndPos
= rCmpArr
.GetNdEnd();
705 lcl_SetAttrPam( rPam
, rCmpArr
.GetNdStt(), &nEndPos
, true );
709 const SwpHints
& rHtArr
= rTextNd
.GetSwpHints();
710 const SwTextAttr
* pAttr
;
713 // if everything is already there then check with which it will be ended
714 if( rCmpArr
.Found() )
716 for( ; nPos
< rHtArr
.Count(); ++nPos
)
718 pAttr
= rHtArr
.Get( nPos
);
719 if( !rCmpArr
.SetAttrFwd( *pAttr
) )
721 if( rCmpArr
.GetNdStt() < pAttr
->GetStart() )
724 auto nTmpStart
= pAttr
->GetStart();
725 lcl_SetAttrPam( rPam
, rCmpArr
.GetNdStt(),
734 if( nPos
== rHtArr
.Count() && rCmpArr
.Found() )
737 nEndPos
= rCmpArr
.GetNdEnd();
738 lcl_SetAttrPam( rPam
, rCmpArr
.GetNdStt(), &nEndPos
, true );
744 for( ; nPos
< rHtArr
.Count(); ++nPos
)
746 pAttr
= rHtArr
.Get( nPos
);
747 if( rCmpArr
.SetAttrFwd( *pAttr
) )
749 // Do multiple start at that position? Do also check those:
750 nSttPos
= pAttr
->GetStart();
751 while( ++nPos
< rHtArr
.Count() )
753 pAttr
= rHtArr
.Get( nPos
);
754 if( nSttPos
!= pAttr
->GetStart() || !rCmpArr
.SetAttrFwd( *pAttr
) )
758 if( !rCmpArr
.Found() )
761 // then we have our search area
762 nSttPos
= rCmpArr
.Start();
763 nEndPos
= rCmpArr
.End();
764 if( nSttPos
> nEndPos
)
767 lcl_SetAttrPam( rPam
, nSttPos
, &nEndPos
, true );
772 if( !rCmpArr
.CheckStack() )
774 nSttPos
= rCmpArr
.Start();
775 nEndPos
= rCmpArr
.End();
776 if( nSttPos
> nEndPos
)
779 lcl_SetAttrPam( rPam
, nSttPos
, &nEndPos
, true );
783 static bool lcl_SearchBackward( const SwTextNode
& rTextNd
, SwAttrCheckArr
& rCmpArr
,
787 rCmpArr
.SetNewSet( rTextNd
, rPam
);
788 if( !rTextNd
.HasHints() )
790 if( !rCmpArr
.Found() )
792 nEndPos
= rCmpArr
.GetNdEnd();
793 lcl_SetAttrPam( rPam
, rCmpArr
.GetNdStt(), &nEndPos
, false );
797 const SwpHints
& rHtArr
= rTextNd
.GetSwpHints();
798 const SwTextAttr
* pAttr
;
799 size_t nPos
= rHtArr
.Count();
802 // if everything is already there then check with which it will be ended
803 if( rCmpArr
.Found() )
807 pAttr
= rHtArr
.GetSortedByEnd( --nPos
);
808 if( !rCmpArr
.SetAttrBwd( *pAttr
) )
810 nSttPos
= pAttr
->GetAnyEnd();
811 if( nSttPos
< rCmpArr
.GetNdEnd() )
814 nEndPos
= rCmpArr
.GetNdEnd();
815 lcl_SetAttrPam( rPam
, nSttPos
, &nEndPos
, false );
824 if( !nPos
&& rCmpArr
.Found() )
827 nEndPos
= rCmpArr
.GetNdEnd();
828 lcl_SetAttrPam( rPam
, rCmpArr
.GetNdStt(), &nEndPos
, false );
835 pAttr
= rHtArr
.GetSortedByEnd( --nPos
);
836 if( rCmpArr
.SetAttrBwd( *pAttr
) )
838 // Do multiple start at that position? Do also check those:
841 nEndPos
= pAttr
->GetAnyEnd();
844 pAttr
= rHtArr
.GetSortedByEnd( nPos
);
845 if( nEndPos
!= pAttr
->GetAnyEnd() || !rCmpArr
.SetAttrBwd( *pAttr
) )
849 if( !rCmpArr
.Found() )
852 // then we have our search area
853 nSttPos
= rCmpArr
.Start();
854 nEndPos
= rCmpArr
.End();
855 if( nSttPos
> nEndPos
)
858 lcl_SetAttrPam( rPam
, nSttPos
, &nEndPos
, false );
863 if( !rCmpArr
.CheckStack() )
865 nSttPos
= rCmpArr
.Start();
866 nEndPos
= rCmpArr
.End();
867 if( nSttPos
> nEndPos
)
870 lcl_SetAttrPam( rPam
, nSttPos
, &nEndPos
, false );
874 static bool lcl_Search( const SwContentNode
& rCNd
, const SfxItemSet
& rCmpSet
, bool bNoColls
)
876 // search only hard attribution?
877 if( bNoColls
&& !rCNd
.HasSwAttrSet() )
880 const SfxItemSet
& rNdSet
= rCNd
.GetSwAttrSet();
881 SfxItemIter
aIter( rCmpSet
);
882 const SfxPoolItem
* pItem
= aIter
.GetCurItem();
883 const SfxPoolItem
* pNdItem
;
888 if( IsInvalidItem( pItem
))
890 nWhich
= rCmpSet
.GetWhichByPos( aIter
.GetCurPos() );
891 if( SfxItemState::SET
!= rNdSet
.GetItemState( nWhich
, !bNoColls
, &pNdItem
)
892 || CmpAttr( *pNdItem
, rNdSet
.GetPool()->GetDefaultItem( nWhich
) ))
897 nWhich
= pItem
->Which();
899 if( !CmpAttr( rNdSet
.Get( nWhich
, !bNoColls
), *pItem
))
903 pItem
= aIter
.NextItem();
905 return true; // found
910 bool FindAttrImpl(SwPaM
& rSearchPam
,
911 const SfxPoolItem
& rAttr
, SwMoveFnCollection
const & fnMove
,
912 const SwPaM
& rRegion
, bool bInReadOnly
,
913 SwRootFrame
const*const pLayout
)
915 // determine which attribute is searched:
916 const sal_uInt16 nWhich
= rAttr
.Which();
917 bool bCharAttr
= isCHRATR(nWhich
) || isTXTATR(nWhich
);
918 assert(isTXTATR(nWhich
)); // sw_redlinehide: only works for non-formatting hints such as needed in UpdateFields; use FindAttrsImpl for others
920 std::optional
<SwPaM
> oPam
;
921 sw::MakeRegion(fnMove
, rRegion
, oPam
);
925 const bool bSrchForward
= &fnMove
== &fnMoveForward
;
926 SwContentNode
* pNode
;
928 // if at beginning/end then move it out of the node
930 ? oPam
->GetPoint()->GetContentIndex() == oPam
->GetPointContentNode()->Len()
931 : !oPam
->GetPoint()->GetContentIndex() )
933 if( !(*fnMove
.fnPos
)( oPam
->GetPoint(), false ))
937 SwContentNode
*pNd
= oPam
->GetPointContentNode();
938 oPam
->GetPoint()->SetContent( bSrchForward
? 0 : pNd
->Len() );
941 while (nullptr != (pNode
= ::GetNode(*oPam
, bFirst
, fnMove
, bInReadOnly
, pLayout
)))
945 if( !pNode
->IsTextNode() ) // CharAttr are only in text nodes
948 SwTextFrame
const*const pFrame(pLayout
949 ? static_cast<SwTextFrame
const*>(pNode
->getLayoutFrame(pLayout
))
953 SwTextNode
const* pAttrNode(nullptr);
954 SwTextAttr
const* pAttr(nullptr);
957 sw::MergedAttrIter
iter(*pFrame
);
960 pAttr
= iter
.NextAttr(&pAttrNode
);
963 && (pAttrNode
->GetIndex() < oPam
->GetPoint()->GetNodeIndex()
964 || (pAttrNode
->GetIndex() == oPam
->GetPoint()->GetNodeIndex()
965 && pAttr
->GetStart() < oPam
->GetPoint()->GetContentIndex())
966 || pAttr
->Which() != nWhich
));
970 sw::MergedAttrIterReverse
iter(*pFrame
);
973 pAttr
= iter
.PrevAttr(&pAttrNode
);
976 && (oPam
->GetPoint()->GetNodeIndex() < pAttrNode
->GetIndex()
977 || (oPam
->GetPoint()->GetNodeIndex() == pAttrNode
->GetIndex()
978 && oPam
->GetPoint()->GetContentIndex() <= pAttr
->GetStart())
979 || pAttr
->Which() != nWhich
));
984 oPam
->GetPoint()->Assign(*pAttrNode
);
985 lcl_SetAttrPam(*oPam
, pAttr
->GetStart(), pAttr
->End(), bSrchForward
);
990 else if (!pLayout
&& pNode
->GetTextNode()->HasHints() &&
991 lcl_SearchAttr(*pNode
->GetTextNode(), *oPam
, rAttr
, fnMove
))
997 // set to the values of the attribute
998 rSearchPam
.SetMark();
999 *rSearchPam
.GetPoint() = *oPam
->GetPoint();
1000 *rSearchPam
.GetMark() = *oPam
->GetMark();
1003 else if (isTXTATR(nWhich
))
1008 // no hard attribution, so check if node was asked for this attr before
1009 if( !pNode
->HasSwAttrSet() )
1011 SwFormat
* pTmpFormat
= pNode
->GetFormatColl();
1012 if( !aFormatArr
.insert( pTmpFormat
).second
)
1013 continue; // collection was requested earlier
1016 if( SfxItemState::SET
== pNode
->GetSwAttrSet().GetItemState( nWhich
,
1019 // FORWARD: SPoint at the end, GetMark at the beginning of the node
1020 // BACKWARD: SPoint at the beginning, GetMark at the end of the node
1021 // always: incl. start and incl. end
1022 *rSearchPam
.GetPoint() = *pPam
->GetPoint();
1023 rSearchPam
.SetMark();
1024 rSearchPam
.GetPoint()->SetContent(pNode
->Len());
1031 // if backward search, switch point and mark
1032 if( bFound
&& !bSrchForward
)
1033 rSearchPam
.Exchange();
1040 typedef bool (*FnSearchAttr
)( const SwTextNode
&, SwAttrCheckArr
&, SwPaM
& );
1042 static bool FindAttrsImpl(SwPaM
& rSearchPam
,
1043 const SfxItemSet
& rSet
, bool bNoColls
, SwMoveFnCollection
const & fnMove
,
1044 const SwPaM
& rRegion
, bool bInReadOnly
, bool bMoveFirst
,
1045 SwRootFrame
const*const pLayout
)
1047 std::optional
<SwPaM
> oPam
;
1048 sw::MakeRegion(fnMove
, rRegion
, oPam
);
1050 bool bFound
= false;
1052 const bool bSrchForward
= &fnMove
== &fnMoveForward
;
1053 SwContentNode
* pNode
;
1054 o3tl::sorted_vector
<SwFormat
*> aFormatArr
;
1056 // check which text/char attributes are searched
1057 SwAttrCheckArr
aCmpArr( rSet
, bSrchForward
, bNoColls
);
1058 SfxItemSetFixed
<RES_PARATR_BEGIN
, RES_GRFATR_END
-1> aOtherSet( rSearchPam
.GetDoc().GetAttrPool() );
1059 aOtherSet
.Put( rSet
, false ); // got all invalid items
1061 FnSearchAttr fnSearch
= bSrchForward
1062 ? (&::lcl_SearchForward
)
1063 : (&::lcl_SearchBackward
);
1065 // if at beginning/end then move it out of the node
1068 ? oPam
->GetPoint()->GetContentIndex() == oPam
->GetPointContentNode()->Len()
1069 : !oPam
->GetPoint()->GetContentIndex() ) )
1071 if( !(*fnMove
.fnPos
)( oPam
->GetPoint(), false ))
1075 SwContentNode
*pNd
= oPam
->GetPointContentNode();
1076 oPam
->GetPoint()->SetContent( bSrchForward
? 0 : pNd
->Len() );
1079 while (nullptr != (pNode
= ::GetNode(*oPam
, bFirst
, fnMove
, bInReadOnly
, pLayout
)))
1081 SwTextFrame
const*const pFrame(pLayout
&& pNode
->IsTextNode()
1082 ? static_cast<SwTextFrame
const*>(pNode
->getLayoutFrame(pLayout
))
1084 assert(!pLayout
|| !pNode
->IsTextNode() || pFrame
);
1085 // sw_redlinehide: it's apparently not possible to find break items
1086 // with the UI, so checking one node is enough
1087 SwContentNode
const& rPropsNode(*(pFrame
1088 ? pFrame
->GetTextNodeForParaProps()
1091 if( aCmpArr
.Count() )
1093 if( !pNode
->IsTextNode() ) // CharAttr are only in text nodes
1096 if (aOtherSet
.Count() &&
1097 !lcl_Search(rPropsNode
, aOtherSet
, bNoColls
))
1101 sw::MergedPara
const*const pMergedPara(pFrame
? pFrame
->GetMergedPara() : nullptr);
1104 SwPosition
const& rStart(*oPam
->Start());
1105 SwPosition
const& rEnd(*oPam
->End());
1106 // no extents? fall back to searching index 0 of propsnode
1107 // to find its node items
1108 if (pMergedPara
->extents
.empty())
1110 if (rStart
.GetNodeIndex() <= rPropsNode
.GetIndex()
1111 && rPropsNode
.GetIndex() <= rEnd
.GetNodeIndex())
1113 SwPaM
tmp(rPropsNode
, 0, rPropsNode
, 0);
1114 bFound
= (*fnSearch
)(*pNode
->GetTextNode(), aCmpArr
, tmp
);
1123 // iterate the extents, and intersect with input pPam:
1124 // the found ranges should never include delete redlines
1125 // so that subsequent Replace will not affect them
1126 for (size_t i
= 0; i
< pMergedPara
->extents
.size(); ++i
)
1128 auto const rExtent(pMergedPara
->extents
[bSrchForward
1130 : pMergedPara
->extents
.size() - i
- 1]);
1131 if (rExtent
.pNode
->GetIndex() < rStart
.GetNodeIndex()
1132 || rEnd
.GetNodeIndex() < rExtent
.pNode
->GetIndex())
1136 sal_Int32
const nStart(rExtent
.pNode
== &rStart
.GetNode()
1137 ? rStart
.GetContentIndex()
1139 if (rExtent
.nEnd
<= nStart
)
1143 sal_Int32
const nEnd(rExtent
.pNode
== &rEnd
.GetNode()
1144 ? rEnd
.GetContentIndex()
1145 : rExtent
.pNode
->Len());
1146 if (nEnd
< rExtent
.nStart
1147 || (nStart
!= nEnd
&& nEnd
== rExtent
.nStart
))
1151 SwPaM
tmp(*rExtent
.pNode
, std::max(nStart
, rExtent
.nStart
),
1152 *rExtent
.pNode
, std::min(nEnd
, rExtent
.nEnd
));
1153 tmp
.Normalize(bSrchForward
);
1154 bFound
= (*fnSearch
)(*rExtent
.pNode
, aCmpArr
, tmp
);
1165 bFound
= (*fnSearch
)(*pNode
->GetTextNode(), aCmpArr
, *oPam
);
1169 // set to the values of the attribute
1170 rSearchPam
.SetMark();
1171 *rSearchPam
.GetPoint() = *oPam
->GetPoint();
1172 *rSearchPam
.GetMark() = *oPam
->GetMark();
1175 continue; // text attribute
1178 if( !aOtherSet
.Count() )
1181 // no hard attribution, so check if node was asked for this attr before
1182 // (as an optimisation)
1183 if (!rPropsNode
.HasSwAttrSet())
1185 SwFormat
* pTmpFormat
= rPropsNode
.GetFormatColl();
1186 if( !aFormatArr
.insert( pTmpFormat
).second
)
1187 continue; // collection was requested earlier
1190 if (lcl_Search(rPropsNode
, aOtherSet
, bNoColls
))
1192 // FORWARD: SPoint at the end, GetMark at the beginning of the node
1193 // BACKWARD: SPoint at the beginning, GetMark at the end of the node
1196 *rSearchPam
.GetPoint() = *oPam
->GetPoint();
1197 rSearchPam
.SetMark();
1198 *rSearchPam
.GetMark() = pFrame
->MapViewToModelPos(
1199 TextFrameIndex(bSrchForward
? pFrame
->GetText().getLength() : 0));
1203 *rSearchPam
.GetPoint() = *oPam
->GetPoint();
1204 rSearchPam
.SetMark();
1207 rSearchPam
.GetPoint()->SetContent(pNode
->Len());
1211 rSearchPam
.GetPoint()->SetContent(0);
1219 // in search direction, mark precedes point, because the next iteration
1223 rSearchPam
.Normalize(!bSrchForward
);
1231 /// parameters for search for attributes
1232 struct SwFindParaAttr
: public SwFindParas
1234 bool m_bNoCollection
;
1235 const SfxItemSet
*pSet
, *pReplSet
;
1236 const i18nutil::SearchOptions2
*pSearchOpt
;
1237 SwCursor
& m_rCursor
;
1238 SwRootFrame
const* m_pLayout
;
1239 std::unique_ptr
<utl::TextSearch
> pSText
;
1241 SwFindParaAttr( const SfxItemSet
& rSet
, bool bNoCollection
,
1242 const i18nutil::SearchOptions2
* pOpt
, const SfxItemSet
* pRSet
,
1243 SwCursor
& rCursor
, SwRootFrame
const*const pLayout
)
1244 : m_bNoCollection(bNoCollection
)
1247 , pSearchOpt( pOpt
)
1248 , m_rCursor(rCursor
)
1249 , m_pLayout(pLayout
)
1252 virtual ~SwFindParaAttr() {}
1254 virtual int DoFind(SwPaM
&, SwMoveFnCollection
const &, const SwPaM
&, bool bInReadOnly
,
1255 std::unique_ptr
<SvxSearchItem
>& xSearchItem
) override
;
1256 virtual bool IsReplaceMode() const override
;
1261 int SwFindParaAttr::DoFind(SwPaM
& rCursor
, SwMoveFnCollection
const & fnMove
,
1262 const SwPaM
& rRegion
, bool bInReadOnly
,
1263 std::unique_ptr
<SvxSearchItem
>& xSearchItem
)
1265 // replace string (only if text given and search is not parameterized)?
1266 bool bReplaceText
= pSearchOpt
&& ( !pSearchOpt
->replaceString
.isEmpty() ||
1268 bool bReplaceAttr
= pReplSet
&& pReplSet
->Count();
1269 bool bMoveFirst
= !bReplaceAttr
;
1270 if( bInReadOnly
&& (bReplaceAttr
|| bReplaceText
))
1271 bInReadOnly
= false;
1273 // We search for attributes, should we search for text as well?
1275 SwPaM
aRegion( *rRegion
.GetMark(), *rRegion
.GetPoint() );
1276 SwPaM
* pTextRegion
= &aRegion
;
1277 SwPaM
aSrchPam( *rCursor
.GetPoint() );
1281 if( pSet
->Count() ) // any attributes?
1284 if (!FindAttrsImpl(aSrchPam
, *pSet
, m_bNoCollection
, fnMove
, aRegion
, bInReadOnly
, bMoveFirst
, m_pLayout
))
1285 return FIND_NOT_FOUND
;
1289 break; // ok, only attributes, so found
1291 pTextRegion
= &aSrchPam
;
1293 else if( !pSearchOpt
)
1294 return FIND_NOT_FOUND
;
1296 // then search in text of it
1299 i18nutil::SearchOptions2
aTmp( *pSearchOpt
);
1301 // search in selection
1302 aTmp
.searchFlag
|= (SearchFlags::REG_NOT_BEGINOFLINE
|
1303 SearchFlags::REG_NOT_ENDOFLINE
);
1305 aTmp
.Locale
= SvtSysLocale().GetLanguageTag().getLocale();
1307 pSText
.reset( new utl::TextSearch( aTmp
) );
1310 // TODO: searching for attributes in Outliner text?!
1312 // continue search in correct section (pTextRegion)
1313 if (sw::FindTextImpl(aSrchPam
, *pSearchOpt
, false/*bSearchInNotes*/, *pSText
, fnMove
, *pTextRegion
, bInReadOnly
, m_pLayout
, xSearchItem
) &&
1314 *aSrchPam
.GetMark() != *aSrchPam
.GetPoint() )
1316 else if( !pSet
->Count() )
1317 return FIND_NOT_FOUND
; // only text and nothing found
1319 *aRegion
.GetMark() = *aSrchPam
.GetPoint();
1322 *rCursor
.GetPoint() = *aSrchPam
.GetPoint();
1324 *rCursor
.GetMark() = *aSrchPam
.GetMark();
1330 SearchAlgorithms2::REGEXP
== pSearchOpt
->AlgorithmType2
);
1331 SwPosition
& rSttCntPos
= *rCursor
.Start();
1332 const sal_Int32 nSttCnt
= rSttCntPos
.GetContentIndex();
1334 // add to shell-cursor-ring so that the regions will be moved eventually
1335 SwPaM
* pPrevRing(nullptr);
1338 pPrevRing
= const_cast<SwPaM
&>(rRegion
).GetPrev();
1339 const_cast<SwPaM
&>(rRegion
).GetRingContainer().merge( m_rCursor
.GetRingContainer() );
1342 std::optional
<OUString
> xRepl
;
1344 xRepl
= sw::ReplaceBackReferences(*pSearchOpt
, &rCursor
, m_pLayout
);
1345 sw::ReplaceImpl(rCursor
,
1346 xRepl
? *xRepl
: pSearchOpt
->replaceString
, bRegExp
,
1347 m_rCursor
.GetDoc(), m_pLayout
);
1349 m_rCursor
.SaveTableBoxContent( rCursor
.GetPoint() );
1353 // and remove region again
1355 SwPaM
* pNext
= const_cast<SwPaM
*>(&rRegion
);
1358 pNext
= p
->GetNext();
1359 p
->MoveTo(const_cast<SwPaM
*>(&rRegion
));
1360 } while( p
!= pPrevRing
);
1362 rSttCntPos
.SetContent(nSttCnt
);
1367 // is the selection still existent?
1368 // all searched attributes are reset to default if
1369 // they are not in ReplaceSet
1370 if( !pSet
->Count() )
1372 rCursor
.GetDoc().getIDocumentContentOperations().InsertItemSet(
1373 rCursor
, *pReplSet
, SetAttrMode::DEFAULT
, m_pLayout
);
1377 SfxItemPool
* pPool
= pReplSet
->GetPool();
1378 SfxItemSet
aSet( *pPool
, pReplSet
->GetRanges() );
1380 SfxItemIter
aIter( *pSet
);
1381 const SfxPoolItem
* pItem
= aIter
.GetCurItem();
1384 // reset all that are not set with pool defaults
1385 if( !IsInvalidItem( pItem
) && SfxItemState::SET
!=
1386 pReplSet
->GetItemState( pItem
->Which(), false ))
1387 aSet
.Put( pPool
->GetDefaultItem( pItem
->Which() ));
1389 pItem
= aIter
.NextItem();
1391 aSet
.Put( *pReplSet
);
1392 rCursor
.GetDoc().getIDocumentContentOperations().InsertItemSet(
1393 rCursor
, aSet
, SetAttrMode::DEFAULT
, m_pLayout
);
1396 return FIND_NO_RING
;
1402 bool SwFindParaAttr::IsReplaceMode() const
1404 return ( pSearchOpt
&& !pSearchOpt
->replaceString
.isEmpty() ) ||
1405 ( pReplSet
&& pReplSet
->Count() );
1408 /// search for attributes
1409 sal_Int32
SwCursor::FindAttrs( const SfxItemSet
& rSet
, bool bNoCollections
,
1410 SwDocPositions nStart
, SwDocPositions nEnd
,
1411 bool& bCancel
, FindRanges eFndRngs
,
1412 const i18nutil::SearchOptions2
* pSearchOpt
,
1413 const SfxItemSet
* pReplSet
,
1414 SwRootFrame
const*const pLayout
)
1416 // switch off OLE-notifications
1417 SwDoc
& rDoc
= GetDoc();
1418 Link
<bool,void> aLnk( rDoc
.GetOle2Link() );
1419 rDoc
.SetOle2Link( Link
<bool,void>() );
1421 bool bReplace
= ( pSearchOpt
&& ( !pSearchOpt
->replaceString
.isEmpty() ||
1422 !rSet
.Count() ) ) ||
1423 (pReplSet
&& pReplSet
->Count());
1424 bool const bStartUndo
= rDoc
.GetIDocumentUndoRedo().DoesUndo() && bReplace
;
1427 rDoc
.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE
, nullptr );
1430 SwFindParaAttr
aSwFindParaAttr( rSet
, bNoCollections
, pSearchOpt
,
1431 pReplSet
, *this, pLayout
);
1433 sal_Int32 nRet
= FindAll( aSwFindParaAttr
, nStart
, nEnd
, eFndRngs
, bCancel
);
1434 rDoc
.SetOle2Link( aLnk
);
1435 if( nRet
&& bReplace
)
1436 rDoc
.getIDocumentState().SetModified();
1440 rDoc
.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE
, nullptr );
1446 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */