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 .
21 #include <sal/config.h>
23 #include <com/sun/star/drawing/XShapes.hpp>
24 #include <com/sun/star/drawing/XDrawPage.hpp>
25 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
27 #include <vcl/svapp.hxx>
29 #include <svx/svdobj.hxx>
30 #include <svx/svdpool.hxx>
31 #include <editeng/unoipset.hxx>
32 #include <editeng/unotext.hxx>
33 #include <tools/debug.hxx>
35 #include <unoprnms.hxx>
36 #include <unosrch.hxx>
38 using namespace ::com::sun::star
;
40 #define WID_SEARCH_BACKWARDS 0
41 #define WID_SEARCH_CASE 1
42 #define WID_SEARCH_WORDS 2
44 static std::span
<const SfxItemPropertyMapEntry
> ImplGetSearchPropertyMap()
46 static const SfxItemPropertyMapEntry aSearchPropertyMap_Impl
[] =
48 { u
"" UNO_NAME_SEARCH_BACKWARDS
""_ustr
, WID_SEARCH_BACKWARDS
, cppu::UnoType
<bool>::get(), 0, 0 },
49 { u
"" UNO_NAME_SEARCH_CASE
""_ustr
, WID_SEARCH_CASE
, cppu::UnoType
<bool>::get(), 0, 0 },
50 { u
"" UNO_NAME_SEARCH_WORDS
""_ustr
, WID_SEARCH_WORDS
, cppu::UnoType
<bool>::get(), 0, 0 },
53 return aSearchPropertyMap_Impl
;
58 class SearchContext_impl
60 uno::Reference
< drawing::XShapes
> mxShapes
;
64 SearchContext_impl(uno::Reference
<drawing::XShapes
> xShapes
)
65 : mxShapes(std::move( xShapes
)), mnIndex( -1 ) {}
67 uno::Reference
< drawing::XShape
> firstShape()
73 uno::Reference
< drawing::XShape
> nextShape()
75 uno::Reference
< drawing::XShape
> xShape
;
77 if( mxShapes
.is() && mxShapes
->getCount() > mnIndex
)
79 mxShapes
->getByIndex( mnIndex
) >>= xShape
;
87 /* ================================================================= */
88 /** this class implements a search or replace operation on a given
89 page or a given sdrobj
92 SdUnoSearchReplaceShape::SdUnoSearchReplaceShape( drawing::XDrawPage
* pPage
) noexcept
97 SdUnoSearchReplaceShape::~SdUnoSearchReplaceShape() noexcept
101 // util::XReplaceable
102 uno::Reference
< util::XReplaceDescriptor
> SAL_CALL
SdUnoSearchReplaceShape::createReplaceDescriptor()
104 return new SdUnoSearchReplaceDescriptor
;
107 sal_Int32 SAL_CALL
SdUnoSearchReplaceShape::replaceAll( const uno::Reference
< util::XSearchDescriptor
>& xDesc
)
109 SdUnoSearchReplaceDescriptor
* pDescr
= dynamic_cast<SdUnoSearchReplaceDescriptor
*>( xDesc
.get() );
110 if( pDescr
== nullptr )
113 sal_Int32 nFound
= 0;
115 uno::Reference
< drawing::XShapes
> xShapes
;
116 uno::Reference
< drawing::XShape
> xShape
;
118 std::vector
<SearchContext_impl
> aContexts
;
123 if( xShapes
->getCount() )
125 aContexts
.push_back(SearchContext_impl(xShapes
));
126 xShape
= aContexts
.back().firstShape();
137 uno::Reference
< text::XText
> xText(xShape
, uno::UNO_QUERY
);
138 uno::Reference
< text::XTextRange
> xRange
= xText
;
139 uno::Reference
< text::XTextRange
> xFound
;
143 xFound
= Search( xRange
, pDescr
);
147 xFound
->setString( pDescr
->getReplaceString() );
148 xRange
= xFound
->getEnd();
151 // done with xShape -> get next shape
153 // test if it's a group
154 uno::Reference
< drawing::XShapes
> xGroupShape( xShape
, uno::UNO_QUERY
);
155 if( xGroupShape
.is() && ( xGroupShape
->getCount() > 0 ) )
157 aContexts
.push_back(SearchContext_impl(xGroupShape
));
158 xShape
= aContexts
.back().firstShape();
162 if (!aContexts
.empty())
163 xShape
= aContexts
.back().nextShape();
168 // test parent contexts for next shape if none
169 // is found in the current context
170 while (!aContexts
.empty() && !xShape
.is())
172 aContexts
.pop_back();
173 if (!aContexts
.empty())
174 xShape
= aContexts
.back().nextShape();
182 uno::Reference
< css::util::XSearchDescriptor
> SAL_CALL
SdUnoSearchReplaceShape::createSearchDescriptor( )
184 return new SdUnoSearchReplaceDescriptor
;
187 uno::Reference
< css::container::XIndexAccess
> SAL_CALL
SdUnoSearchReplaceShape::findAll( const css::uno::Reference
< css::util::XSearchDescriptor
>& xDesc
)
189 SdUnoSearchReplaceDescriptor
* pDescr
= dynamic_cast<SdUnoSearchReplaceDescriptor
*>( xDesc
.get() );
190 if( pDescr
== nullptr )
191 return uno::Reference
< container::XIndexAccess
> ();
193 sal_Int32 nSequence
= 32;
194 sal_Int32 nFound
= 0;
196 uno::Sequence
< uno::Reference
< uno::XInterface
> > aSeq( nSequence
);
198 uno::Reference
< uno::XInterface
> * pArray
= aSeq
.getArray();
200 uno::Reference
< drawing::XShapes
> xShapes
;
201 uno::Reference
< drawing::XShape
> xShape
;
203 std::vector
<SearchContext_impl
> aContexts
;
208 if( xShapes
->getCount() > 0 )
210 aContexts
.push_back(SearchContext_impl(xShapes
));
211 xShape
= aContexts
.back().firstShape();
222 uno::Reference
< text::XText
> xText(xShape
, uno::UNO_QUERY
);
223 uno::Reference
< text::XTextRange
> xRange
= xText
;
224 uno::Reference
< text::XTextRange
> xFound
;
228 xFound
= Search( xRange
, pDescr
);
232 if( nFound
>= nSequence
)
235 aSeq
.realloc( nSequence
);
236 pArray
= aSeq
.getArray();
239 pArray
[nFound
++] = xFound
;
241 xRange
= xFound
->getEnd();
243 // done with shape -> get next shape
245 // test if it's a group
246 uno::Reference
< drawing::XShapes
> xGroupShape
;
247 xGroupShape
.set( xShape
, uno::UNO_QUERY
);
249 if( xGroupShape
.is() && xGroupShape
->getCount() > 0 )
251 aContexts
.push_back(SearchContext_impl(xGroupShape
));
252 xShape
= aContexts
.back().firstShape();
256 if (!aContexts
.empty())
257 xShape
= aContexts
.back().nextShape();
262 // test parent contexts for next shape if none
263 // is found in the current context
264 while (!aContexts
.empty() && !xShape
.is())
266 aContexts
.pop_back();
267 if (!aContexts
.empty())
268 xShape
= aContexts
.back().nextShape();
272 if( nFound
!= nSequence
)
273 aSeq
.realloc( nFound
);
275 uno::Reference
<css::container::XIndexAccess
> xRet(new SdUnoFindAllAccess(aSeq
));
279 uno::Reference
< css::uno::XInterface
> SAL_CALL
SdUnoSearchReplaceShape::findFirst( const css::uno::Reference
< css::util::XSearchDescriptor
>& xDesc
)
281 uno::Reference
< text::XTextRange
> xRange( GetCurrentShape(), uno::UNO_QUERY
);
283 return findNext( xRange
, xDesc
);
285 return uno::Reference
< uno::XInterface
> ();
288 uno::Reference
< drawing::XShape
> SdUnoSearchReplaceShape::GetCurrentShape() const noexcept
290 uno::Reference
< drawing::XShape
> xShape
;
292 if( mpPage
&& mpPage
->getCount() > 0)
293 mpPage
->getByIndex(0) >>= xShape
;
299 uno::Reference
< css::uno::XInterface
> SAL_CALL
SdUnoSearchReplaceShape::findNext( const css::uno::Reference
< css::uno::XInterface
>& xStartAt
, const css::uno::Reference
< css::util::XSearchDescriptor
>& xDesc
)
301 SdUnoSearchReplaceDescriptor
* pDescr
= dynamic_cast<SdUnoSearchReplaceDescriptor
*>( xDesc
.get() );
303 uno::Reference
< uno::XInterface
> xFound
;
305 uno::Reference
< text::XTextRange
> xRange( xStartAt
, uno::UNO_QUERY
);
306 if(pDescr
&& xRange
.is() )
309 uno::Reference
< text::XTextRange
> xCurrentRange( xStartAt
, uno::UNO_QUERY
);
311 uno::Reference
< drawing::XShape
> xCurrentShape( GetShape( xCurrentRange
) );
313 while(!xFound
.is() && xRange
.is())
315 xFound
= Search( xRange
, pDescr
);
318 // we need a new starting range now
323 // we do a page wide search, so skip to the next shape here
324 // get next shape on our page
325 uno::Reference
< drawing::XShape
> xFound2( GetNextShape( mpPage
, xCurrentShape
) );
326 if( xFound2
.is() && (xFound2
.get() != xCurrentShape
.get()) )
327 xCurrentShape
= std::move(xFound2
);
329 xCurrentShape
= nullptr;
331 xRange
.set( xCurrentShape
, uno::UNO_QUERY
);
332 if(!(xCurrentShape
.is() && (xRange
.is())))
337 // we search only in this shape, so end search if we have
338 // not found anything
346 /** this method returns the shape that follows xCurrentShape in the shape collection xShapes.
347 It steps recursive into groupshapes and returns the xCurrentShape if it is the last
348 shape in this collection */
349 uno::Reference
< drawing::XShape
> SdUnoSearchReplaceShape::GetNextShape( const uno::Reference
< container::XIndexAccess
>& xShapes
, const uno::Reference
< drawing::XShape
>& xCurrentShape
) noexcept
351 uno::Reference
< drawing::XShape
> xFound
;
353 if(xShapes
.is() && xCurrentShape
.is())
355 const sal_Int32 nCount
= xShapes
->getCount();
356 for( sal_Int32 i
= 0; i
< nCount
; i
++ )
358 uno::Reference
< drawing::XShape
> xSearchShape
;
359 xShapes
->getByIndex(i
) >>= xSearchShape
;
361 if( xSearchShape
.is() )
363 uno::Reference
< container::XIndexAccess
> xGroup( xSearchShape
, uno::UNO_QUERY
);
365 if( xCurrentShape
.get() == xSearchShape
.get() )
367 if( xGroup
.is() && xGroup
->getCount() > 0 )
369 xGroup
->getByIndex( 0 ) >>= xFound
;
375 xShapes
->getByIndex( i
) >>= xFound
;
377 xFound
= xCurrentShape
;
382 else if( xGroup
.is() )
384 xFound
= GetNextShape( xGroup
, xCurrentShape
);
387 if( xFound
.get() == xCurrentShape
.get() )
389 // the current shape was found at the end of the group
393 xShapes
->getByIndex(i
) >>= xFound
;
406 uno::Reference
< text::XTextRange
> SdUnoSearchReplaceShape::Search( const uno::Reference
< text::XTextRange
>& xText
, SdUnoSearchReplaceDescriptor
* pDescr
)
409 return uno::Reference
< text::XTextRange
> ();
411 uno::Reference
< text::XText
> xParent( xText
->getText() );
415 xParent
.set( xText
, uno::UNO_QUERY
);
418 const OUString
aText( xParent
->getString() );
420 const sal_Int32 nTextLen
= aText
.getLength();
422 std::unique_ptr
<sal_Int32
[]> pConvertPos( new sal_Int32
[nTextLen
+2] );
423 std::unique_ptr
<sal_Int32
[]> pConvertPara( new sal_Int32
[nTextLen
+2] );
425 sal_Int32
* pPos
= pConvertPos
.get();
426 sal_Int32
* pPara
= pConvertPara
.get();
428 sal_Int32 nLastPos
= 0, nLastPara
= 0;
430 uno::Reference
< container::XEnumerationAccess
> xEnumAccess( xParent
, uno::UNO_QUERY
);
432 // first we fill the arrays with the position and paragraph for every character
434 if( xEnumAccess
.is() )
436 uno::Reference
< container::XEnumeration
> xParaEnum( xEnumAccess
->createEnumeration() );
438 while(xParaEnum
->hasMoreElements())
441 uno::Reference
< text::XTextContent
> xParagraph( xParaEnum
->nextElement(), uno::UNO_QUERY
);
442 if( xParagraph
.is() )
443 xEnumAccess
.set(xParagraph
, css::uno::UNO_QUERY
);
447 if( xEnumAccess
.is() )
449 uno::Reference
< container::XEnumeration
> xPortionEnum( xEnumAccess
->createEnumeration() );
450 if( xPortionEnum
.is() )
452 while(xPortionEnum
->hasMoreElements())
454 uno::Reference
< text::XTextRange
> xPortion( xPortionEnum
->nextElement(), uno::UNO_QUERY
);
457 const OUString
aPortion( xPortion
->getString() );
458 const sal_Int32 nLen
= aPortion
.getLength();
460 ESelection
aStartSel( GetSelection( xPortion
->getStart() ) );
461 ESelection
aEndSel( GetSelection( xPortion
->getEnd() ) );
463 // special case for empty portions with content or length one portions with content (fields)
464 if( (aStartSel
.start
.nIndex
== aEndSel
.start
.nIndex
) || ( (aStartSel
.start
.nIndex
== (aEndSel
.start
.nIndex
- 1)) && (nLen
> 1) ) )
466 for( sal_Int32 i
= 0; i
< nLen
; i
++ )
468 if( ndbg
< (nTextLen
+2) )
470 *pPos
++ = aStartSel
.start
.nIndex
;
471 *pPara
++ = aStartSel
.start
.nPara
;
477 OSL_FAIL( "array overflow while searching" );
481 nLastPos
= aStartSel
.start
.nIndex
;
486 for( sal_Int32 i
= 0; i
< nLen
; i
++ )
488 if( ndbg
< (nTextLen
+2) )
490 *pPos
++ = aStartSel
.start
.nIndex
++;
491 *pPara
++ = aStartSel
.start
.nPara
;
497 OSL_FAIL( "array overflow while searching" );
501 nLastPos
= aStartSel
.start
.nIndex
- 1;
502 DBG_ASSERT( aEndSel
.start
.nIndex
== aStartSel
.start
.nIndex
, "Search is not working" );
504 nLastPara
= aStartSel
.start
.nPara
;
510 if( ndbg
< (nTextLen
+2) )
512 *pPos
++ = nLastPos
+ 1;
513 *pPara
++ = nLastPara
;
517 OSL_FAIL( "array overflow while searching" );
522 uno::Reference
< text::XTextRange
> xFound
;
526 aSel
= GetSelection( xText
);
529 sal_Int32 nEndPos
= 0;
530 for( nStartPos
= 0; nStartPos
< nTextLen
; nStartPos
++ )
532 if( pConvertPara
[nStartPos
] == aSel
.start
.nPara
&& pConvertPos
[nStartPos
] == aSel
.start
.nIndex
)
536 if( Search( aText
, nStartPos
, nEndPos
, pDescr
) )
538 if( nStartPos
<= nTextLen
&& nEndPos
<= nTextLen
)
540 ESelection
aSelection( pConvertPara
[nStartPos
], pConvertPos
[nStartPos
],
541 pConvertPara
[nEndPos
], pConvertPos
[nEndPos
] );
543 SvxUnoTextBase
* pParent
= comphelper::getFromUnoTunnel
<SvxUnoTextBase
>( xParent
);
547 rtl::Reference
<SvxUnoTextRange
> pRange
= new SvxUnoTextRange( *pParent
);
549 pRange
->SetSelection(aSelection
);
554 OSL_FAIL("Array overflow while searching!");
561 bool SdUnoSearchReplaceShape::Search( const OUString
& rText
, sal_Int32
& nStartPos
, sal_Int32
& nEndPos
, SdUnoSearchReplaceDescriptor
* pDescr
) noexcept
563 OUString
aSearchStr( pDescr
->getSearchString() );
564 OUString
aText( rText
);
566 if( !pDescr
->IsCaseSensitive() )
568 aText
= aText
.toAsciiLowerCase();
569 aSearchStr
= aSearchStr
.toAsciiLowerCase();
572 sal_Int32 nFound
= aText
.indexOf( aSearchStr
, nStartPos
);
576 nEndPos
= nFound
+ aSearchStr
.getLength();
578 if(pDescr
->IsWords())
580 if( (nStartPos
> 0 && aText
[nStartPos
-1] > ' ') ||
581 (nEndPos
< aText
.getLength() && aText
[nEndPos
] > ' ') )
584 return Search( aText
, nStartPos
, nEndPos
, pDescr
);
594 ESelection
SdUnoSearchReplaceShape::GetSelection( const uno::Reference
< text::XTextRange
>& xTextRange
) noexcept
597 SvxUnoTextRangeBase
* pRange
= comphelper::getFromUnoTunnel
<SvxUnoTextRangeBase
>( xTextRange
);
600 aSel
= pRange
->GetSelection();
605 uno::Reference
< drawing::XShape
> SdUnoSearchReplaceShape::GetShape( const uno::Reference
< text::XTextRange
>& xTextRange
) noexcept
607 uno::Reference
< drawing::XShape
> xShape
;
611 uno::Reference
< text::XText
> xText( xTextRange
->getText() );
617 xShape
.set( xText
, uno::UNO_QUERY
);
620 uno::Reference
< text::XText
> xParent( xText
->getText() );
621 if(!xParent
.is() || xText
.get() == xParent
.get())
624 xText
= std::move(xParent
);
626 } while( !xShape
.is() );
633 /* ================================================================= */
634 /** this class holds the parameters and status of a search or replace
635 operation performed by class SdUnoSearchReplaceShape
638 SdUnoSearchReplaceDescriptor::SdUnoSearchReplaceDescriptor()
640 mpPropSet
.reset( new SvxItemPropertySet(ImplGetSearchPropertyMap(), SdrObject::GetGlobalDrawObjectItemPool()) );
643 mbCaseSensitive
= false;
647 SdUnoSearchReplaceDescriptor::~SdUnoSearchReplaceDescriptor() noexcept
652 OUString SAL_CALL
SdUnoSearchReplaceDescriptor::getSearchString()
657 void SAL_CALL
SdUnoSearchReplaceDescriptor::setSearchString( const OUString
& aString
)
659 maSearchStr
= aString
;
662 // XReplaceDescriptor
663 OUString SAL_CALL
SdUnoSearchReplaceDescriptor::getReplaceString()
668 void SAL_CALL
SdUnoSearchReplaceDescriptor::setReplaceString( const OUString
& aReplaceString
)
670 maReplaceStr
= aReplaceString
;
674 uno::Reference
< css::beans::XPropertySetInfo
> SAL_CALL
SdUnoSearchReplaceDescriptor::getPropertySetInfo()
676 SolarMutexGuard aGuard
;
677 return mpPropSet
->getPropertySetInfo();
680 void SAL_CALL
SdUnoSearchReplaceDescriptor::setPropertyValue( const OUString
& aPropertyName
, const css::uno::Any
& aValue
)
682 SolarMutexGuard aGuard
;
684 const SfxItemPropertyMapEntry
* pEntry
= mpPropSet
->getPropertyMapEntry(aPropertyName
);
688 switch( pEntry
? pEntry
->nWID
: -1 )
690 case WID_SEARCH_BACKWARDS
:
691 bOk
= (aValue
>>= mbBackwards
);
693 case WID_SEARCH_CASE
:
694 bOk
= (aValue
>>= mbCaseSensitive
);
696 case WID_SEARCH_WORDS
:
697 bOk
= (aValue
>>= mbWords
);
700 throw beans::UnknownPropertyException( aPropertyName
, static_cast<cppu::OWeakObject
*>(this));
704 throw lang::IllegalArgumentException();
707 uno::Any SAL_CALL
SdUnoSearchReplaceDescriptor::getPropertyValue( const OUString
& PropertyName
)
709 SolarMutexGuard aGuard
;
713 const SfxItemPropertyMapEntry
* pEntry
= mpPropSet
->getPropertyMapEntry(PropertyName
);
715 switch( pEntry
? pEntry
->nWID
: -1 )
717 case WID_SEARCH_BACKWARDS
:
718 aAny
<<= mbBackwards
;
720 case WID_SEARCH_CASE
:
721 aAny
<<= mbCaseSensitive
;
723 case WID_SEARCH_WORDS
:
727 throw beans::UnknownPropertyException( PropertyName
, static_cast<cppu::OWeakObject
*>(this));
733 void SAL_CALL
SdUnoSearchReplaceDescriptor::addPropertyChangeListener( const OUString
& , const css::uno::Reference
< css::beans::XPropertyChangeListener
>& ) {}
734 void SAL_CALL
SdUnoSearchReplaceDescriptor::removePropertyChangeListener( const OUString
& , const css::uno::Reference
< css::beans::XPropertyChangeListener
>& ) {}
735 void SAL_CALL
SdUnoSearchReplaceDescriptor::addVetoableChangeListener( const OUString
& , const css::uno::Reference
< css::beans::XVetoableChangeListener
>& ) {}
736 void SAL_CALL
SdUnoSearchReplaceDescriptor::removeVetoableChangeListener( const OUString
& , const css::uno::Reference
< css::beans::XVetoableChangeListener
>& ) {}
738 /* ================================================================= */
740 SdUnoFindAllAccess::SdUnoFindAllAccess( uno::Sequence
< uno::Reference
< uno::XInterface
> > const & rSequence
) noexcept
741 :maSequence( rSequence
)
745 SdUnoFindAllAccess::~SdUnoFindAllAccess() noexcept
750 uno::Type SAL_CALL
SdUnoFindAllAccess::getElementType()
752 return cppu::UnoType
<text::XTextRange
>::get();
755 sal_Bool SAL_CALL
SdUnoFindAllAccess::hasElements()
757 return maSequence
.hasElements();
761 sal_Int32 SAL_CALL
SdUnoFindAllAccess::getCount()
763 return maSequence
.getLength();
766 uno::Any SAL_CALL
SdUnoFindAllAccess::getByIndex( sal_Int32 Index
)
768 if( Index
< 0 || Index
>= getCount() )
769 throw lang::IndexOutOfBoundsException();
772 aAny
<<= maSequence
[Index
];
776 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */