xref: /trunk/main/i18npool/source/search/textsearch.cxx (revision 91144cd0085a7583d2099b982122deb2184ab956)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_search.hxx"
26 
27 #include "textsearch.hxx"
28 #include "levdis.hxx"
29 #include <com/sun/star/lang/Locale.hpp>
30 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
31 #include <comphelper/processfactory.hxx>
32 #include <com/sun/star/i18n/UnicodeType.hpp>
33 #include <com/sun/star/util/SearchFlags.hpp>
34 #include <com/sun/star/i18n/WordType.hpp>
35 #include <com/sun/star/i18n/ScriptType.hpp>
36 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
37 #include <com/sun/star/i18n/KCharacterType.hpp>
38 #include <com/sun/star/registry/XRegistryKey.hpp>
39 #include <cppuhelper/factory.hxx>
40 #include <cppuhelper/weak.hxx>
41 
42 #ifdef _MSC_VER
43 // get rid of that dumb compiler warning
44 // identifier was truncated to '255' characters in the debug information
45 // for STL template usage, if .pdb files are to be created
46 #pragma warning( disable: 4786 )
47 #endif
48 
49 #include <string.h>
50 
51 using namespace ::com::sun::star::util;
52 using namespace ::com::sun::star::uno;
53 using namespace ::com::sun::star::lang;
54 using namespace ::com::sun::star::i18n;
55 using namespace ::rtl;
56 
57 static sal_Int32 COMPLEX_TRANS_MASK_TMP =
58     TransliterationModules_ignoreBaFa_ja_JP |
59     TransliterationModules_ignoreIterationMark_ja_JP |
60     TransliterationModules_ignoreTiJi_ja_JP |
61     TransliterationModules_ignoreHyuByu_ja_JP |
62     TransliterationModules_ignoreSeZe_ja_JP |
63     TransliterationModules_ignoreIandEfollowedByYa_ja_JP |
64     TransliterationModules_ignoreKiKuFollowedBySa_ja_JP |
65     TransliterationModules_ignoreProlongedSoundMark_ja_JP;
66 static const sal_Int32 COMPLEX_TRANS_MASK = COMPLEX_TRANS_MASK_TMP | TransliterationModules_IGNORE_KANA | TransliterationModules_FULLWIDTH_HALFWIDTH;
67 static const sal_Int32 SIMPLE_TRANS_MASK = ~COMPLEX_TRANS_MASK;
68 static const sal_Int32 REGEX_TRANS_MASK = ~(COMPLEX_TRANS_MASK | TransliterationModules_IGNORE_CASE | TransliterationModules_UPPERCASE_LOWERCASE | TransliterationModules_LOWERCASE_UPPERCASE);
69     // Above 2 transliteration is simple but need to take effect in
70     // complex transliteration
71 
TextSearch(const Reference<XMultiServiceFactory> & rxMSF)72 TextSearch::TextSearch(const Reference < XMultiServiceFactory > & rxMSF)
73         : xMSF( rxMSF )
74         , pJumpTable( 0 )
75         , pJumpTable2( 0 )
76         , pRegexMatcher( NULL )
77         , pWLD( 0 )
78 {
79     SearchOptions aOpt;
80     aOpt.algorithmType = SearchAlgorithms_ABSOLUTE;
81     aOpt.searchFlag = SearchFlags::ALL_IGNORE_CASE;
82     //aOpt.Locale = ???;
83     setOptions( aOpt );
84 }
85 
~TextSearch()86 TextSearch::~TextSearch()
87 {
88     delete pRegexMatcher;
89     delete pWLD;
90     delete pJumpTable;
91     delete pJumpTable2;
92 }
93 
setOptions(const SearchOptions & rOptions)94 void TextSearch::setOptions( const SearchOptions& rOptions )
95 {
96     aSrchPara = rOptions;
97 
98     delete pRegexMatcher, pRegexMatcher = NULL;
99     delete pWLD, pWLD = 0;
100     delete pJumpTable, pJumpTable = 0;
101     delete pJumpTable2, pJumpTable2 = 0;
102 
103     // Create Transliteration class
104     if( aSrchPara.transliterateFlags & SIMPLE_TRANS_MASK )
105     {
106         if( !xTranslit.is() )
107         {
108             Reference < XInterface > xI = xMSF->createInstance(
109                     OUString::createFromAscii(
110                         "com.sun.star.i18n.Transliteration"));
111             if ( xI.is() )
112                 xI->queryInterface( ::getCppuType(
113                             (const Reference< XExtendedTransliteration >*)0))
114                     >>= xTranslit;
115         }
116         // Load transliteration module
117         if( xTranslit.is() )
118             xTranslit->loadModule(
119                     (TransliterationModules)( aSrchPara.transliterateFlags & SIMPLE_TRANS_MASK ),
120                     aSrchPara.Locale);
121     }
122     else if( xTranslit.is() )
123         xTranslit = 0;
124 
125     // Create Transliteration for 2<->1, 2<->2 transliteration
126     if ( aSrchPara.transliterateFlags & COMPLEX_TRANS_MASK )
127     {
128         if( !xTranslit2.is() )
129         {
130             Reference < XInterface > xI = xMSF->createInstance(
131                     OUString::createFromAscii(
132                         "com.sun.star.i18n.Transliteration"));
133             if ( xI.is() )
134                 xI->queryInterface( ::getCppuType(
135                             (const Reference< XExtendedTransliteration >*)0))
136                     >>= xTranslit2;
137         }
138         // Load transliteration module
139         if( xTranslit2.is() )
140             xTranslit2->loadModule(
141                     (TransliterationModules)( aSrchPara.transliterateFlags & COMPLEX_TRANS_MASK ),
142                     aSrchPara.Locale);
143     }
144 
145     if ( !xBreak.is() )
146     {
147         Reference < XInterface > xI = xMSF->createInstance(
148                 OUString::createFromAscii( "com.sun.star.i18n.BreakIterator"));
149         if( xI.is() )
150             xI->queryInterface( ::getCppuType(
151                         (const Reference< XBreakIterator >*)0))
152                 >>= xBreak;
153     }
154 
155     sSrchStr = aSrchPara.searchString;
156 
157     // use transliteration here
158     if ( xTranslit.is() &&
159      aSrchPara.transliterateFlags & SIMPLE_TRANS_MASK )
160         sSrchStr = xTranslit->transliterateString2String(
161                 aSrchPara.searchString, 0, aSrchPara.searchString.getLength());
162 
163     if ( xTranslit2.is() &&
164      aSrchPara.transliterateFlags & COMPLEX_TRANS_MASK )
165     sSrchStr2 = xTranslit2->transliterateString2String(
166             aSrchPara.searchString, 0, aSrchPara.searchString.getLength());
167 
168     // When start or end of search string is a complex script type, we need to
169     // make sure the result boundary is not located in the middle of cell.
170     checkCTLStart = (xBreak.is() && (xBreak->getScriptType(sSrchStr, 0) ==
171                 ScriptType::COMPLEX));
172     checkCTLEnd = (xBreak.is() && (xBreak->getScriptType(sSrchStr,
173                     sSrchStr.getLength()-1) == ScriptType::COMPLEX));
174 
175     switch( aSrchPara.algorithmType)
176     {
177         case SearchAlgorithms_REGEXP:
178             fnForward = &TextSearch::RESrchFrwrd;
179             fnBackward = &TextSearch::RESrchBkwrd;
180             RESrchPrepare( aSrchPara);
181             break;
182 
183         case SearchAlgorithms_APPROXIMATE:
184             fnForward = &TextSearch::ApproxSrchFrwrd;
185             fnBackward = &TextSearch::ApproxSrchBkwrd;
186 
187             pWLD = new WLevDistance( sSrchStr.getStr(), aSrchPara.changedChars,
188                     aSrchPara.insertedChars, aSrchPara.deletedChars,
189                     0 != (SearchFlags::LEV_RELAXED & aSrchPara.searchFlag ) );
190 
191             nLimit = pWLD->GetLimit();
192             break;
193 
194         default:
195             fnForward = &TextSearch::NSrchFrwrd;
196             fnBackward = &TextSearch::NSrchBkwrd;
197             break;
198     }
199 }
200 
FindPosInSeq_Impl(const Sequence<sal_Int32> & rOff,sal_Int32 nPos)201 sal_Int32 FindPosInSeq_Impl( const Sequence <sal_Int32>& rOff, sal_Int32 nPos )
202 {
203     sal_Int32 nRet = 0, nEnd = rOff.getLength();
204     while( nRet < nEnd && nPos > rOff[ nRet ] ) ++nRet;
205     return nRet;
206 }
207 
isCellStart(const OUString & searchStr,sal_Int32 nPos)208 sal_Bool TextSearch::isCellStart(const OUString& searchStr, sal_Int32 nPos)
209 {
210     sal_Int32 nDone;
211     return nPos == xBreak->previousCharacters(searchStr, nPos+1,
212             aSrchPara.Locale, CharacterIteratorMode::SKIPCELL, 1, nDone);
213 }
214 
searchForward(const OUString & searchStr,sal_Int32 startPos,sal_Int32 endPos)215 SearchResult TextSearch::searchForward( const OUString& searchStr, sal_Int32 startPos, sal_Int32 endPos )
216 {
217     SearchResult sres;
218 
219     OUString in_str(searchStr);
220     sal_Int32 newStartPos = startPos;
221     sal_Int32 newEndPos = endPos;
222 
223     bUsePrimarySrchStr = true;
224 
225     if ( xTranslit.is() )
226     {
227         // apply normal transliteration (1<->1, 1<->0)
228         com::sun::star::uno::Sequence <sal_Int32> offset( in_str.getLength());
229         in_str = xTranslit->transliterate( searchStr, 0, in_str.getLength(), offset );
230 
231         // JP 20.6.2001: also the start and end positions must be corrected!
232         if( startPos )
233             newStartPos = FindPosInSeq_Impl( offset, startPos );
234 
235         if( endPos < searchStr.getLength() )
236         newEndPos = FindPosInSeq_Impl( offset, endPos );
237         else
238             newEndPos = in_str.getLength();
239 
240         sres = (this->*fnForward)( in_str, newStartPos, newEndPos );
241 
242         sal_Int32 nOffsetLength = offset.getLength();
243         sal_Int32 nStartOffset = 0;
244         for ( int k = 0; k < sres.startOffset.getLength(); k++ )
245         {
246             nStartOffset = sres.startOffset[k];
247             if ( nStartOffset )
248             {
249                 if ( nStartOffset < nOffsetLength )
250                     sres.startOffset[k] = offset[nStartOffset];
251                 else
252                     sres.startOffset[k] = offset[offset.getLength()-1] +1;
253             }
254             // JP 20.6.2001: end is ever exclusive and then don't return
255             //               the position of the next character - return the
256             //               next position behind the last found character!
257             //               "a b c" find "b" must return 2,3 and not 2,4!!!
258             if (sres.endOffset[k])
259           sres.endOffset[k] = offset[sres.endOffset[k]-1] + 1;
260         }
261     }
262     else
263     {
264         sres = (this->*fnForward)( in_str, startPos, endPos );
265     }
266 
267     if ( xTranslit2.is() && aSrchPara.algorithmType != SearchAlgorithms_REGEXP)
268     {
269         SearchResult sres2;
270 
271     in_str = OUString(searchStr);
272         com::sun::star::uno::Sequence <sal_Int32> offset( in_str.getLength());
273 
274         in_str = xTranslit2->transliterate( searchStr, 0, in_str.getLength(), offset );
275 
276         if( startPos )
277             startPos = FindPosInSeq_Impl( offset, startPos );
278 
279         if( endPos < searchStr.getLength() )
280             endPos = FindPosInSeq_Impl( offset, endPos );
281         else
282             endPos = in_str.getLength();
283 
284     bUsePrimarySrchStr = false;
285         sres2 = (this->*fnForward)( in_str, startPos, endPos );
286 
287         for ( int k = 0; k < sres2.startOffset.getLength(); k++ )
288         {
289             if (sres2.startOffset[k])
290           sres2.startOffset[k] = offset[sres2.startOffset[k]-1] + 1;
291             if (sres2.endOffset[k])
292           sres2.endOffset[k] = offset[sres2.endOffset[k]-1] + 1;
293         }
294 
295     // pick first and long one
296     if ( sres.subRegExpressions == 0)
297         return sres2;
298     if ( sres2.subRegExpressions == 1)
299     {
300         if ( sres.startOffset[0] > sres2.startOffset[0])
301             return sres2;
302         else if ( sres.startOffset[0] == sres2.startOffset[0] &&
303             sres.endOffset[0] < sres2.endOffset[0])
304             return sres2;
305     }
306     }
307 
308     return sres;
309 }
310 
searchBackward(const OUString & searchStr,sal_Int32 startPos,sal_Int32 endPos)311 SearchResult TextSearch::searchBackward( const OUString& searchStr, sal_Int32 startPos, sal_Int32 endPos )
312 {
313     SearchResult sres;
314 
315     OUString in_str(searchStr);
316     sal_Int32 newStartPos = startPos;
317     sal_Int32 newEndPos = endPos;
318 
319     bUsePrimarySrchStr = true;
320 
321     if ( xTranslit.is() )
322     {
323         // apply only simple 1<->1 transliteration here
324         com::sun::star::uno::Sequence <sal_Int32> offset( in_str.getLength());
325     in_str = xTranslit->transliterate( searchStr, 0, in_str.getLength(), offset );
326 
327         // JP 20.6.2001: also the start and end positions must be corrected!
328         if( startPos < searchStr.getLength() )
329             newStartPos = FindPosInSeq_Impl( offset, startPos );
330     else
331         newStartPos = in_str.getLength();
332 
333         if( endPos )
334         newEndPos = FindPosInSeq_Impl( offset, endPos );
335 
336         sres = (this->*fnBackward)( in_str, newStartPos, newEndPos );
337 
338         sal_Int32 nOffsetLength = offset.getLength();
339         sal_Int32 nEndOffset = 0;
340         for ( int k = 0; k < sres.startOffset.getLength(); k++ )
341         {
342             if (sres.startOffset[k])
343           sres.startOffset[k] = offset[sres.startOffset[k] - 1] + 1;
344             // JP 20.6.2001: end is ever exclusive and then don't return
345             //               the position of the next character - return the
346             //               next position behind the last found character!
347             //               "a b c" find "b" must return 2,3 and not 2,4!!!
348             nEndOffset = sres.endOffset[k];
349             if ( nEndOffset )
350             {
351                 if ( nEndOffset < nOffsetLength )
352                     sres.endOffset[k] = offset[nEndOffset];
353                 else
354                     sres.endOffset[k] = offset[offset.getLength()-1] +1;
355             }
356         }
357     }
358     else
359     {
360         sres = (this->*fnBackward)( in_str, startPos, endPos );
361     }
362 
363     if ( xTranslit2.is() && aSrchPara.algorithmType != SearchAlgorithms_REGEXP )
364     {
365     SearchResult sres2;
366 
367     in_str = OUString(searchStr);
368         com::sun::star::uno::Sequence <sal_Int32> offset( in_str.getLength());
369 
370         in_str = xTranslit2->transliterate(searchStr, 0, in_str.getLength(), offset);
371 
372         if( startPos < searchStr.getLength() )
373             startPos = FindPosInSeq_Impl( offset, startPos );
374         else
375             startPos = in_str.getLength();
376 
377         if( endPos )
378             endPos = FindPosInSeq_Impl( offset, endPos );
379 
380     bUsePrimarySrchStr = false;
381     sres2 = (this->*fnBackward)( in_str, startPos, endPos );
382 
383         for( int k = 0; k < sres2.startOffset.getLength(); k++ )
384         {
385             if (sres2.startOffset[k])
386                 sres2.startOffset[k] = offset[sres2.startOffset[k]-1]+1;
387             if (sres2.endOffset[k])
388                 sres2.endOffset[k] = offset[sres2.endOffset[k]-1]+1;
389         }
390 
391     // pick last and long one
392     if ( sres.subRegExpressions == 0 )
393         return sres2;
394     if ( sres2.subRegExpressions == 1 )
395     {
396         if ( sres.startOffset[0] < sres2.startOffset[0] )
397             return sres2;
398         if ( sres.startOffset[0] == sres2.startOffset[0] &&
399         sres.endOffset[0] > sres2.endOffset[0] )
400             return sres2;
401     }
402     }
403 
404     return sres;
405 }
406 
407 //---------------------------------------------------------------------
408 
IsDelimiter(const OUString & rStr,sal_Int32 nPos) const409 bool TextSearch::IsDelimiter( const OUString& rStr, sal_Int32 nPos ) const
410 {
411     bool bRet = 1;
412     if( '\x7f' != rStr[nPos])
413     {
414         if ( !xCharClass.is() )
415         {
416             Reference < XInterface > xI = xMSF->createInstance(
417                     OUString::createFromAscii( "com.sun.star.i18n.CharacterClassification"));
418             if( xI.is() )
419                 xI->queryInterface( ::getCppuType(
420                             (const Reference< XCharacterClassification >*)0))
421                     >>= xCharClass;
422         }
423         if ( xCharClass.is() )
424         {
425             sal_Int32 nCType = xCharClass->getCharacterType( rStr, nPos,
426                     aSrchPara.Locale );
427             if( 0 != (( KCharacterType::DIGIT | KCharacterType::ALPHA |
428                             KCharacterType::LETTER ) & nCType ) )
429                 bRet = 0;
430         }
431     }
432     return bRet;
433 }
434 
435 // --------- helper methods for Boyer-Moore like text searching ----------
436 // TODO: use ICU's regex UREGEX_LITERAL mode instead when it becomes available
437 
MakeForwardTab()438 void TextSearch::MakeForwardTab()
439 {
440     // create the jumptable for the search text
441     if( pJumpTable )
442     {
443         if( bIsForwardTab )
444             return ;                                        // the jumpTable is ok
445         delete pJumpTable;
446     }
447     bIsForwardTab = true;
448 
449     sal_Int32 n, nLen = sSrchStr.getLength();
450     pJumpTable = new TextSearchJumpTable;
451 
452     for( n = 0; n < nLen - 1; ++n )
453     {
454         sal_Unicode cCh = sSrchStr[n];
455         sal_Int32 nDiff = nLen - n - 1;
456     TextSearchJumpTable::value_type aEntry( cCh, nDiff );
457 
458         ::std::pair< TextSearchJumpTable::iterator, bool > aPair =
459             pJumpTable->insert( aEntry );
460         if ( !aPair.second )
461             (*(aPair.first)).second = nDiff;
462     }
463 }
464 
MakeForwardTab2()465 void TextSearch::MakeForwardTab2()
466 {
467     // create the jumptable for the search text
468     if( pJumpTable2 )
469     {
470         if( bIsForwardTab )
471             return ;                                        // the jumpTable is ok
472         delete pJumpTable2;
473     }
474     bIsForwardTab = true;
475 
476     sal_Int32 n, nLen = sSrchStr2.getLength();
477     pJumpTable2 = new TextSearchJumpTable;
478 
479     for( n = 0; n < nLen - 1; ++n )
480     {
481         sal_Unicode cCh = sSrchStr2[n];
482         sal_Int32 nDiff = nLen - n - 1;
483 
484     TextSearchJumpTable::value_type aEntry( cCh, nDiff );
485         ::std::pair< TextSearchJumpTable::iterator, bool > aPair =
486             pJumpTable2->insert( aEntry );
487         if ( !aPair.second )
488             (*(aPair.first)).second = nDiff;
489     }
490 }
491 
MakeBackwardTab()492 void TextSearch::MakeBackwardTab()
493 {
494     // create the jumptable for the search text
495     if( pJumpTable )
496     {
497         if( !bIsForwardTab )
498             return ;                                        // the jumpTable is ok
499         delete pJumpTable;
500     }
501     bIsForwardTab = false;
502 
503     sal_Int32 n, nLen = sSrchStr.getLength();
504     pJumpTable = new TextSearchJumpTable;
505 
506     for( n = nLen-1; n > 0; --n )
507     {
508         sal_Unicode cCh = sSrchStr[n];
509         TextSearchJumpTable::value_type aEntry( cCh, n );
510         ::std::pair< TextSearchJumpTable::iterator, bool > aPair =
511             pJumpTable->insert( aEntry );
512         if ( !aPair.second )
513             (*(aPair.first)).second = n;
514     }
515 }
516 
MakeBackwardTab2()517 void TextSearch::MakeBackwardTab2()
518 {
519     // create the jumptable for the search text
520     if( pJumpTable2 )
521     {
522         if( !bIsForwardTab )
523             return ;                                        // the jumpTable is ok
524         delete pJumpTable2;
525     }
526     bIsForwardTab = false;
527 
528     sal_Int32 n, nLen = sSrchStr2.getLength();
529     pJumpTable2 = new TextSearchJumpTable;
530 
531     for( n = nLen-1; n > 0; --n )
532     {
533         sal_Unicode cCh = sSrchStr2[n];
534         TextSearchJumpTable::value_type aEntry( cCh, n );
535         ::std::pair< TextSearchJumpTable::iterator, bool > aPair =
536             pJumpTable2->insert( aEntry );
537         if ( !aPair.second )
538             (*(aPair.first)).second = n;
539     }
540 }
541 
GetDiff(const sal_Unicode cChr) const542 sal_Int32 TextSearch::GetDiff( const sal_Unicode cChr ) const
543 {
544     TextSearchJumpTable *pJump;
545     OUString sSearchKey;
546 
547     if ( bUsePrimarySrchStr ) {
548       pJump = pJumpTable;
549       sSearchKey = sSrchStr;
550     } else {
551       pJump = pJumpTable2;
552       sSearchKey = sSrchStr2;
553     }
554 
555     TextSearchJumpTable::const_iterator iLook = pJump->find( cChr );
556     if ( iLook == pJump->end() )
557         return sSearchKey.getLength();
558     return (*iLook).second;
559 }
560 
561 
562 // TextSearch::NSrchFrwrd is mis-optimized on unxsoli (#i105945#)
NSrchFrwrd(const OUString & searchStr,sal_Int32 startPos,sal_Int32 endPos)563 SearchResult TextSearch::NSrchFrwrd( const OUString& searchStr, sal_Int32 startPos, sal_Int32 endPos )
564 {
565     SearchResult aRet;
566     aRet.subRegExpressions = 0;
567 
568     OUString sSearchKey = bUsePrimarySrchStr ? sSrchStr : sSrchStr2;
569 
570     OUString aStr( searchStr );
571     sal_Int32 nSuchIdx = aStr.getLength();
572     sal_Int32 nEnde = endPos;
573     if( !nSuchIdx || !sSearchKey.getLength() || sSearchKey.getLength() > nSuchIdx )
574         return aRet;
575 
576 
577     if( nEnde < sSearchKey.getLength() )  // position inside the search region ?
578         return aRet;
579 
580     nEnde -= sSearchKey.getLength();
581 
582     if (bUsePrimarySrchStr)
583       MakeForwardTab();                   // create the jumptable
584     else
585       MakeForwardTab2();
586 
587     for (sal_Int32 nCmpIdx = startPos; // start position for the search
588             nCmpIdx <= nEnde;
589             nCmpIdx += GetDiff( aStr[nCmpIdx + sSearchKey.getLength()-1]))
590     {
591         // if the match would be the completed cells, skip it.
592         if ( (checkCTLStart && !isCellStart( aStr, nCmpIdx )) || (checkCTLEnd
593                     && !isCellStart( aStr, nCmpIdx + sSearchKey.getLength())) )
594             continue;
595 
596         nSuchIdx = sSearchKey.getLength() - 1;
597         while( nSuchIdx >= 0 && sSearchKey[nSuchIdx] == aStr[nCmpIdx + nSuchIdx])
598         {
599             if( nSuchIdx == 0 )
600             {
601                 if( SearchFlags::NORM_WORD_ONLY & aSrchPara.searchFlag )
602                 {
603                     sal_Int32 nFndEnd = nCmpIdx + sSearchKey.getLength();
604                     bool bAtStart = !nCmpIdx;
605                     bool bAtEnd = nFndEnd == endPos;
606                     bool bDelimBefore = bAtStart || IsDelimiter( aStr, nCmpIdx-1 );
607                     bool bDelimBehind = IsDelimiter(  aStr, nFndEnd );
608                     //  *       1 -> only one word in the paragraph
609                     //  *       2 -> at begin of paragraph
610                     //  *       3 -> at end of paragraph
611                     //  *       4 -> inside the paragraph
612                     if( !(  ( bAtStart && bAtEnd ) ||           // 1
613                                 ( bAtStart && bDelimBehind ) ||     // 2
614                                 ( bAtEnd && bDelimBefore ) ||       // 3
615                                 ( bDelimBefore && bDelimBehind )))  // 4
616                         break;
617                 }
618 
619                 aRet.subRegExpressions = 1;
620                 aRet.startOffset.realloc( 1 );
621                 aRet.startOffset[ 0 ] = nCmpIdx;
622                 aRet.endOffset.realloc( 1 );
623                 aRet.endOffset[ 0 ] = nCmpIdx + sSearchKey.getLength();
624 
625                 return aRet;
626             }
627             else
628                 nSuchIdx--;
629         }
630     }
631     return aRet;
632 }
633 
NSrchBkwrd(const OUString & searchStr,sal_Int32 startPos,sal_Int32 endPos)634 SearchResult TextSearch::NSrchBkwrd( const OUString& searchStr, sal_Int32 startPos, sal_Int32 endPos )
635 {
636     SearchResult aRet;
637     aRet.subRegExpressions = 0;
638 
639     OUString sSearchKey = bUsePrimarySrchStr ? sSrchStr : sSrchStr2;
640 
641     OUString aStr( searchStr );
642     sal_Int32 nSuchIdx = aStr.getLength();
643     sal_Int32 nEnde = endPos;
644     if( nSuchIdx == 0 || sSearchKey.getLength() == 0 || sSearchKey.getLength() > nSuchIdx)
645         return aRet;
646 
647     if (bUsePrimarySrchStr)
648       MakeBackwardTab();                      // create the jumptable
649     else
650       MakeBackwardTab2();
651 
652     if( nEnde == nSuchIdx )                 // end position for the search
653         nEnde = sSearchKey.getLength();
654     else
655         nEnde += sSearchKey.getLength();
656 
657     sal_Int32 nCmpIdx = startPos;          // start position for the search
658 
659     while (nCmpIdx >= nEnde)
660     {
661         // if the match would be the completed cells, skip it.
662         if ( (!checkCTLStart || isCellStart( aStr, nCmpIdx -
663                         sSearchKey.getLength() )) && (!checkCTLEnd ||
664                     isCellStart( aStr, nCmpIdx)))
665         {
666             nSuchIdx = 0;
667             while( nSuchIdx < sSearchKey.getLength() && sSearchKey[nSuchIdx] ==
668                     aStr[nCmpIdx + nSuchIdx - sSearchKey.getLength()] )
669                 nSuchIdx++;
670             if( nSuchIdx >= sSearchKey.getLength() )
671             {
672                 if( SearchFlags::NORM_WORD_ONLY & aSrchPara.searchFlag )
673                 {
674                     sal_Int32 nFndStt = nCmpIdx - sSearchKey.getLength();
675                     bool bAtStart = !nFndStt;
676                     bool bAtEnd = nCmpIdx == startPos;
677                     bool bDelimBehind = IsDelimiter( aStr, nCmpIdx );
678                     bool bDelimBefore = bAtStart || // begin of paragraph
679                         IsDelimiter( aStr, nFndStt-1 );
680                     //  *       1 -> only one word in the paragraph
681                     //  *       2 -> at begin of paragraph
682                     //  *       3 -> at end of paragraph
683                     //  *       4 -> inside the paragraph
684                     if( ( bAtStart && bAtEnd ) ||           // 1
685                             ( bAtStart && bDelimBehind ) ||     // 2
686                             ( bAtEnd && bDelimBefore ) ||       // 3
687                             ( bDelimBefore && bDelimBehind ))   // 4
688                     {
689                         aRet.subRegExpressions = 1;
690                         aRet.startOffset.realloc( 1 );
691                         aRet.startOffset[ 0 ] = nCmpIdx;
692                         aRet.endOffset.realloc( 1 );
693                         aRet.endOffset[ 0 ] = nCmpIdx - sSearchKey.getLength();
694                         return aRet;
695                     }
696                 }
697                 else
698                 {
699                     aRet.subRegExpressions = 1;
700                     aRet.startOffset.realloc( 1 );
701                     aRet.startOffset[ 0 ] = nCmpIdx;
702                     aRet.endOffset.realloc( 1 );
703                     aRet.endOffset[ 0 ] = nCmpIdx - sSearchKey.getLength();
704                     return aRet;
705                 }
706             }
707         }
708         nSuchIdx = GetDiff( aStr[nCmpIdx - sSearchKey.getLength()] );
709         if( nCmpIdx < nSuchIdx )
710             return aRet;
711         nCmpIdx -= nSuchIdx;
712     }
713     return aRet;
714 }
715 
RESrchPrepare(const::com::sun::star::util::SearchOptions & rOptions)716 void TextSearch::RESrchPrepare( const ::com::sun::star::util::SearchOptions& rOptions)
717 {
718     // select the transliterated pattern string
719     const OUString& rPatternStr =
720         (rOptions.transliterateFlags & REGEX_TRANS_MASK) ? sSrchStr
721         : ((rOptions.transliterateFlags & COMPLEX_TRANS_MASK) ? sSrchStr2 : rOptions.searchString);
722 
723     sal_uInt32 nIcuSearchFlags = UREGEX_UWORD; // request UAX#29 unicode capability
724     // map com::sun::star::util::SearchFlags to ICU uregex.h flags
725     // TODO: REG_EXTENDED, REG_NOT_BEGINOFLINE, REG_NOT_ENDOFLINE
726     // REG_NEWLINE is neither properly defined nor used anywhere => not implemented
727     // REG_NOSUB is not used anywhere => not implemented
728     // NORM_WORD_ONLY is only used for SearchAlgorithm==Absolute
729     // LEV_RELAXED is only used for SearchAlgorithm==Approximate
730     // Note that the search flag ALL_IGNORE_CASE is deprecated in UNO
731     // probably because the transliteration flag IGNORE_CASE handles it as well.
732     if( (rOptions.searchFlag & com::sun::star::util::SearchFlags::ALL_IGNORE_CASE) != 0
733     ||  (rOptions.transliterateFlags & TransliterationModules_IGNORE_CASE) != 0)
734         nIcuSearchFlags |= UREGEX_CASE_INSENSITIVE;
735     UErrorCode nIcuErr = U_ZERO_ERROR;
736     // assumption: transliteration didn't mangle regexp control chars
737     IcuUniString aIcuSearchPatStr( (const UChar*)rPatternStr.getStr(), rPatternStr.getLength());
738 #ifndef DISABLE_WORDBOUND_EMULATION
739     // for convenience specific syntax elements of the old regex engine are emulated
740     // - by replacing \< with "word-break followed by a look-ahead word-char"
741     static const IcuUniString aChevronPatternB( "\\\\<", -1, IcuUniString::kInvariant);
742     static const IcuUniString aChevronReplaceB( "\\\\b(?=\\\\w)", -1, IcuUniString::kInvariant);
743     static RegexMatcher aChevronMatcherB( aChevronPatternB, 0, nIcuErr);
744     aChevronMatcherB.reset( aIcuSearchPatStr);
745     aIcuSearchPatStr = aChevronMatcherB.replaceAll( aChevronReplaceB, nIcuErr);
746     aChevronMatcherB.reset();
747     // - by replacing \> with "look-behind word-char followed by a word-break"
748     static const IcuUniString aChevronPatternE( "\\\\>", -1, IcuUniString::kInvariant);
749     static const IcuUniString aChevronReplaceE( "(?<=\\\\w)\\\\b", -1, IcuUniString::kInvariant);
750     static RegexMatcher aChevronMatcherE( aChevronPatternE, 0, nIcuErr);
751     aChevronMatcherE.reset( aIcuSearchPatStr);
752     aIcuSearchPatStr = aChevronMatcherE.replaceAll( aChevronReplaceE, nIcuErr);
753     aChevronMatcherE.reset();
754 #endif
755     pRegexMatcher = new RegexMatcher( aIcuSearchPatStr, nIcuSearchFlags, nIcuErr);
756     if( nIcuErr)
757         { delete pRegexMatcher; pRegexMatcher = NULL;}
758 }
759 
760 //---------------------------------------------------------------------------
761 
RESrchFrwrd(const OUString & searchStr,sal_Int32 startPos,sal_Int32 endPos)762 SearchResult TextSearch::RESrchFrwrd( const OUString& searchStr,
763                                       sal_Int32 startPos, sal_Int32 endPos )
764 {
765     SearchResult aRet;
766     aRet.subRegExpressions = 0;
767     if( !pRegexMatcher)
768         return aRet;
769 
770     if( endPos > searchStr.getLength())
771         endPos = searchStr.getLength();
772 
773     // use the ICU RegexMatcher to find the matches
774     UErrorCode nIcuErr = U_ZERO_ERROR;
775     const IcuUniString aSearchTargetStr( (const UChar*)searchStr.getStr(), endPos);
776     pRegexMatcher->reset( aSearchTargetStr);
777     // search until there is a valid match
778     for(;;)
779     {
780         if( !pRegexMatcher->find( startPos, nIcuErr))
781             return aRet;
782 
783         // #i118887# ignore zero-length matches e.g. "a*" in "bc"
784         int nStartOfs = pRegexMatcher->start( nIcuErr);
785         int nEndOfs = pRegexMatcher->end( nIcuErr);
786         if( nStartOfs < nEndOfs)
787             break;
788         // try at next position if there was a zero-length match
789         if( ++startPos >= endPos)
790             return aRet;
791     }
792 
793     // extract the result of the search
794     const int nGroupCount = pRegexMatcher->groupCount();
795     aRet.subRegExpressions = nGroupCount + 1;
796     aRet.startOffset.realloc( aRet.subRegExpressions);
797     aRet.endOffset.realloc( aRet.subRegExpressions);
798     aRet.startOffset[0] = pRegexMatcher->start( nIcuErr);
799     aRet.endOffset[0]   = pRegexMatcher->end( nIcuErr);
800     for( int i = 1; i <= nGroupCount; ++i) {
801         aRet.startOffset[i] = pRegexMatcher->start( i, nIcuErr);
802         aRet.endOffset[i]   = pRegexMatcher->end( i, nIcuErr);
803     }
804 
805     return aRet;
806 }
807 
RESrchBkwrd(const OUString & searchStr,sal_Int32 startPos,sal_Int32 endPos)808 SearchResult TextSearch::RESrchBkwrd( const OUString& searchStr,
809                                       sal_Int32 startPos, sal_Int32 endPos )
810 {
811     // NOTE: for backwards search callers provide startPos/endPos inverted!
812     SearchResult aRet;
813     aRet.subRegExpressions = 0;
814     if( !pRegexMatcher)
815         return aRet;
816 
817     if( startPos > searchStr.getLength())
818         startPos = searchStr.getLength();
819 
820     // use the ICU RegexMatcher to find the matches
821     // TODO: use ICU's backward searching once it becomes available
822     //       as its replacement using forward search is not as good as the real thing
823     UErrorCode nIcuErr = U_ZERO_ERROR;
824     const IcuUniString aSearchTargetStr( (const UChar*)searchStr.getStr(), startPos);
825     pRegexMatcher->reset( aSearchTargetStr);
826     if( !pRegexMatcher->find( endPos, nIcuErr))
827         return aRet;
828 
829     // find the last match
830     int nLastPos = 0;
831     int nFoundEnd = 0;
832     do {
833         nLastPos = pRegexMatcher->start( nIcuErr);
834         nFoundEnd = pRegexMatcher->end( nIcuErr);
835         if( nFoundEnd >= startPos)
836             break;
837         if( nFoundEnd == nLastPos)
838             ++nFoundEnd;
839     } while( pRegexMatcher->find( nFoundEnd, nIcuErr));
840 
841     // find last match again to get its details
842     pRegexMatcher->find( nLastPos, nIcuErr);
843 
844     // fill in the details of the last match
845     const int nGroupCount = pRegexMatcher->groupCount();
846     aRet.subRegExpressions = nGroupCount + 1;
847     aRet.startOffset.realloc( aRet.subRegExpressions);
848     aRet.endOffset.realloc( aRet.subRegExpressions);
849     // NOTE: existing users of backward search seem to expect startOfs/endOfs being inverted!
850     aRet.startOffset[0] = pRegexMatcher->end( nIcuErr);
851     aRet.endOffset[0]   = pRegexMatcher->start( nIcuErr);
852     for( int i = 1; i <= nGroupCount; ++i) {
853         aRet.startOffset[i] = pRegexMatcher->end( i, nIcuErr);
854         aRet.endOffset[i]   = pRegexMatcher->start( i, nIcuErr);
855     }
856 
857     return aRet;
858 }
859 
860 //---------------------------------------------------------------------------
861 
862 // search for words phonetically
ApproxSrchFrwrd(const OUString & searchStr,sal_Int32 startPos,sal_Int32 endPos)863 SearchResult TextSearch::ApproxSrchFrwrd( const OUString& searchStr,
864                                           sal_Int32 startPos, sal_Int32 endPos )
865 {
866     SearchResult aRet;
867     aRet.subRegExpressions = 0;
868 
869     if( !xBreak.is() )
870         return aRet;
871 
872     OUString aWTemp( searchStr );
873 
874     sal_Int32 nStt, nEnd;
875 
876     Boundary aWBnd = xBreak->getWordBoundary( aWTemp, startPos,
877             aSrchPara.Locale,
878             WordType::ANYWORD_IGNOREWHITESPACES, sal_True );
879 
880     do
881     {
882         if( aWBnd.startPos >= endPos )
883             break;
884         nStt = aWBnd.startPos < startPos ? startPos : aWBnd.startPos;
885         nEnd = aWBnd.endPos > endPos ? endPos : aWBnd.endPos;
886 
887         if( nStt < nEnd &&
888                 pWLD->WLD( aWTemp.getStr() + nStt, nEnd - nStt ) <= nLimit )
889         {
890             aRet.subRegExpressions = 1;
891             aRet.startOffset.realloc( 1 );
892             aRet.startOffset[ 0 ] = nStt;
893             aRet.endOffset.realloc( 1 );
894             aRet.endOffset[ 0 ] = nEnd;
895             break;
896         }
897 
898         nStt = nEnd - 1;
899         aWBnd = xBreak->nextWord( aWTemp, nStt, aSrchPara.Locale,
900                 WordType::ANYWORD_IGNOREWHITESPACES);
901     } while( aWBnd.startPos != aWBnd.endPos ||
902             (aWBnd.endPos != aWTemp.getLength() && aWBnd.endPos != nEnd) );
903     // #i50244# aWBnd.endPos != nEnd : in case there is _no_ word (only
904     // whitespace) in searchStr, getWordBoundary() returned startPos,startPos
905     // and nextWord() does also => don't loop forever.
906     return aRet;
907 }
908 
ApproxSrchBkwrd(const OUString & searchStr,sal_Int32 startPos,sal_Int32 endPos)909 SearchResult TextSearch::ApproxSrchBkwrd( const OUString& searchStr,
910                                           sal_Int32 startPos, sal_Int32 endPos )
911 {
912     SearchResult aRet;
913     aRet.subRegExpressions = 0;
914 
915     if( !xBreak.is() )
916         return aRet;
917 
918     OUString aWTemp( searchStr );
919 
920     sal_Int32 nStt, nEnd;
921 
922     Boundary aWBnd = xBreak->getWordBoundary( aWTemp, startPos,
923             aSrchPara.Locale,
924             WordType::ANYWORD_IGNOREWHITESPACES, sal_True );
925 
926     do
927     {
928         if( aWBnd.endPos <= endPos )
929             break;
930         nStt = aWBnd.startPos < endPos ? endPos : aWBnd.startPos;
931         nEnd = aWBnd.endPos > startPos ? startPos : aWBnd.endPos;
932 
933         if( nStt < nEnd &&
934                 pWLD->WLD( aWTemp.getStr() + nStt, nEnd - nStt ) <= nLimit )
935         {
936             aRet.subRegExpressions = 1;
937             aRet.startOffset.realloc( 1 );
938             aRet.startOffset[ 0 ] = nEnd;
939             aRet.endOffset.realloc( 1 );
940             aRet.endOffset[ 0 ] = nStt;
941             break;
942         }
943         if( !nStt )
944             break;
945 
946         aWBnd = xBreak->previousWord( aWTemp, nStt, aSrchPara.Locale,
947                 WordType::ANYWORD_IGNOREWHITESPACES);
948     } while( aWBnd.startPos != aWBnd.endPos || aWBnd.endPos != aWTemp.getLength() );
949     return aRet;
950 }
951 
952 
953 static const sal_Char cSearchName[] = "com.sun.star.util.TextSearch";
954 static const sal_Char cSearchImpl[] = "com.sun.star.util.TextSearch_i18n";
955 
getServiceName_Static()956 static OUString getServiceName_Static()
957 {
958     return OUString::createFromAscii( cSearchName );
959 }
960 
getImplementationName_Static()961 static OUString getImplementationName_Static()
962 {
963     return OUString::createFromAscii( cSearchImpl );
964 }
965 
966 OUString SAL_CALL
getImplementationName()967 TextSearch::getImplementationName()
968 {
969     return getImplementationName_Static();
970 }
971 
972 sal_Bool SAL_CALL
supportsService(const OUString & rServiceName)973 TextSearch::supportsService(const OUString& rServiceName)
974 {
975     return !rServiceName.compareToAscii( cSearchName );
976 }
977 
978 Sequence< OUString > SAL_CALL
getSupportedServiceNames(void)979 TextSearch::getSupportedServiceNames(void)
980 {
981     Sequence< OUString > aRet(1);
982     aRet[0] = getServiceName_Static();
983     return aRet;
984 }
985 
986 ::com::sun::star::uno::Reference< ::com::sun::star::uno::XInterface >
TextSearch_CreateInstance(const::com::sun::star::uno::Reference<::com::sun::star::lang::XMultiServiceFactory> & rxMSF)987 SAL_CALL TextSearch_CreateInstance(
988         const ::com::sun::star::uno::Reference<
989         ::com::sun::star::lang::XMultiServiceFactory >& rxMSF )
990 {
991     return ::com::sun::star::uno::Reference<
992         ::com::sun::star::uno::XInterface >(
993                 (::cppu::OWeakObject*) new TextSearch( rxMSF ) );
994 }
995 
996 extern "C"
997 {
998 
component_getImplementationEnvironment(const sal_Char ** ppEnvTypeName,uno_Environment **)999 SAL_DLLPUBLIC_EXPORT void SAL_CALL component_getImplementationEnvironment(
1000         const sal_Char** ppEnvTypeName, uno_Environment** /*ppEnv*/ )
1001 {
1002     *ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
1003 }
1004 
component_getFactory(const sal_Char * sImplementationName,void * _pServiceManager,void *)1005 SAL_DLLPUBLIC_EXPORT void* SAL_CALL component_getFactory( const sal_Char* sImplementationName,
1006         void* _pServiceManager, void* /*_pRegistryKey*/ )
1007 {
1008     void* pRet = NULL;
1009 
1010     ::com::sun::star::lang::XMultiServiceFactory* pServiceManager =
1011         reinterpret_cast< ::com::sun::star::lang::XMultiServiceFactory* >
1012             ( _pServiceManager );
1013     ::com::sun::star::uno::Reference<
1014             ::com::sun::star::lang::XSingleServiceFactory > xFactory;
1015 
1016     if ( 0 == rtl_str_compare( sImplementationName, cSearchImpl) )
1017     {
1018         ::com::sun::star::uno::Sequence< ::rtl::OUString > aServiceNames(1);
1019         aServiceNames[0] = getServiceName_Static();
1020         xFactory = ::cppu::createSingleFactory(
1021                 pServiceManager, getImplementationName_Static(),
1022                 &TextSearch_CreateInstance, aServiceNames );
1023     }
1024 
1025     if ( xFactory.is() )
1026     {
1027         xFactory->acquire();
1028         pRet = xFactory.get();
1029     }
1030 
1031     return pRet;
1032 }
1033 
1034 } // extern "C"
1035