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/lang/IndexOutOfBoundsException.hpp>
24 #include <vcl/svapp.hxx>
26 #include <svx/unoshape.hxx>
27 #include <svx/svdpool.hxx>
28 #include <svx/unoprov.hxx>
29 #include <editeng/unotext.hxx>
30 #include <tools/debug.hxx>
32 #include <unoprnms.hxx>
33 #include <unosrch.hxx>
35 using namespace ::com::sun::star
;
37 #define WID_SEARCH_BACKWARDS 0
38 #define WID_SEARCH_CASE 1
39 #define WID_SEARCH_WORDS 2
41 static const SfxItemPropertyMapEntry
* ImplGetSearchPropertyMap()
43 static const SfxItemPropertyMapEntry aSearchPropertyMap_Impl
[] =
45 { OUString(UNO_NAME_SEARCH_BACKWARDS
), WID_SEARCH_BACKWARDS
, cppu::UnoType
<bool>::get(), 0, 0 },
46 { OUString(UNO_NAME_SEARCH_CASE
), WID_SEARCH_CASE
, cppu::UnoType
<bool>::get(), 0, 0 },
47 { OUString(UNO_NAME_SEARCH_WORDS
), WID_SEARCH_WORDS
, cppu::UnoType
<bool>::get(), 0, 0 },
48 { OUString(), 0, css::uno::Type(), 0, 0 }
51 return aSearchPropertyMap_Impl
;
54 class SearchContext_impl
56 uno::Reference
< drawing::XShapes
> mxShapes
;
60 SearchContext_impl(uno::Reference
<drawing::XShapes
> const& xShapes
)
61 : mxShapes( xShapes
), mnIndex( -1 ) {}
63 uno::Reference
< drawing::XShape
> firstShape()
69 uno::Reference
< drawing::XShape
> nextShape()
71 uno::Reference
< drawing::XShape
> xShape
;
73 if( mxShapes
.is() && mxShapes
->getCount() > mnIndex
)
75 mxShapes
->getByIndex( mnIndex
) >>= xShape
;
81 /* ================================================================= */
82 /** this class implements a search or replace operation on a given
83 page or a given sdrobj
86 SdUnoSearchReplaceShape::SdUnoSearchReplaceShape( drawing::XDrawPage
* pPage
) throw()
91 SdUnoSearchReplaceShape::~SdUnoSearchReplaceShape() throw()
96 uno::Reference
< util::XReplaceDescriptor
> SAL_CALL
SdUnoSearchReplaceShape::createReplaceDescriptor()
98 return new SdUnoSearchReplaceDescriptor
;
101 sal_Int32 SAL_CALL
SdUnoSearchReplaceShape::replaceAll( const uno::Reference
< util::XSearchDescriptor
>& xDesc
)
103 SdUnoSearchReplaceDescriptor
* pDescr
= SdUnoSearchReplaceDescriptor::getImplementation( xDesc
);
104 if( pDescr
== nullptr )
107 sal_Int32 nFound
= 0;
109 uno::Reference
< drawing::XShapes
> xShapes
;
110 uno::Reference
< drawing::XShape
> xShape
;
112 std::vector
<SearchContext_impl
> aContexts
;
115 uno::Reference
< drawing::XDrawPage
> xPage( mpPage
);
117 xShapes
.set( xPage
, uno::UNO_QUERY
);
119 if( xShapes
.is() && (xShapes
->getCount() > 0) )
121 aContexts
.push_back(SearchContext_impl(xShapes
));
122 xShape
= aContexts
.back().firstShape();
133 uno::Reference
< text::XText
> xText(xShape
, uno::UNO_QUERY
);
134 uno::Reference
< text::XTextRange
> xRange(xText
, uno::UNO_QUERY
);
135 uno::Reference
< text::XTextRange
> xFound
;
139 xFound
= Search( xRange
, pDescr
);
143 xFound
->setString( pDescr
->getReplaceString() );
144 xRange
= xFound
->getEnd();
147 // done with xShape -> get next shape
149 // test if it's a group
150 uno::Reference
< drawing::XShapes
> xGroupShape( xShape
, uno::UNO_QUERY
);
151 if( xGroupShape
.is() && ( xGroupShape
->getCount() > 0 ) )
153 aContexts
.push_back(SearchContext_impl(xGroupShape
));
154 xShape
= aContexts
.back().firstShape();
158 if (!aContexts
.empty())
159 xShape
= aContexts
.back().nextShape();
164 // test parent contexts for next shape if none
165 // is found in the current context
166 while (!aContexts
.empty() && !xShape
.is())
168 aContexts
.pop_back();
169 if (!aContexts
.empty())
170 xShape
= aContexts
.back().nextShape();
178 uno::Reference
< css::util::XSearchDescriptor
> SAL_CALL
SdUnoSearchReplaceShape::createSearchDescriptor( )
180 return new SdUnoSearchReplaceDescriptor
;
183 uno::Reference
< css::container::XIndexAccess
> SAL_CALL
SdUnoSearchReplaceShape::findAll( const css::uno::Reference
< css::util::XSearchDescriptor
>& xDesc
)
185 SdUnoSearchReplaceDescriptor
* pDescr
= SdUnoSearchReplaceDescriptor::getImplementation( xDesc
);
186 if( pDescr
== nullptr )
187 return uno::Reference
< container::XIndexAccess
> ();
189 sal_Int32 nSequence
= 32;
190 sal_Int32 nFound
= 0;
192 uno::Sequence
< uno::Reference
< uno::XInterface
> > aSeq( nSequence
);
194 uno::Reference
< uno::XInterface
> * pArray
= aSeq
.getArray();
196 uno::Reference
< drawing::XShapes
> xShapes
;
197 uno::Reference
< drawing::XShape
> xShape
;
199 std::vector
<SearchContext_impl
> aContexts
;
202 uno::Reference
< drawing::XDrawPage
> xPage( mpPage
);
203 xShapes
.set( xPage
, uno::UNO_QUERY
);
205 if( xShapes
.is() && xShapes
->getCount() > 0 )
207 aContexts
.push_back(SearchContext_impl(xShapes
));
208 xShape
= aContexts
.back().firstShape();
219 uno::Reference
< text::XText
> xText(xShape
, uno::UNO_QUERY
);
220 uno::Reference
< text::XTextRange
> xRange(xText
, uno::UNO_QUERY
);
221 uno::Reference
< text::XTextRange
> xFound
;
225 xFound
= Search( xRange
, pDescr
);
229 if( nFound
>= nSequence
)
232 aSeq
.realloc( nSequence
);
233 pArray
= aSeq
.getArray();
236 pArray
[nFound
++] = xFound
;
238 xRange
= xFound
->getEnd();
240 // done with shape -> get next shape
242 // test if it's a group
243 uno::Reference
< drawing::XShapes
> xGroupShape
;
244 xGroupShape
.set( xShape
, uno::UNO_QUERY
);
246 if( xGroupShape
.is() && xGroupShape
->getCount() > 0 )
248 aContexts
.push_back(SearchContext_impl(xGroupShape
));
249 xShape
= aContexts
.back().firstShape();
253 if (!aContexts
.empty())
254 xShape
= aContexts
.back().nextShape();
259 // test parent contexts for next shape if none
260 // is found in the current context
261 while (!aContexts
.empty() && !xShape
.is())
263 aContexts
.pop_back();
264 if (!aContexts
.empty())
265 xShape
= aContexts
.back().nextShape();
269 if( nFound
!= nSequence
)
270 aSeq
.realloc( nFound
);
272 uno::Reference
<css::container::XIndexAccess
> xRet(new SdUnoFindAllAccess(aSeq
));
276 uno::Reference
< css::uno::XInterface
> SAL_CALL
SdUnoSearchReplaceShape::findFirst( const css::uno::Reference
< css::util::XSearchDescriptor
>& xDesc
)
278 uno::Reference
< text::XTextRange
> xRange( GetCurrentShape(), uno::UNO_QUERY
);
280 return findNext( xRange
, xDesc
);
282 return uno::Reference
< uno::XInterface
> ();
285 uno::Reference
< drawing::XShape
> SdUnoSearchReplaceShape::GetCurrentShape() const throw()
287 uno::Reference
< drawing::XShape
> xShape
;
291 uno::Reference
< drawing::XDrawPage
> xPage( mpPage
);
292 uno::Reference
< container::XIndexAccess
> xShapes( xPage
, uno::UNO_QUERY
);
295 if(xShapes
->getCount() > 0)
297 xShapes
->getByIndex(0) >>= xShape
;
306 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
)
308 SdUnoSearchReplaceDescriptor
* pDescr
= SdUnoSearchReplaceDescriptor::getImplementation( xDesc
);
310 uno::Reference
< uno::XInterface
> xFound
;
312 uno::Reference
< text::XTextRange
> xRange( xStartAt
, uno::UNO_QUERY
);
313 if(pDescr
&& xRange
.is() )
316 uno::Reference
< text::XTextRange
> xCurrentRange( xStartAt
, uno::UNO_QUERY
);
318 uno::Reference
< drawing::XShape
> xCurrentShape( GetShape( xCurrentRange
) );
320 while(!xFound
.is() && xRange
.is())
322 xFound
= Search( xRange
, pDescr
);
325 // we need a new starting range now
330 uno::Reference
< drawing::XDrawPage
> xPage( mpPage
);
332 // we do a page wide search, so skip to the next shape here
333 uno::Reference
< container::XIndexAccess
> xShapes( xPage
, uno::UNO_QUERY
);
335 // get next shape on our page
338 uno::Reference
< drawing::XShape
> xFound2( GetNextShape( xShapes
, xCurrentShape
) );
339 if( xFound2
.is() && (xFound2
.get() != xCurrentShape
.get()) )
340 xCurrentShape
= xFound2
;
342 xCurrentShape
= nullptr;
344 xRange
.set( xCurrentShape
, uno::UNO_QUERY
);
345 if(!(xCurrentShape
.is() && (xRange
.is())))
351 // we search only in this shape, so end search if we have
352 // not found anything
360 /** this method returns the shape that follows xCurrentShape in the shape collection xShapes.
361 It steps recursive into groupshapes and returns the xCurrentShape if it is the last
362 shape in this collection */
363 uno::Reference
< drawing::XShape
> SdUnoSearchReplaceShape::GetNextShape( const uno::Reference
< container::XIndexAccess
>& xShapes
, const uno::Reference
< drawing::XShape
>& xCurrentShape
) throw()
365 uno::Reference
< drawing::XShape
> xFound
;
367 if(xShapes
.is() && xCurrentShape
.is())
369 const sal_Int32 nCount
= xShapes
->getCount();
370 for( sal_Int32 i
= 0; i
< nCount
; i
++ )
372 uno::Reference
< drawing::XShape
> xSearchShape
;
373 xShapes
->getByIndex(i
) >>= xSearchShape
;
375 if( xSearchShape
.is() )
377 uno::Reference
< container::XIndexAccess
> xGroup( xSearchShape
, uno::UNO_QUERY
);
379 if( xCurrentShape
.get() == xSearchShape
.get() )
381 if( xGroup
.is() && xGroup
->getCount() > 0 )
383 xGroup
->getByIndex( 0 ) >>= xFound
;
389 xShapes
->getByIndex( i
) >>= xFound
;
391 xFound
= xCurrentShape
;
396 else if( xGroup
.is() )
398 xFound
= GetNextShape( xGroup
, xCurrentShape
);
401 if( xFound
.get() == xCurrentShape
.get() )
403 // the current shape was found at the end of the group
407 xShapes
->getByIndex(i
) >>= xFound
;
420 uno::Reference
< text::XTextRange
> SdUnoSearchReplaceShape::Search( const uno::Reference
< text::XTextRange
>& xText
, SdUnoSearchReplaceDescriptor
* pDescr
)
423 return uno::Reference
< text::XTextRange
> ();
425 uno::Reference
< text::XText
> xParent( xText
->getText() );
429 xParent
.set( xText
, uno::UNO_QUERY
);
432 const OUString
aText( xParent
->getString() );
434 const sal_Int32 nTextLen
= aText
.getLength();
436 std::unique_ptr
<sal_Int32
[]> pConvertPos( new sal_Int32
[nTextLen
+2] );
437 std::unique_ptr
<sal_Int32
[]> pConvertPara( new sal_Int32
[nTextLen
+2] );
439 const sal_Unicode
* pText
= aText
.getStr();
441 sal_Int32
* pPos
= pConvertPos
.get();
442 sal_Int32
* pPara
= pConvertPara
.get();
444 sal_Int32 nLastPos
= 0, nLastPara
= 0;
446 uno::Reference
< container::XEnumerationAccess
> xEnumAccess( xParent
, uno::UNO_QUERY
);
448 // first we fill the arrays with the position and paragraph for every character
450 if( xEnumAccess
.is() )
452 uno::Reference
< container::XEnumeration
> xParaEnum( xEnumAccess
->createEnumeration() );
454 while(xParaEnum
->hasMoreElements())
457 uno::Reference
< text::XTextContent
> xParagraph( xParaEnum
->nextElement(), uno::UNO_QUERY
);
458 if( xParagraph
.is() )
459 xEnumAccess
.set(xParagraph
, css::uno::UNO_QUERY
);
463 if( xEnumAccess
.is() )
465 uno::Reference
< container::XEnumeration
> xPortionEnum( xEnumAccess
->createEnumeration() );
466 if( xPortionEnum
.is() )
468 while(xPortionEnum
->hasMoreElements())
470 uno::Reference
< text::XTextRange
> xPortion( xPortionEnum
->nextElement(), uno::UNO_QUERY
);
473 const OUString
aPortion( xPortion
->getString() );
474 const sal_Int32 nLen
= aPortion
.getLength();
476 ESelection
aStartSel( GetSelection( xPortion
->getStart() ) );
477 ESelection
aEndSel( GetSelection( xPortion
->getEnd() ) );
479 // special case for empty portions with content or length one portions with content (fields)
480 if( (aStartSel
.nStartPos
== aEndSel
.nStartPos
) || ( (aStartSel
.nStartPos
== (aEndSel
.nStartPos
- 1)) && (nLen
> 1) ) )
482 for( sal_Int32 i
= 0; i
< nLen
; i
++ )
484 if( ndbg
< (nTextLen
+2) )
486 *pPos
++ = aStartSel
.nStartPos
;
487 *pPara
++ = aStartSel
.nStartPara
;
494 OSL_FAIL( "array overflow while searching" );
498 nLastPos
= aStartSel
.nStartPos
;
503 for( sal_Int32 i
= 0; i
< nLen
; i
++ )
505 if( ndbg
< (nTextLen
+2) )
507 *pPos
++ = aStartSel
.nStartPos
++;
508 *pPara
++ = aStartSel
.nStartPara
;
515 OSL_FAIL( "array overflow while searching" );
519 nLastPos
= aStartSel
.nStartPos
- 1;
520 DBG_ASSERT( aEndSel
.nStartPos
== aStartSel
.nStartPos
, "Search is not working" );
522 nLastPara
= aStartSel
.nStartPara
;
528 if( ndbg
< (nTextLen
+2) )
530 *pPos
++ = nLastPos
+ 1;
531 *pPara
++ = nLastPara
;
537 OSL_FAIL( "array overflow while searching" );
542 uno::Reference
< text::XTextRange
> xFound
;
545 uno::Reference
< text::XTextRange
> xRangeRef( xText
, uno::UNO_QUERY
);
547 aSel
= GetSelection( xRangeRef
);
550 sal_Int32 nEndPos
= 0;
551 for( nStartPos
= 0; nStartPos
< nTextLen
; nStartPos
++ )
553 if( pConvertPara
[nStartPos
] == aSel
.nStartPara
&& pConvertPos
[nStartPos
] == aSel
.nStartPos
)
557 if( Search( aText
, nStartPos
, nEndPos
, pDescr
) )
559 if( nStartPos
<= nTextLen
&& nEndPos
<= nTextLen
)
561 ESelection
aSelection( pConvertPara
[nStartPos
], pConvertPos
[nStartPos
],
562 pConvertPara
[nEndPos
], pConvertPos
[nEndPos
] );
563 SvxUnoTextRange
*pRange
;
565 SvxUnoTextBase
* pParent
= SvxUnoTextBase::getImplementation( xParent
);
569 pRange
= new SvxUnoTextRange( *pParent
);
571 pRange
->SetSelection(aSelection
);
577 OSL_FAIL("Array overflow while searching!");
584 bool SdUnoSearchReplaceShape::Search( const OUString
& rText
, sal_Int32
& nStartPos
, sal_Int32
& nEndPos
, SdUnoSearchReplaceDescriptor
* pDescr
) throw()
586 OUString
aSearchStr( pDescr
->getSearchString() );
587 OUString
aText( rText
);
589 if( !pDescr
->IsCaseSensitive() )
591 aText
= aText
.toAsciiLowerCase();
592 aSearchStr
= aSearchStr
.toAsciiLowerCase();
595 sal_Int32 nFound
= aText
.indexOf( aSearchStr
, nStartPos
);
599 nEndPos
= nFound
+ aSearchStr
.getLength();
601 if(pDescr
->IsWords())
603 if( (nStartPos
> 0 && aText
[nStartPos
-1] > ' ') ||
604 (nEndPos
< aText
.getLength() && aText
[nEndPos
] > ' ') )
607 return Search( aText
, nStartPos
, nEndPos
, pDescr
);
617 ESelection
SdUnoSearchReplaceShape::GetSelection( const uno::Reference
< text::XTextRange
>& xTextRange
) throw()
620 SvxUnoTextRangeBase
* pRange
= SvxUnoTextRangeBase::getImplementation( xTextRange
);
623 aSel
= pRange
->GetSelection();
628 uno::Reference
< drawing::XShape
> SdUnoSearchReplaceShape::GetShape( const uno::Reference
< text::XTextRange
>& xTextRange
) throw()
630 uno::Reference
< drawing::XShape
> xShape
;
634 uno::Reference
< text::XText
> xText( xTextRange
->getText() );
640 xShape
.set( xText
, uno::UNO_QUERY
);
643 uno::Reference
< text::XText
> xParent( xText
->getText() );
644 if(!xParent
.is() || xText
.get() == xParent
.get())
649 } while( !xShape
.is() );
656 /* ================================================================= */
657 /** this class holds the parameters and status of a search or replace
658 operation performed by class SdUnoSearchReplaceShape
661 UNO3_GETIMPLEMENTATION_IMPL( SdUnoSearchReplaceDescriptor
);
663 SdUnoSearchReplaceDescriptor::SdUnoSearchReplaceDescriptor()
665 mpPropSet
.reset( new SvxItemPropertySet(ImplGetSearchPropertyMap(), SdrObject::GetGlobalDrawObjectItemPool()) );
668 mbCaseSensitive
= false;
672 SdUnoSearchReplaceDescriptor::~SdUnoSearchReplaceDescriptor() throw()
677 OUString SAL_CALL
SdUnoSearchReplaceDescriptor::getSearchString()
682 void SAL_CALL
SdUnoSearchReplaceDescriptor::setSearchString( const OUString
& aString
)
684 maSearchStr
= aString
;
687 // XReplaceDescriptor
688 OUString SAL_CALL
SdUnoSearchReplaceDescriptor::getReplaceString()
693 void SAL_CALL
SdUnoSearchReplaceDescriptor::setReplaceString( const OUString
& aReplaceString
)
695 maReplaceStr
= aReplaceString
;
699 uno::Reference
< css::beans::XPropertySetInfo
> SAL_CALL
SdUnoSearchReplaceDescriptor::getPropertySetInfo()
701 SolarMutexGuard aGuard
;
702 return mpPropSet
->getPropertySetInfo();
705 void SAL_CALL
SdUnoSearchReplaceDescriptor::setPropertyValue( const OUString
& aPropertyName
, const css::uno::Any
& aValue
)
707 SolarMutexGuard aGuard
;
709 const SfxItemPropertySimpleEntry
* pEntry
= mpPropSet
->getPropertyMapEntry(aPropertyName
);
713 switch( pEntry
? pEntry
->nWID
: -1 )
715 case WID_SEARCH_BACKWARDS
:
716 bOk
= (aValue
>>= mbBackwards
);
718 case WID_SEARCH_CASE
:
719 bOk
= (aValue
>>= mbCaseSensitive
);
721 case WID_SEARCH_WORDS
:
722 bOk
= (aValue
>>= mbWords
);
725 throw beans::UnknownPropertyException( aPropertyName
, static_cast<cppu::OWeakObject
*>(this));
729 throw lang::IllegalArgumentException();
732 uno::Any SAL_CALL
SdUnoSearchReplaceDescriptor::getPropertyValue( const OUString
& PropertyName
)
734 SolarMutexGuard aGuard
;
738 const SfxItemPropertySimpleEntry
* pEntry
= mpPropSet
->getPropertyMapEntry(PropertyName
);
740 switch( pEntry
? pEntry
->nWID
: -1 )
742 case WID_SEARCH_BACKWARDS
:
743 aAny
<<= mbBackwards
;
745 case WID_SEARCH_CASE
:
746 aAny
<<= mbCaseSensitive
;
748 case WID_SEARCH_WORDS
:
752 throw beans::UnknownPropertyException( PropertyName
, static_cast<cppu::OWeakObject
*>(this));
758 void SAL_CALL
SdUnoSearchReplaceDescriptor::addPropertyChangeListener( const OUString
& , const css::uno::Reference
< css::beans::XPropertyChangeListener
>& ) {}
759 void SAL_CALL
SdUnoSearchReplaceDescriptor::removePropertyChangeListener( const OUString
& , const css::uno::Reference
< css::beans::XPropertyChangeListener
>& ) {}
760 void SAL_CALL
SdUnoSearchReplaceDescriptor::addVetoableChangeListener( const OUString
& , const css::uno::Reference
< css::beans::XVetoableChangeListener
>& ) {}
761 void SAL_CALL
SdUnoSearchReplaceDescriptor::removeVetoableChangeListener( const OUString
& , const css::uno::Reference
< css::beans::XVetoableChangeListener
>& ) {}
763 /* ================================================================= */
765 SdUnoFindAllAccess::SdUnoFindAllAccess( uno::Sequence
< uno::Reference
< uno::XInterface
> > const & rSequence
) throw()
766 :maSequence( rSequence
)
770 SdUnoFindAllAccess::~SdUnoFindAllAccess() throw()
775 uno::Type SAL_CALL
SdUnoFindAllAccess::getElementType()
777 return cppu::UnoType
<text::XTextRange
>::get();
780 sal_Bool SAL_CALL
SdUnoFindAllAccess::hasElements()
782 return maSequence
.hasElements();
786 sal_Int32 SAL_CALL
SdUnoFindAllAccess::getCount()
788 return maSequence
.getLength();
791 uno::Any SAL_CALL
SdUnoFindAllAccess::getByIndex( sal_Int32 Index
)
793 if( Index
< 0 || Index
>= getCount() )
794 throw lang::IndexOutOfBoundsException();
797 aAny
<<= maSequence
[Index
];
801 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */