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 sal_uInt16
nMinUsedWhichID(0);
224 sal_uInt16
nMaxUsedWhichID(0);
226 if (0 != m_aComapeSet
.Count())
228 nMinUsedWhichID
= 5000; // SFX_WHICH_MAX+1;
229 for (SfxItemIter
aIter(m_aComapeSet
); !aIter
.IsAtEnd(); aIter
.NextItem())
231 const sal_uInt16
nCurrentWhich(aIter
.GetCurWhich());
232 if (SfxItemPool::IsSlot(nCurrentWhich
))
234 nMinUsedWhichID
= std::min(nMinUsedWhichID
, nCurrentWhich
);
235 nMaxUsedWhichID
= std::max(nMaxUsedWhichID
, nCurrentWhich
);
238 if (nMinUsedWhichID
> nMaxUsedWhichID
)
239 nMinUsedWhichID
= nMaxUsedWhichID
= 0;
242 m_nArrStart
= nMinUsedWhichID
;//m_aComapeSet.GetWhichByOffset( aIter.GetFirstPos() );
243 m_nArrLen
= nMaxUsedWhichID
- nMinUsedWhichID
+ 1;//m_aComapeSet.GetWhichByOffset( aIter.GetLastPos() ) - m_nArrStart+1;
245 char* pFndChar
= new char[ m_nArrLen
* sizeof(SwSrchChrAttr
) ];
246 char* pStackChar
= new char[ m_nArrLen
* sizeof(SwSrchChrAttr
) ];
248 m_pFindArr
= reinterpret_cast<SwSrchChrAttr
*>(pFndChar
);
249 m_pStackArr
= reinterpret_cast<SwSrchChrAttr
*>(pStackChar
);
252 SwAttrCheckArr::~SwAttrCheckArr()
254 delete[] reinterpret_cast<char*>(m_pFindArr
);
255 delete[] reinterpret_cast<char*>(m_pStackArr
);
258 void SwAttrCheckArr::SetNewSet( const SwTextNode
& rTextNd
, const SwPaM
& rPam
)
260 std::fill(m_pFindArr
, m_pFindArr
+ m_nArrLen
, SwSrchChrAttr());
261 std::fill(m_pStackArr
, m_pStackArr
+ m_nArrLen
, SwSrchChrAttr());
267 m_nNodeStart
= rPam
.GetPoint()->GetContentIndex();
268 m_nNodeEnd
= rPam
.GetPoint()->GetNode() == rPam
.GetMark()->GetNode()
269 ? rPam
.GetMark()->GetContentIndex()
270 : rTextNd
.GetText().getLength();
274 m_nNodeEnd
= rPam
.GetPoint()->GetContentIndex();
275 m_nNodeStart
= rPam
.GetPoint()->GetNode() == rPam
.GetMark()->GetNode()
276 ? rPam
.GetMark()->GetContentIndex()
280 if( m_bNoColls
&& !rTextNd
.HasSwAttrSet() )
283 const SfxItemSet
& rSet
= rTextNd
.GetSwAttrSet();
285 SfxItemIter
aIter( m_aComapeSet
);
286 const SfxPoolItem
* pItem
= aIter
.GetCurItem();
287 const SfxPoolItem
* pFndItem
;
292 if( IsInvalidItem( pItem
) )
294 nWhich
= aIter
.GetCurWhich();
295 if( RES_TXTATR_END
<= nWhich
)
296 break; // end of text attributes
298 if( SfxItemState::SET
== rSet
.GetItemState( nWhich
, !m_bNoColls
, &pFndItem
)
299 && !CmpAttr( *pFndItem
, rSet
.GetPool()->GetUserOrPoolDefaultItem( nWhich
) ))
301 m_pFindArr
[ nWhich
- m_nArrStart
] =
302 SwSrchChrAttr( *pFndItem
, m_nNodeStart
, m_nNodeEnd
);
308 nWhich
= pItem
->Which();
309 if( RES_TXTATR_END
<= nWhich
)
310 break; // end of text attributes
312 if( CmpAttr( rSet
.Get( nWhich
, !m_bNoColls
), *pItem
) )
314 m_pFindArr
[ nWhich
- m_nArrStart
] =
315 SwSrchChrAttr( *pItem
, m_nNodeStart
, m_nNodeEnd
);
320 pItem
= aIter
.NextItem();
325 lcl_IsAttributeIgnorable(sal_Int32
const nNdStart
, sal_Int32
const nNdEnd
,
326 SwSrchChrAttr
const& rTmp
)
328 // #i115528#: if there is a paragraph attribute, it has been added by the
329 // SwAttrCheckArr ctor, and nFound is 1.
330 // if the paragraph is entirely covered by hints that override the paragraph
331 // attribute, then this function must find an attribute to decrement nFound!
332 // so check for an empty search range, let attributes that start/end there
333 // cover it, and hope for the best...
334 return ((nNdEnd
== nNdStart
)
335 ? ((rTmp
.nEnd
< nNdStart
) || (nNdEnd
< rTmp
.nStt
))
336 : ((rTmp
.nEnd
<= nNdStart
) || (nNdEnd
<= rTmp
.nStt
)));
339 bool SwAttrCheckArr::SetAttrFwd( const SwTextAttr
& rAttr
)
341 SwSrchChrAttr
aTmp( rAttr
.GetAttr(), rAttr
.GetStart(), rAttr
.GetAnyEnd() );
343 // ignore all attributes not in search range
344 if (lcl_IsAttributeIgnorable(m_nNodeStart
, m_nNodeEnd
, aTmp
))
349 const SfxPoolItem
* pItem
;
350 // here we explicitly also search in character templates
351 sal_uInt16 nWhch
= rAttr
.Which();
352 std::optional
<SfxWhichIter
> oIter
;
353 const SfxPoolItem
* pTmpItem
= nullptr;
354 const SfxItemSet
* pSet
= nullptr;
355 if( RES_TXTATR_CHARFMT
== nWhch
|| RES_TXTATR_AUTOFMT
== nWhch
)
357 if( m_bNoColls
&& RES_TXTATR_CHARFMT
== nWhch
)
360 pSet
= CharFormat::GetItemSet( rAttr
.GetAttr() );
363 oIter
.emplace( *pSet
);
364 nWhch
= oIter
->FirstWhich();
366 SfxItemState::SET
!= oIter
->GetItemState( true, &pTmpItem
) )
367 nWhch
= oIter
->NextWhich();
373 pTmpItem
= &rAttr
.GetAttr();
377 SfxItemState eState
= m_aComapeSet
.GetItemState( nWhch
, false, &pItem
);
378 if( SfxItemState::INVALID
== eState
|| SfxItemState::SET
== eState
)
383 // first delete all up to start position that are already invalid
384 SwSrchChrAttr
* pArrPtr
;
386 for( pArrPtr
= m_pFindArr
, n
= 0; n
< m_nArrLen
;
388 if( pArrPtr
->nWhich
&& pArrPtr
->nEnd
<= aTmp
.nStt
)
390 pArrPtr
->nWhich
= 0; // deleted
394 // delete all up to start position that are already invalid and
395 // move all "open" ones (= stick out over start position) from stack
398 for( pArrPtr
= m_pStackArr
, n
=0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
400 if( !pArrPtr
->nWhich
)
403 if( pArrPtr
->nEnd
<= aTmp
.nStt
)
405 pArrPtr
->nWhich
= 0; // deleted
406 if( !--m_nStackCount
)
409 else if( pArrPtr
->nStt
<= aTmp
.nStt
)
411 pCmp
= &m_pFindArr
[ n
];
414 if( pCmp
->nEnd
< pArrPtr
->nEnd
) // extend
415 pCmp
->nEnd
= pArrPtr
->nEnd
;
423 if( !--m_nStackCount
)
428 bool bContinue
= false;
430 if( SfxItemState::INVALID
== eState
)
432 // Will the attribute become valid?
433 if( !CmpAttr( m_aComapeSet
.GetPool()->GetUserOrPoolDefaultItem( nWhch
),
436 // search attribute and extend if needed
437 pCmp
= &m_pFindArr
[ nWhch
- m_nArrStart
];
440 *pCmp
= aTmp
; // not found, insert
443 else if( pCmp
->nEnd
< aTmp
.nEnd
) // extend?
444 pCmp
->nEnd
= aTmp
.nEnd
;
449 // Will the attribute become valid?
450 else if( CmpAttr( *pItem
, *pTmpItem
) )
452 m_pFindArr
[ nWhch
- m_nArrStart
] = aTmp
;
457 // then is has to go on the stack
460 pCmp
= &m_pFindArr
[ nWhch
- m_nArrStart
];
463 // exists on stack, only if it is even bigger
464 if( pCmp
->nEnd
> aTmp
.nEnd
)
466 OSL_ENSURE( !m_pStackArr
[ nWhch
- m_nArrStart
].nWhich
,
467 "slot on stack is still in use" );
469 if( aTmp
.nStt
<= pCmp
->nStt
)
470 pCmp
->nStt
= aTmp
.nEnd
;
472 pCmp
->nEnd
= aTmp
.nStt
;
474 m_pStackArr
[ nWhch
- m_nArrStart
] = *pCmp
;
484 assert(pSet
&& "otherwise no oIter");
485 nWhch
= oIter
->NextWhich();
487 SfxItemState::SET
!= oIter
->GetItemState( true, &pTmpItem
) )
488 nWhch
= oIter
->NextWhich();
499 bool SwAttrCheckArr::SetAttrBwd( const SwTextAttr
& rAttr
)
501 SwSrchChrAttr
aTmp( rAttr
.GetAttr(), rAttr
.GetStart(), rAttr
.GetAnyEnd() );
503 // ignore all attributes not in search range
504 if (lcl_IsAttributeIgnorable(m_nNodeStart
, m_nNodeEnd
, aTmp
))
509 const SfxPoolItem
* pItem
;
510 // here we explicitly also search in character templates
511 sal_uInt16 nWhch
= rAttr
.Which();
512 std::optional
<SfxWhichIter
> oIter
;
513 const SfxPoolItem
* pTmpItem
= nullptr;
514 const SfxItemSet
* pSet
= nullptr;
515 if( RES_TXTATR_CHARFMT
== nWhch
|| RES_TXTATR_AUTOFMT
== nWhch
)
517 if( m_bNoColls
&& RES_TXTATR_CHARFMT
== nWhch
)
520 pSet
= CharFormat::GetItemSet( rAttr
.GetAttr() );
523 oIter
.emplace( *pSet
);
524 nWhch
= oIter
->FirstWhich();
526 SfxItemState::SET
!= oIter
->GetItemState( true, &pTmpItem
) )
527 nWhch
= oIter
->NextWhich();
533 pTmpItem
= &rAttr
.GetAttr();
537 SfxItemState eState
= m_aComapeSet
.GetItemState( nWhch
, false, &pItem
);
538 if( SfxItemState::INVALID
== eState
|| SfxItemState::SET
== eState
)
543 // first delete all up to start position that are already invalid
544 SwSrchChrAttr
* pArrPtr
;
546 for( pArrPtr
= m_pFindArr
, n
= 0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
547 if( pArrPtr
->nWhich
&& pArrPtr
->nStt
>= aTmp
.nEnd
)
549 pArrPtr
->nWhich
= 0; // deleted
553 // delete all up to start position that are already invalid and
554 // move all "open" ones (= stick out over start position) from stack
557 for( pArrPtr
= m_pStackArr
, n
= 0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
559 if( !pArrPtr
->nWhich
)
562 if( pArrPtr
->nStt
>= aTmp
.nEnd
)
564 pArrPtr
->nWhich
= 0; // deleted
565 if( !--m_nStackCount
)
568 else if( pArrPtr
->nEnd
>= aTmp
.nEnd
)
570 pCmp
= &m_pFindArr
[ n
];
573 if( pCmp
->nStt
> pArrPtr
->nStt
) // extend
574 pCmp
->nStt
= pArrPtr
->nStt
;
582 if( !--m_nStackCount
)
587 bool bContinue
= false;
588 if( SfxItemState::INVALID
== eState
)
590 // Will the attribute become valid?
591 if( !CmpAttr( m_aComapeSet
.GetPool()->GetUserOrPoolDefaultItem( nWhch
),
594 // search attribute and extend if needed
595 pCmp
= &m_pFindArr
[ nWhch
- m_nArrStart
];
598 *pCmp
= aTmp
; // not found, insert
601 else if( pCmp
->nStt
> aTmp
.nStt
) // extend?
602 pCmp
->nStt
= aTmp
.nStt
;
607 // Will the attribute become valid?
608 else if( CmpAttr( *pItem
, *pTmpItem
))
610 m_pFindArr
[ nWhch
- m_nArrStart
] = aTmp
;
615 // then is has to go on the stack
618 pCmp
= &m_pFindArr
[ nWhch
- m_nArrStart
];
621 // exists on stack, only if it is even bigger
622 if( pCmp
->nStt
< aTmp
.nStt
)
624 OSL_ENSURE( !m_pStackArr
[ nWhch
- m_nArrStart
].nWhich
,
625 "slot on stack is still in use" );
627 if( aTmp
.nEnd
<= pCmp
->nEnd
)
628 pCmp
->nEnd
= aTmp
.nStt
;
630 pCmp
->nStt
= aTmp
.nEnd
;
632 m_pStackArr
[ nWhch
- m_nArrStart
] = *pCmp
;
642 assert(pSet
&& "otherwise no oIter");
643 nWhch
= oIter
->NextWhich();
645 SfxItemState::SET
!= oIter
->GetItemState( true, &pTmpItem
) )
646 nWhch
= oIter
->NextWhich();
657 sal_Int32
SwAttrCheckArr::Start() const
659 sal_Int32 nStart
= m_nNodeStart
;
660 SwSrchChrAttr
* pArrPtr
= m_pFindArr
;
661 for( sal_uInt16 n
= 0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
662 if( pArrPtr
->nWhich
&& pArrPtr
->nStt
> nStart
)
663 nStart
= pArrPtr
->nStt
;
668 sal_Int32
SwAttrCheckArr::End() const
670 SwSrchChrAttr
* pArrPtr
= m_pFindArr
;
671 sal_Int32 nEnd
= m_nNodeEnd
;
672 for( sal_uInt16 n
= 0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
673 if( pArrPtr
->nWhich
&& pArrPtr
->nEnd
< nEnd
)
674 nEnd
= pArrPtr
->nEnd
;
679 bool SwAttrCheckArr::CheckStack()
685 const sal_Int32 nSttPos
= Start();
686 const sal_Int32 nEndPos
= End();
687 SwSrchChrAttr
* pArrPtr
;
688 for( pArrPtr
= m_pStackArr
, n
= 0; n
< m_nArrLen
; ++n
, ++pArrPtr
)
690 if( !pArrPtr
->nWhich
)
693 if( m_bForward
? pArrPtr
->nEnd
<= nSttPos
: pArrPtr
->nStt
>= nEndPos
)
695 pArrPtr
->nWhich
= 0; // deleted
696 if( !--m_nStackCount
)
697 return m_nFound
== m_aComapeSet
.Count();
699 else if( m_bForward
? pArrPtr
->nStt
< nEndPos
: pArrPtr
->nEnd
> nSttPos
)
701 // move all "open" ones (= stick out over start position) into FndSet
702 OSL_ENSURE( !m_pFindArr
[ n
].nWhich
, "slot in array is already in use" );
703 m_pFindArr
[ n
] = *pArrPtr
;
706 if( !--m_nStackCount
)
707 return m_nFound
== m_aComapeSet
.Count();
710 return m_nFound
== m_aComapeSet
.Count();
713 static bool lcl_SearchForward( const SwTextNode
& rTextNd
, SwAttrCheckArr
& rCmpArr
,
717 rCmpArr
.SetNewSet( rTextNd
, rPam
);
718 if( !rTextNd
.HasHints() )
720 if( !rCmpArr
.Found() )
722 nEndPos
= rCmpArr
.GetNdEnd();
723 lcl_SetAttrPam( rPam
, rCmpArr
.GetNdStt(), &nEndPos
, true );
727 const SwpHints
& rHtArr
= rTextNd
.GetSwpHints();
728 const SwTextAttr
* pAttr
;
731 // if everything is already there then check with which it will be ended
732 if( rCmpArr
.Found() )
734 for( ; nPos
< rHtArr
.Count(); ++nPos
)
736 pAttr
= rHtArr
.Get( nPos
);
737 if( !rCmpArr
.SetAttrFwd( *pAttr
) )
739 if( rCmpArr
.GetNdStt() < pAttr
->GetStart() )
742 auto nTmpStart
= pAttr
->GetStart();
743 lcl_SetAttrPam( rPam
, rCmpArr
.GetNdStt(),
752 if( nPos
== rHtArr
.Count() && rCmpArr
.Found() )
755 nEndPos
= rCmpArr
.GetNdEnd();
756 lcl_SetAttrPam( rPam
, rCmpArr
.GetNdStt(), &nEndPos
, true );
762 for( ; nPos
< rHtArr
.Count(); ++nPos
)
764 pAttr
= rHtArr
.Get( nPos
);
765 if( rCmpArr
.SetAttrFwd( *pAttr
) )
767 // Do multiple start at that position? Do also check those:
768 nSttPos
= pAttr
->GetStart();
769 while( ++nPos
< rHtArr
.Count() )
771 pAttr
= rHtArr
.Get( nPos
);
772 if( nSttPos
!= pAttr
->GetStart() || !rCmpArr
.SetAttrFwd( *pAttr
) )
776 if( !rCmpArr
.Found() )
779 // then we have our search area
780 nSttPos
= rCmpArr
.Start();
781 nEndPos
= rCmpArr
.End();
782 if( nSttPos
> nEndPos
)
785 lcl_SetAttrPam( rPam
, nSttPos
, &nEndPos
, true );
790 if( !rCmpArr
.CheckStack() )
792 nSttPos
= rCmpArr
.Start();
793 nEndPos
= rCmpArr
.End();
794 if( nSttPos
> nEndPos
)
797 lcl_SetAttrPam( rPam
, nSttPos
, &nEndPos
, true );
801 static bool lcl_SearchBackward( const SwTextNode
& rTextNd
, SwAttrCheckArr
& rCmpArr
,
805 rCmpArr
.SetNewSet( rTextNd
, rPam
);
806 if( !rTextNd
.HasHints() )
808 if( !rCmpArr
.Found() )
810 nEndPos
= rCmpArr
.GetNdEnd();
811 lcl_SetAttrPam( rPam
, rCmpArr
.GetNdStt(), &nEndPos
, false );
815 const SwpHints
& rHtArr
= rTextNd
.GetSwpHints();
816 const SwTextAttr
* pAttr
;
817 size_t nPos
= rHtArr
.Count();
820 // if everything is already there then check with which it will be ended
821 if( rCmpArr
.Found() )
825 pAttr
= rHtArr
.GetSortedByEnd( --nPos
);
826 if( !rCmpArr
.SetAttrBwd( *pAttr
) )
828 nSttPos
= pAttr
->GetAnyEnd();
829 if( nSttPos
< rCmpArr
.GetNdEnd() )
832 nEndPos
= rCmpArr
.GetNdEnd();
833 lcl_SetAttrPam( rPam
, nSttPos
, &nEndPos
, false );
842 if( !nPos
&& rCmpArr
.Found() )
845 nEndPos
= rCmpArr
.GetNdEnd();
846 lcl_SetAttrPam( rPam
, rCmpArr
.GetNdStt(), &nEndPos
, false );
853 pAttr
= rHtArr
.GetSortedByEnd( --nPos
);
854 if( rCmpArr
.SetAttrBwd( *pAttr
) )
856 // Do multiple start at that position? Do also check those:
859 nEndPos
= pAttr
->GetAnyEnd();
862 pAttr
= rHtArr
.GetSortedByEnd( nPos
);
863 if( nEndPos
!= pAttr
->GetAnyEnd() || !rCmpArr
.SetAttrBwd( *pAttr
) )
867 if( !rCmpArr
.Found() )
870 // then we have our search area
871 nSttPos
= rCmpArr
.Start();
872 nEndPos
= rCmpArr
.End();
873 if( nSttPos
> nEndPos
)
876 lcl_SetAttrPam( rPam
, nSttPos
, &nEndPos
, false );
881 if( !rCmpArr
.CheckStack() )
883 nSttPos
= rCmpArr
.Start();
884 nEndPos
= rCmpArr
.End();
885 if( nSttPos
> nEndPos
)
888 lcl_SetAttrPam( rPam
, nSttPos
, &nEndPos
, false );
892 static bool lcl_Search( const SwContentNode
& rCNd
, const SfxItemSet
& rCmpSet
, bool bNoColls
)
894 // search only hard attribution?
895 if( bNoColls
&& !rCNd
.HasSwAttrSet() )
898 const SfxItemSet
& rNdSet
= rCNd
.GetSwAttrSet();
899 SfxItemIter
aIter( rCmpSet
);
900 const SfxPoolItem
* pItem
= aIter
.GetCurItem();
901 const SfxPoolItem
* pNdItem
;
906 if( IsInvalidItem( pItem
))
908 nWhich
= aIter
.GetCurWhich();
909 if( SfxItemState::SET
!= rNdSet
.GetItemState( nWhich
, !bNoColls
, &pNdItem
)
910 || CmpAttr( *pNdItem
, rNdSet
.GetPool()->GetUserOrPoolDefaultItem( nWhich
) ))
915 nWhich
= pItem
->Which();
917 if( !CmpAttr( rNdSet
.Get( nWhich
, !bNoColls
), *pItem
))
921 pItem
= aIter
.NextItem();
923 return true; // found
928 bool FindAttrImpl(SwPaM
& rSearchPam
,
929 const SfxPoolItem
& rAttr
, SwMoveFnCollection
const & fnMove
,
930 const SwPaM
& rRegion
, bool bInReadOnly
,
931 SwRootFrame
const*const pLayout
)
933 // determine which attribute is searched:
934 const sal_uInt16 nWhich
= rAttr
.Which();
935 bool bCharAttr
= isCHRATR(nWhich
) || isTXTATR(nWhich
);
936 assert(isTXTATR(nWhich
)); // sw_redlinehide: only works for non-formatting hints such as needed in UpdateFields; use FindAttrsImpl for others
938 std::optional
<SwPaM
> oPam
;
939 sw::MakeRegion(fnMove
, rRegion
, oPam
);
943 const bool bSrchForward
= &fnMove
== &fnMoveForward
;
944 SwContentNode
* pNode
;
946 // if at beginning/end then move it out of the node
948 ? oPam
->GetPoint()->GetContentIndex() == oPam
->GetPointContentNode()->Len()
949 : !oPam
->GetPoint()->GetContentIndex() )
951 if( !(*fnMove
.fnPos
)( oPam
->GetPoint(), false ))
955 SwContentNode
*pNd
= oPam
->GetPointContentNode();
956 oPam
->GetPoint()->SetContent( bSrchForward
? 0 : pNd
->Len() );
959 while (nullptr != (pNode
= ::GetNode(*oPam
, bFirst
, fnMove
, bInReadOnly
, pLayout
)))
963 if( !pNode
->IsTextNode() ) // CharAttr are only in text nodes
966 SwTextFrame
const*const pFrame(pLayout
967 ? static_cast<SwTextFrame
const*>(pNode
->getLayoutFrame(pLayout
))
971 SwTextNode
const* pAttrNode(nullptr);
972 SwTextAttr
const* pAttr(nullptr);
975 sw::MergedAttrIter
iter(*pFrame
);
978 pAttr
= iter
.NextAttr(&pAttrNode
);
981 && (pAttrNode
->GetIndex() < oPam
->GetPoint()->GetNodeIndex()
982 || (pAttrNode
->GetIndex() == oPam
->GetPoint()->GetNodeIndex()
983 && pAttr
->GetStart() < oPam
->GetPoint()->GetContentIndex())
984 || pAttr
->Which() != nWhich
));
988 sw::MergedAttrIterReverse
iter(*pFrame
);
991 pAttr
= iter
.PrevAttr(&pAttrNode
);
994 && (oPam
->GetPoint()->GetNodeIndex() < pAttrNode
->GetIndex()
995 || (oPam
->GetPoint()->GetNodeIndex() == pAttrNode
->GetIndex()
996 && oPam
->GetPoint()->GetContentIndex() <= pAttr
->GetStart())
997 || pAttr
->Which() != nWhich
));
1002 oPam
->GetPoint()->Assign(*pAttrNode
);
1003 lcl_SetAttrPam(*oPam
, pAttr
->GetStart(), pAttr
->End(), bSrchForward
);
1008 else if (!pLayout
&& pNode
->GetTextNode()->HasHints() &&
1009 lcl_SearchAttr(*pNode
->GetTextNode(), *oPam
, rAttr
, fnMove
))
1015 // set to the values of the attribute
1016 rSearchPam
.SetMark();
1017 *rSearchPam
.GetPoint() = *oPam
->GetPoint();
1018 *rSearchPam
.GetMark() = *oPam
->GetMark();
1021 else if (isTXTATR(nWhich
))
1026 // no hard attribution, so check if node was asked for this attr before
1027 if( !pNode
->HasSwAttrSet() )
1029 SwFormat
* pTmpFormat
= pNode
->GetFormatColl();
1030 if( !aFormatArr
.insert( pTmpFormat
).second
)
1031 continue; // collection was requested earlier
1034 if( SfxItemState::SET
== pNode
->GetSwAttrSet().GetItemState( nWhich
,
1037 // FORWARD: SPoint at the end, GetMark at the beginning of the node
1038 // BACKWARD: SPoint at the beginning, GetMark at the end of the node
1039 // always: incl. start and incl. end
1040 *rSearchPam
.GetPoint() = *pPam
->GetPoint();
1041 rSearchPam
.SetMark();
1042 rSearchPam
.GetPoint()->SetContent(pNode
->Len());
1049 // if backward search, switch point and mark
1050 if( bFound
&& !bSrchForward
)
1051 rSearchPam
.Exchange();
1058 typedef bool (*FnSearchAttr
)( const SwTextNode
&, SwAttrCheckArr
&, SwPaM
& );
1060 static bool FindAttrsImpl(SwPaM
& rSearchPam
,
1061 const SfxItemSet
& rSet
, bool bNoColls
, SwMoveFnCollection
const & fnMove
,
1062 const SwPaM
& rRegion
, bool bInReadOnly
, bool bMoveFirst
,
1063 SwRootFrame
const*const pLayout
)
1065 std::optional
<SwPaM
> oPam
;
1066 sw::MakeRegion(fnMove
, rRegion
, oPam
);
1068 bool bFound
= false;
1070 const bool bSrchForward
= &fnMove
== &fnMoveForward
;
1071 SwContentNode
* pNode
;
1072 o3tl::sorted_vector
<SwFormat
*> aFormatArr
;
1074 // check which text/char attributes are searched
1075 SwAttrCheckArr
aCmpArr( rSet
, bSrchForward
, bNoColls
);
1076 SfxItemSetFixed
<RES_PARATR_BEGIN
, RES_GRFATR_END
-1> aOtherSet( rSearchPam
.GetDoc().GetAttrPool() );
1077 aOtherSet
.Put( rSet
, false ); // got all invalid items
1079 FnSearchAttr fnSearch
= bSrchForward
1080 ? (&::lcl_SearchForward
)
1081 : (&::lcl_SearchBackward
);
1083 // if at beginning/end then move it out of the node
1086 ? oPam
->GetPoint()->GetContentIndex() == oPam
->GetPointContentNode()->Len()
1087 : !oPam
->GetPoint()->GetContentIndex() ) )
1089 if( !(*fnMove
.fnPos
)( oPam
->GetPoint(), false ))
1093 SwContentNode
*pNd
= oPam
->GetPointContentNode();
1094 oPam
->GetPoint()->SetContent( bSrchForward
? 0 : pNd
->Len() );
1097 while (nullptr != (pNode
= ::GetNode(*oPam
, bFirst
, fnMove
, bInReadOnly
, pLayout
)))
1099 SwTextFrame
const*const pFrame(pLayout
&& pNode
->IsTextNode()
1100 ? static_cast<SwTextFrame
const*>(pNode
->getLayoutFrame(pLayout
))
1102 assert(!pLayout
|| !pNode
->IsTextNode() || pFrame
);
1103 // sw_redlinehide: it's apparently not possible to find break items
1104 // with the UI, so checking one node is enough
1105 SwContentNode
const& rPropsNode(*(pFrame
1106 ? pFrame
->GetTextNodeForParaProps()
1109 if( aCmpArr
.Count() )
1111 if( !pNode
->IsTextNode() ) // CharAttr are only in text nodes
1114 if (aOtherSet
.Count() &&
1115 !lcl_Search(rPropsNode
, aOtherSet
, bNoColls
))
1119 sw::MergedPara
const*const pMergedPara(pFrame
? pFrame
->GetMergedPara() : nullptr);
1122 SwPosition
const& rStart(*oPam
->Start());
1123 SwPosition
const& rEnd(*oPam
->End());
1124 // no extents? fall back to searching index 0 of propsnode
1125 // to find its node items
1126 if (pMergedPara
->extents
.empty())
1128 if (rStart
.GetNodeIndex() <= rPropsNode
.GetIndex()
1129 && rPropsNode
.GetIndex() <= rEnd
.GetNodeIndex())
1131 SwPaM
tmp(rPropsNode
, 0, rPropsNode
, 0);
1132 bFound
= (*fnSearch
)(*pNode
->GetTextNode(), aCmpArr
, tmp
);
1141 // iterate the extents, and intersect with input pPam:
1142 // the found ranges should never include delete redlines
1143 // so that subsequent Replace will not affect them
1144 for (size_t i
= 0; i
< pMergedPara
->extents
.size(); ++i
)
1146 auto const rExtent(pMergedPara
->extents
[bSrchForward
1148 : pMergedPara
->extents
.size() - i
- 1]);
1149 if (rExtent
.pNode
->GetIndex() < rStart
.GetNodeIndex()
1150 || rEnd
.GetNodeIndex() < rExtent
.pNode
->GetIndex())
1154 sal_Int32
const nStart(rExtent
.pNode
== &rStart
.GetNode()
1155 ? rStart
.GetContentIndex()
1157 if (rExtent
.nEnd
<= nStart
)
1161 sal_Int32
const nEnd(rExtent
.pNode
== &rEnd
.GetNode()
1162 ? rEnd
.GetContentIndex()
1163 : rExtent
.pNode
->Len());
1164 if (nEnd
< rExtent
.nStart
1165 || (nStart
!= nEnd
&& nEnd
== rExtent
.nStart
))
1169 SwPaM
tmp(*rExtent
.pNode
, std::max(nStart
, rExtent
.nStart
),
1170 *rExtent
.pNode
, std::min(nEnd
, rExtent
.nEnd
));
1171 tmp
.Normalize(bSrchForward
);
1172 bFound
= (*fnSearch
)(*rExtent
.pNode
, aCmpArr
, tmp
);
1183 bFound
= (*fnSearch
)(*pNode
->GetTextNode(), aCmpArr
, *oPam
);
1187 // set to the values of the attribute
1188 rSearchPam
.SetMark();
1189 *rSearchPam
.GetPoint() = *oPam
->GetPoint();
1190 *rSearchPam
.GetMark() = *oPam
->GetMark();
1193 continue; // text attribute
1196 if( !aOtherSet
.Count() )
1199 // no hard attribution, so check if node was asked for this attr before
1200 // (as an optimisation)
1201 if (!rPropsNode
.HasSwAttrSet())
1203 SwFormat
* pTmpFormat
= rPropsNode
.GetFormatColl();
1204 if( !aFormatArr
.insert( pTmpFormat
).second
)
1205 continue; // collection was requested earlier
1208 if (lcl_Search(rPropsNode
, aOtherSet
, bNoColls
))
1210 // FORWARD: SPoint at the end, GetMark at the beginning of the node
1211 // BACKWARD: SPoint at the beginning, GetMark at the end of the node
1214 *rSearchPam
.GetPoint() = *oPam
->GetPoint();
1215 rSearchPam
.SetMark();
1216 *rSearchPam
.GetMark() = pFrame
->MapViewToModelPos(
1217 TextFrameIndex(bSrchForward
? pFrame
->GetText().getLength() : 0));
1221 *rSearchPam
.GetPoint() = *oPam
->GetPoint();
1222 rSearchPam
.SetMark();
1225 rSearchPam
.GetPoint()->SetContent(pNode
->Len());
1229 rSearchPam
.GetPoint()->SetContent(0);
1237 // in search direction, mark precedes point, because the next iteration
1241 rSearchPam
.Normalize(!bSrchForward
);
1249 /// parameters for search for attributes
1250 struct SwFindParaAttr
: public SwFindParas
1252 bool m_bNoCollection
;
1253 const SfxItemSet
*pSet
, *pReplSet
;
1254 const i18nutil::SearchOptions2
*pSearchOpt
;
1255 SwCursor
& m_rCursor
;
1256 SwRootFrame
const* m_pLayout
;
1257 std::unique_ptr
<utl::TextSearch
> pSText
;
1259 SwFindParaAttr( const SfxItemSet
& rSet
, bool bNoCollection
,
1260 const i18nutil::SearchOptions2
* pOpt
, const SfxItemSet
* pRSet
,
1261 SwCursor
& rCursor
, SwRootFrame
const*const pLayout
)
1262 : m_bNoCollection(bNoCollection
)
1265 , pSearchOpt( pOpt
)
1266 , m_rCursor(rCursor
)
1267 , m_pLayout(pLayout
)
1270 virtual ~SwFindParaAttr() {}
1272 virtual int DoFind(SwPaM
&, SwMoveFnCollection
const &, const SwPaM
&, bool bInReadOnly
,
1273 std::unique_ptr
<SvxSearchItem
>& xSearchItem
) override
;
1274 virtual bool IsReplaceMode() const override
;
1279 int SwFindParaAttr::DoFind(SwPaM
& rCursor
, SwMoveFnCollection
const & fnMove
,
1280 const SwPaM
& rRegion
, bool bInReadOnly
,
1281 std::unique_ptr
<SvxSearchItem
>& xSearchItem
)
1283 // replace string (only if text given and search is not parameterized)?
1284 bool bReplaceText
= pSearchOpt
&& ( !pSearchOpt
->replaceString
.isEmpty() ||
1286 bool bReplaceAttr
= pReplSet
&& pReplSet
->Count();
1287 bool bMoveFirst
= !bReplaceAttr
;
1288 if( bInReadOnly
&& (bReplaceAttr
|| bReplaceText
))
1289 bInReadOnly
= false;
1291 // We search for attributes, should we search for text as well?
1293 SwPaM
aRegion( *rRegion
.GetMark(), *rRegion
.GetPoint() );
1294 SwPaM
* pTextRegion
= &aRegion
;
1295 SwPaM
aSrchPam( *rCursor
.GetPoint() );
1299 if( pSet
->Count() ) // any attributes?
1302 if (!FindAttrsImpl(aSrchPam
, *pSet
, m_bNoCollection
, fnMove
, aRegion
, bInReadOnly
, bMoveFirst
, m_pLayout
))
1303 return FIND_NOT_FOUND
;
1307 break; // ok, only attributes, so found
1309 pTextRegion
= &aSrchPam
;
1311 else if( !pSearchOpt
)
1312 return FIND_NOT_FOUND
;
1314 // then search in text of it
1317 i18nutil::SearchOptions2
aTmp( *pSearchOpt
);
1319 // search in selection
1320 aTmp
.searchFlag
|= (SearchFlags::REG_NOT_BEGINOFLINE
|
1321 SearchFlags::REG_NOT_ENDOFLINE
);
1323 aTmp
.Locale
= SvtSysLocale().GetLanguageTag().getLocale();
1325 pSText
.reset( new utl::TextSearch( aTmp
) );
1328 // TODO: searching for attributes in Outliner text?!
1330 // continue search in correct section (pTextRegion)
1331 if (sw::FindTextImpl(aSrchPam
, *pSearchOpt
, false/*bSearchInNotes*/, *pSText
, fnMove
, *pTextRegion
, bInReadOnly
, m_pLayout
, xSearchItem
) &&
1332 *aSrchPam
.GetMark() != *aSrchPam
.GetPoint() )
1334 else if( !pSet
->Count() )
1335 return FIND_NOT_FOUND
; // only text and nothing found
1337 *aRegion
.GetMark() = *aSrchPam
.GetPoint();
1340 *rCursor
.GetPoint() = *aSrchPam
.GetPoint();
1342 *rCursor
.GetMark() = *aSrchPam
.GetMark();
1348 SearchAlgorithms2::REGEXP
== pSearchOpt
->AlgorithmType2
);
1349 SwPosition
& rSttCntPos
= *rCursor
.Start();
1350 const sal_Int32 nSttCnt
= rSttCntPos
.GetContentIndex();
1352 // add to shell-cursor-ring so that the regions will be moved eventually
1353 SwPaM
* pPrevRing(nullptr);
1356 pPrevRing
= const_cast<SwPaM
&>(rRegion
).GetPrev();
1357 const_cast<SwPaM
&>(rRegion
).GetRingContainer().merge( m_rCursor
.GetRingContainer() );
1360 std::optional
<OUString
> xRepl
;
1362 xRepl
= sw::ReplaceBackReferences(*pSearchOpt
, &rCursor
, m_pLayout
);
1363 sw::ReplaceImpl(rCursor
,
1364 xRepl
? *xRepl
: pSearchOpt
->replaceString
, bRegExp
,
1365 m_rCursor
.GetDoc(), m_pLayout
);
1367 m_rCursor
.SaveTableBoxContent( rCursor
.GetPoint() );
1371 // and remove region again
1373 SwPaM
* pNext
= const_cast<SwPaM
*>(&rRegion
);
1376 pNext
= p
->GetNext();
1377 p
->MoveTo(const_cast<SwPaM
*>(&rRegion
));
1378 } while( p
!= pPrevRing
);
1380 rSttCntPos
.SetContent(nSttCnt
);
1385 // is the selection still existent?
1386 // all searched attributes are reset to default if
1387 // they are not in ReplaceSet
1388 if( !pSet
->Count() )
1390 rCursor
.GetDoc().getIDocumentContentOperations().InsertItemSet(
1391 rCursor
, *pReplSet
, SetAttrMode::DEFAULT
, m_pLayout
);
1395 SfxItemPool
* pPool
= pReplSet
->GetPool();
1396 SfxItemSet
aSet( *pPool
, pReplSet
->GetRanges() );
1398 SfxItemIter
aIter( *pSet
);
1399 const SfxPoolItem
* pItem
= aIter
.GetCurItem();
1402 // reset all that are not set with pool defaults
1403 if( !IsInvalidItem( pItem
) && SfxItemState::SET
!=
1404 pReplSet
->GetItemState( pItem
->Which(), false ))
1405 aSet
.Put( pPool
->GetUserOrPoolDefaultItem( pItem
->Which() ));
1407 pItem
= aIter
.NextItem();
1409 aSet
.Put( *pReplSet
);
1410 rCursor
.GetDoc().getIDocumentContentOperations().InsertItemSet(
1411 rCursor
, aSet
, SetAttrMode::DEFAULT
, m_pLayout
);
1414 return FIND_NO_RING
;
1420 bool SwFindParaAttr::IsReplaceMode() const
1422 return ( pSearchOpt
&& !pSearchOpt
->replaceString
.isEmpty() ) ||
1423 ( pReplSet
&& pReplSet
->Count() );
1426 /// search for attributes
1427 sal_Int32
SwCursor::FindAttrs( const SfxItemSet
& rSet
, bool bNoCollections
,
1428 SwDocPositions nStart
, SwDocPositions nEnd
,
1429 bool& bCancel
, FindRanges eFndRngs
,
1430 const i18nutil::SearchOptions2
* pSearchOpt
,
1431 const SfxItemSet
* pReplSet
,
1432 SwRootFrame
const*const pLayout
)
1434 // switch off OLE-notifications
1435 SwDoc
& rDoc
= GetDoc();
1436 Link
<bool,void> aLnk( rDoc
.GetOle2Link() );
1437 rDoc
.SetOle2Link( Link
<bool,void>() );
1439 bool bReplace
= ( pSearchOpt
&& ( !pSearchOpt
->replaceString
.isEmpty() ||
1440 !rSet
.Count() ) ) ||
1441 (pReplSet
&& pReplSet
->Count());
1442 bool const bStartUndo
= rDoc
.GetIDocumentUndoRedo().DoesUndo() && bReplace
;
1445 rDoc
.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE
, nullptr );
1448 SwFindParaAttr
aSwFindParaAttr( rSet
, bNoCollections
, pSearchOpt
,
1449 pReplSet
, *this, pLayout
);
1451 sal_Int32 nRet
= FindAll( aSwFindParaAttr
, nStart
, nEnd
, eFndRngs
, bCancel
);
1452 rDoc
.SetOle2Link( aLnk
);
1453 if( nRet
&& bReplace
)
1454 rDoc
.getIDocumentState().SetModified();
1458 rDoc
.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE
, nullptr );
1464 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */