xref: /core/sc/source/core/data/table6.cxx (revision 8ef4dc07)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <unotools/textsearch.hxx>
21 #include <com/sun/star/util/SearchResult.hpp>
22 #include <svl/srchitem.hxx>
23 #include <editeng/editobj.hxx>
24 #include <osl/diagnose.h>
25 #include <sal/log.hxx>
26 
27 #include <table.hxx>
28 #include <formulacell.hxx>
29 #include <document.hxx>
30 #include <stlpool.hxx>
31 #include <stlsheet.hxx>
32 #include <markdata.hxx>
33 #include <editutil.hxx>
34 #include <postit.hxx>
35 
36 namespace {
37 
lcl_GetTextWithBreaks(const EditTextObject & rData,ScDocument * pDoc,OUString & rVal)38 void lcl_GetTextWithBreaks( const EditTextObject& rData, ScDocument* pDoc, OUString& rVal )
39 {
40     EditEngine& rEngine = pDoc->GetEditEngine();
41     rEngine.SetText(rData);
42     rVal = rEngine.GetText();
43 }
44 
45 }
46 
SearchCell(const SvxSearchItem & rSearchItem,SCCOL nCol,sc::ColumnBlockConstPosition & rBlockPos,SCROW nRow,const ScMarkData & rMark,OUString & rUndoStr,ScDocument * pUndoDoc)47 bool ScTable::SearchCell(const SvxSearchItem& rSearchItem, SCCOL nCol, sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow,
48                          const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc)
49 {
50     if ( !IsColRowValid( nCol, nRow ) )
51         return false;
52 
53     bool    bFound = false;
54     bool    bDoSearch = true;
55     bool    bDoBack = rSearchItem.GetBackward();
56     bool    bSearchFormatted = rSearchItem.IsSearchFormatted();
57 
58     OUString  aString;
59     ScRefCellValue aCell;
60     if (rSearchItem.GetSelection())
61         bDoSearch = rMark.IsCellMarked(nCol, nRow);
62 
63     if (!bDoSearch)
64         return false;
65 
66     ScPostIt* pNote;
67     if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
68     {
69         pNote = aCol[nCol].GetCellNote(rBlockPos, nRow);
70         if (!pNote)
71             return false;
72     }
73     else
74     {
75         aCell = aCol[nCol].GetCellValue(rBlockPos, nRow);
76         if (aCell.isEmpty())
77             return false;
78         pNote = nullptr;
79     }
80 
81     CellType eCellType = aCell.getType();
82     switch (rSearchItem.GetCellType())
83     {
84         case SvxSearchCellType::FORMULA:
85         {
86             if ( eCellType == CELLTYPE_FORMULA )
87                 aString = aCell.getFormula()->GetFormula(rDocument.GetGrammar());
88             else if ( eCellType == CELLTYPE_EDIT )
89                 lcl_GetTextWithBreaks(*aCell.getEditText(), &rDocument, aString);
90             else
91             {
92                 if( !bSearchFormatted )
93                     aString = aCol[nCol].GetInputString( rBlockPos, nRow );
94                 else
95                     aString = aCol[nCol].GetString( rBlockPos, nRow );
96             }
97             break;
98         }
99         case SvxSearchCellType::VALUE:
100             if ( eCellType == CELLTYPE_EDIT )
101                 lcl_GetTextWithBreaks(*aCell.getEditText(), &rDocument, aString);
102             else
103             {
104                 if( !bSearchFormatted )
105                     aString = aCol[nCol].GetInputString( rBlockPos, nRow );
106                 else
107                     aString = aCol[nCol].GetString( rBlockPos, nRow );
108             }
109             break;
110         case SvxSearchCellType::NOTE:
111         {
112             if (pNote)
113                 aString = pNote->GetText();
114             break;
115         }
116         default:
117             break;
118     }
119     sal_Int32 nStart = 0;
120     sal_Int32 nEnd = aString.getLength();
121     css::util::SearchResult aSearchResult;
122     if (pSearchText)
123     {
124         if ( bDoBack )
125         {
126             sal_Int32 nTemp=nStart; nStart=nEnd; nEnd=nTemp;
127             bFound = pSearchText->SearchBackward(aString, &nStart, &nEnd, &aSearchResult);
128             // change results to definition before 614:
129             --nEnd;
130         }
131         else
132         {
133             bFound = pSearchText->SearchForward(aString, &nStart, &nEnd, &aSearchResult);
134             // change results to definition before 614:
135             --nEnd;
136         }
137 
138         if (bFound && rSearchItem.GetWordOnly())
139             bFound = (nStart == 0 && nEnd == aString.getLength() - 1);
140     }
141     else
142     {
143         OSL_FAIL("pSearchText == NULL");
144         return bFound;
145     }
146 
147     if (!bFound)
148         return false;
149     if ( rSearchItem.GetCommand() != SvxSearchCmd::REPLACE
150          && rSearchItem.GetCommand() != SvxSearchCmd::REPLACE_ALL )
151         return bFound;
152 
153     if (!IsBlockEditable(nCol, nRow, nCol, nRow))
154         return bFound;
155 
156     ScMatrixMode cMatrixFlag = ScMatrixMode::NONE;
157 
158     // Don't split the matrix, only replace Matrix formulas
159     if (eCellType == CELLTYPE_FORMULA)
160     {
161         cMatrixFlag = aCell.getFormula()->GetMatrixFlag();
162         if(cMatrixFlag == ScMatrixMode::Reference)
163             return bFound;
164     }
165     // No UndoDoc => Matrix not restorable => don't replace
166     if (cMatrixFlag != ScMatrixMode::NONE && !pUndoDoc)
167         return bFound;
168 
169     if ( cMatrixFlag == ScMatrixMode::NONE && rSearchItem.GetCommand() == SvxSearchCmd::REPLACE )
170         rUndoStr = aString;
171     else if (pUndoDoc)
172     {
173         ScAddress aAdr( nCol, nRow, nTab );
174         aCell.commit(*pUndoDoc, aAdr);
175     }
176 
177     bool bRepeat = !rSearchItem.GetWordOnly();
178     do
179     {
180         //  don't continue search if the found text is empty,
181         //  otherwise it would never stop (#35410#)
182         if ( nEnd < nStart )
183             bRepeat = false;
184 
185         OUString sReplStr = rSearchItem.GetReplaceString();
186         if (rSearchItem.GetRegExp())
187         {
188             utl::TextSearch::ReplaceBackReferences( sReplStr, aString, aSearchResult );
189             OUStringBuffer aStrBuffer(aString);
190             aStrBuffer.remove(nStart, nEnd-nStart+1);
191             aStrBuffer.insert(nStart, sReplStr);
192             aString = aStrBuffer.makeStringAndClear();
193         }
194         else
195         {
196             OUStringBuffer aStrBuffer(aString);
197             aStrBuffer.remove(nStart, nEnd-nStart+1);
198             aStrBuffer.insert(nStart, rSearchItem.GetReplaceString());
199             aString = aStrBuffer.makeStringAndClear();
200         }
201 
202         //  Adjust index
203         if (bDoBack)
204         {
205             nEnd = nStart;
206             nStart = 0;
207         }
208         else
209         {
210             nStart = nStart + sReplStr.getLength();
211             nEnd = aString.getLength();
212         }
213 
214         //  continue search ?
215         if (bRepeat)
216         {
217             if ( rSearchItem.GetCommand() != SvxSearchCmd::REPLACE_ALL || nStart >= nEnd )
218                 bRepeat = false;
219             else if (bDoBack)
220             {
221                 sal_Int32 nTemp=nStart; nStart=nEnd; nEnd=nTemp;
222                 bRepeat = pSearchText->SearchBackward(aString, &nStart, &nEnd, &aSearchResult);
223                 // change results to definition before 614:
224                 --nEnd;
225             }
226             else
227             {
228                 bRepeat = pSearchText->SearchForward(aString, &nStart, &nEnd, &aSearchResult);
229                 // change results to definition before 614:
230                 --nEnd;
231             }
232         }
233     }
234     while (bRepeat);
235     if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
236     {
237         // NB: rich text format is lost.
238         // This is also true of Cells.
239         if (pNote)
240             pNote->SetText( ScAddress( nCol, nRow, nTab ), aString );
241     }
242     else if ( cMatrixFlag != ScMatrixMode::NONE )
243     {   // don't split Matrix
244         if ( aString.getLength() > 2 )
245         {   // remove {} here so that "{=" can be replaced by "{=..."
246             if ( aString[ aString.getLength()-1 ] == '}' )
247                 aString = aString.copy( 0, aString.getLength()-1 );
248             if ( aString[0] == '{' )
249                 aString = aString.copy( 1 );
250         }
251         ScAddress aAdr( nCol, nRow, nTab );
252         ScFormulaCell* pFCell = new ScFormulaCell( rDocument, aAdr,
253             aString, rDocument.GetGrammar(), cMatrixFlag );
254         SCCOL nMatCols;
255         SCROW nMatRows;
256         aCell.getFormula()->GetMatColsRows(nMatCols, nMatRows);
257         pFCell->SetMatColsRows( nMatCols, nMatRows );
258         aCol[nCol].SetFormulaCell(nRow, pFCell);
259     }
260     else if (eCellType != CELLTYPE_FORMULA && aString.indexOf('\n') != -1)
261     {
262         ScFieldEditEngine& rEngine = rDocument.GetEditEngine();
263         rEngine.SetTextCurrentDefaults(aString);
264         SetEditText(nCol, nRow, rEngine.CreateTextObject());
265     }
266     else
267         aCol[nCol].SetString(nRow, nTab, aString, rDocument.GetAddressConvention());
268     // pCell is invalid now (deleted)
269     aCol[nCol].InitBlockPosition( rBlockPos ); // invalidate also the cached position
270 
271     return bFound;
272 }
273 
SkipFilteredRows(SCROW & rRow,SCROW & rLastNonFilteredRow,bool bForward)274 void ScTable::SkipFilteredRows(SCROW& rRow, SCROW& rLastNonFilteredRow, bool bForward)
275 {
276     if (bForward)
277     {
278         // forward search
279 
280         if (rRow <= rLastNonFilteredRow)
281             return;
282 
283         SCROW nLastRow = rRow;
284         if (RowFiltered(rRow, nullptr, &nLastRow))
285             // move to the first non-filtered row.
286             rRow = nLastRow + 1;
287         else
288             // record the last non-filtered row to avoid checking
289             // the filtered state for each and every row.
290             rLastNonFilteredRow = nLastRow;
291     }
292     else
293     {
294         // backward search
295 
296         if (rRow >= rLastNonFilteredRow)
297             return;
298 
299         SCROW nFirstRow = rRow;
300         if (RowFiltered(rRow, &nFirstRow))
301             // move to the first non-filtered row.
302             rRow = nFirstRow - 1;
303         else
304             // record the last non-filtered row to avoid checking
305             // the filtered state for each and every row.
306             rLastNonFilteredRow = nFirstRow;
307     }
308 }
309 
Search(const SvxSearchItem & rSearchItem,SCCOL & rCol,SCROW & rRow,const ScMarkData & rMark,OUString & rUndoStr,ScDocument * pUndoDoc)310 bool ScTable::Search(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
311                      const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc)
312 {
313     SCCOL nLastCol;
314     SCROW nLastRow;
315     if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
316         GetCellArea( nLastCol, nLastRow);
317     else
318         GetLastDataPos(nLastCol, nLastRow);
319     std::vector< sc::ColumnBlockConstPosition > blockPos;
320     return Search(rSearchItem, rCol, rRow, nLastCol, nLastRow, rMark, rUndoStr, pUndoDoc, blockPos);
321 }
322 
Search(const SvxSearchItem & rSearchItem,SCCOL & rCol,SCROW & rRow,SCCOL nLastCol,SCROW nLastRow,const ScMarkData & rMark,OUString & rUndoStr,ScDocument * pUndoDoc,std::vector<sc::ColumnBlockConstPosition> & blockPos)323 bool ScTable::Search(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
324                      SCCOL nLastCol, SCROW nLastRow,
325                      const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc,
326                      std::vector< sc::ColumnBlockConstPosition >& blockPos)
327 {
328     bool bFound = false;
329     bool bAll =  (rSearchItem.GetCommand() == SvxSearchCmd::FIND_ALL)
330                ||(rSearchItem.GetCommand() == SvxSearchCmd::REPLACE_ALL);
331     SCCOL nCol = rCol;
332     SCROW nRow = rRow;
333 
334     bool bSkipFiltered = !rSearchItem.IsSearchFiltered();
335     bool bSearchNotes = (rSearchItem.GetCellType() == SvxSearchCellType::NOTE);
336     // We need to cache sc::ColumnBlockConstPosition per each column.
337     if (static_cast<SCCOL>(blockPos.size()) != nLastCol + 1)
338     {
339         blockPos.resize( nLastCol + 1 );
340         for( SCCOL i = 0; i <= nLastCol; ++i )
341             aCol[ i ].InitBlockPosition( blockPos[ i ] );
342     }
343     if (!bAll && rSearchItem.GetBackward())
344     {
345         SCROW nLastNonFilteredRow = rDocument.MaxRow() + 1;
346         if (rSearchItem.GetRowDirection())
347         {
348             nCol--;
349             nCol = std::min(nCol, nLastCol);
350             nRow = std::min(nRow, nLastRow);
351             while (!bFound && (nRow >= 0))
352             {
353                 if (bSkipFiltered)
354                     SkipFilteredRows(nRow, nLastNonFilteredRow, false);
355 
356                 while (!bFound && (nCol >= 0))
357                 {
358                     bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ], nRow,
359                                         rMark, rUndoStr, pUndoDoc);
360                     if (!bFound)
361                     {
362                         bool bIsEmpty;
363                         do
364                         {
365                             nCol--;
366                             if (nCol >= 0)
367                             {
368                                 if (bSearchNotes)
369                                     bIsEmpty = !aCol[nCol].HasCellNotes();
370                                 else
371                                     bIsEmpty = aCol[nCol].IsEmptyData();
372                             }
373                             else
374                                 bIsEmpty = true;
375                         }
376                         while ((nCol >= 0) && bIsEmpty);
377                     }
378                 }
379                 if (!bFound)
380                 {
381                     nCol = nLastCol;
382                     nRow--;
383                 }
384             }
385         }
386         else
387         {
388             nRow--;
389             nCol = std::min(nCol, nLastCol);
390             nRow = std::min(nRow, nLastRow);
391             while (!bFound && (nCol >= 0))
392             {
393                 while (!bFound && (nRow >= 0))
394                 {
395                     if (bSkipFiltered)
396                         SkipFilteredRows(nRow, nLastNonFilteredRow, false);
397 
398                     bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ],
399                                         nRow, rMark, rUndoStr, pUndoDoc);
400                     if (!bFound)
401                     {
402                         if (bSearchNotes)
403                         {
404                             /* TODO: can we look for the previous cell note instead? */
405                             --nRow;
406                         }
407                         else
408                         {
409                             if (!aCol[nCol].GetPrevDataPos(nRow))
410                                 nRow = -1;
411                         }
412                     }
413                 }
414                 if (!bFound)
415                 {
416                     // Not found in this column.  Move to the next column.
417                     bool bIsEmpty;
418                     nRow = nLastRow;
419                     nLastNonFilteredRow = rDocument.MaxRow() + 1;
420                     do
421                     {
422                         nCol--;
423                         if (nCol >= 0)
424                         {
425                             if (bSearchNotes)
426                                 bIsEmpty = !aCol[nCol].HasCellNotes();
427                             else
428                                 bIsEmpty = aCol[nCol].IsEmptyData();
429                         }
430                         else
431                             bIsEmpty = true;
432                     }
433                     while ((nCol >= 0) && bIsEmpty);
434                 }
435             }
436         }
437     }
438     else
439     {
440         SCROW nLastNonFilteredRow = -1;
441         if (rSearchItem.GetRowDirection())
442         {
443             nCol++;
444             while (!bFound && (nRow <= nLastRow))
445             {
446                 if (bSkipFiltered)
447                     SkipFilteredRows(nRow, nLastNonFilteredRow, true);
448 
449                 while (!bFound && (nCol <= nLastCol))
450                 {
451                     bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ],
452                                         nRow, rMark, rUndoStr, pUndoDoc);
453                     if (!bFound)
454                     {
455                         nCol++;
456                         while ((nCol <= nLastCol) &&
457                                 (bSearchNotes ? !aCol[nCol].HasCellNotes() : aCol[nCol].IsEmptyData()))
458                             nCol++;
459                     }
460                 }
461                 if (!bFound)
462                 {
463                     nCol = 0;
464                     nRow++;
465                 }
466             }
467         }
468         else
469         {
470             nRow++;
471             while (!bFound && (nCol <= nLastCol))
472             {
473                 while (!bFound && (nRow <= nLastRow))
474                 {
475                     if (bSkipFiltered)
476                         SkipFilteredRows(nRow, nLastNonFilteredRow, true);
477 
478                     // GetSearchAndReplaceStart sets a nCol of -1 for
479                     // ColDirection() only if rSearchItem.GetPattern() is true,
480                     // so a negative column shouldn't be possible here.
481                     assert(nCol >= 0 && "negative nCol for ColDirection");
482 
483                     bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ],
484                                         nRow, rMark, rUndoStr, pUndoDoc);
485                     if (!bFound)
486                     {
487                         if (bSearchNotes)
488                         {
489                             /* TODO: can we look for the next cell note instead? */
490                             ++nRow;
491                         }
492                         else
493                         {
494                             if (!aCol[nCol].GetNextDataPos(nRow))
495                                 nRow = rDocument.MaxRow() + 1;
496                         }
497                     }
498                 }
499                 if (!bFound)
500                 {
501                     // Not found in this column.  Move to the next column.
502                     nRow = 0;
503                     nLastNonFilteredRow = -1;
504                     nCol++;
505                     while ((nCol <= nLastCol) &&
506                             (bSearchNotes ? !aCol[nCol].HasCellNotes() : aCol[nCol].IsEmptyData()))
507                         nCol++;
508                 }
509             }
510         }
511     }
512     if (bFound)
513     {
514         rCol = nCol;
515         rRow = nRow;
516     }
517     return bFound;
518 }
519 
SearchAll(const SvxSearchItem & rSearchItem,const ScMarkData & rMark,ScRangeList & rMatchedRanges,OUString & rUndoStr,ScDocument * pUndoDoc)520 bool ScTable::SearchAll(const SvxSearchItem& rSearchItem, const ScMarkData& rMark,
521                         ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc)
522 {
523     bool bFound = true;
524     SCCOL nCol = 0;
525     SCROW nRow = -1;
526     bool bEverFound = false;
527 
528     SCCOL nLastCol;
529     SCROW nLastRow;
530     if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
531         GetCellArea( nLastCol, nLastRow);
532     else
533         GetLastDataPos(nLastCol, nLastRow);
534 
535     std::vector< sc::ColumnBlockConstPosition > blockPos;
536     do
537     {
538         bFound = Search(rSearchItem, nCol, nRow, nLastCol, nLastRow, rMark, rUndoStr, pUndoDoc, blockPos);
539         if (bFound)
540         {
541             bEverFound = true;
542             rMatchedRanges.Join(ScRange(nCol, nRow, nTab));
543         }
544     }
545     while (bFound);
546 
547     return bEverFound;
548 }
549 
UpdateSearchItemAddressForReplace(const SvxSearchItem & rSearchItem,SCCOL & rCol,SCROW & rRow)550 void ScTable::UpdateSearchItemAddressForReplace( const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow )
551 {
552     if (rSearchItem.GetBackward())
553     {
554         if (rSearchItem.GetRowDirection())
555             rCol += 1;
556         else
557             rRow += 1;
558     }
559     else
560     {
561         if (rSearchItem.GetRowDirection())
562             rCol -= 1;
563         else
564             rRow -= 1;
565     }
566 }
567 
Replace(const SvxSearchItem & rSearchItem,SCCOL & rCol,SCROW & rRow,const ScMarkData & rMark,OUString & rUndoStr,ScDocument * pUndoDoc)568 bool ScTable::Replace(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
569                       const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc)
570 {
571     SCCOL nCol = rCol;
572     SCROW nRow = rRow;
573 
574     UpdateSearchItemAddressForReplace( rSearchItem, nCol, nRow );
575     bool bFound = Search(rSearchItem, nCol, nRow, rMark, rUndoStr, pUndoDoc);
576     if (bFound)
577     {
578         rCol = nCol;
579         rRow = nRow;
580     }
581     return bFound;
582 }
583 
ReplaceAll(const SvxSearchItem & rSearchItem,const ScMarkData & rMark,ScRangeList & rMatchedRanges,OUString & rUndoStr,ScDocument * pUndoDoc,bool & bMatchedRangesWereClamped)584 bool ScTable::ReplaceAll(
585     const SvxSearchItem& rSearchItem, const ScMarkData& rMark, ScRangeList& rMatchedRanges,
586     OUString& rUndoStr, ScDocument* pUndoDoc, bool& bMatchedRangesWereClamped)
587 {
588     SCCOL nCol = 0;
589     SCROW nRow = -1;
590 
591     SCCOL nLastCol;
592     SCROW nLastRow;
593     if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
594         GetCellArea( nLastCol, nLastRow);
595     else
596         GetLastDataPos(nLastCol, nLastRow);
597 
598     // tdf#92160 - columnar replace is faster, and more memory efficient.
599     SvxSearchItem aCopyItem(rSearchItem);
600     aCopyItem.SetRowDirection(false);
601 
602     std::vector< sc::ColumnBlockConstPosition > blockPos;
603     bool bEverFound = false;
604     while (true)
605     {
606         bool bFound = Search(aCopyItem, nCol, nRow, nLastCol, nLastRow, rMark, rUndoStr, pUndoDoc, blockPos);
607 
608         if (bFound)
609         {
610             bEverFound = true;
611             // The combination of this loop and the Join() algorithm is O(n^2),
612             // so just give up if the list gets too big.
613             if (rMatchedRanges.size() < 1000)
614                 rMatchedRanges.Join(ScRange(nCol, nRow, nTab));
615             else
616                 bMatchedRangesWereClamped = true;
617         }
618         else
619             break;
620     }
621     return bEverFound;
622 }
623 
SearchStyle(const SvxSearchItem & rSearchItem,SCCOL & rCol,SCROW & rRow,const ScMarkData & rMark)624 bool ScTable::SearchStyle(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
625                           const ScMarkData& rMark)
626 {
627     const ScStyleSheet* pSearchStyle = static_cast<const ScStyleSheet*>(
628                                         rDocument.GetStyleSheetPool()->Find(
629                                         rSearchItem.GetSearchString(), SfxStyleFamily::Para ));
630 
631     SCCOL nCol = rCol;
632     SCROW nRow = rRow;
633     bool bFound = false;
634 
635     bool bSelect = rSearchItem.GetSelection();
636     bool bRows = rSearchItem.GetRowDirection();
637     bool bBack = rSearchItem.GetBackward();
638     short nAdd = bBack ? -1 : 1;
639 
640     if (bRows)                                      // by row
641     {
642         if ( !IsColValid( nCol ) )
643         {
644             SAL_WARN( "sc.core", "SearchStyle: bad column " << nCol);
645             return false;
646         }
647         nRow += nAdd;
648         do
649         {
650             SCROW nNextRow = aCol[nCol].SearchStyle( nRow, pSearchStyle, bBack, bSelect, rMark );
651             if (!ValidRow(nNextRow))
652             {
653                 nRow = bBack ? rDocument.MaxRow() : 0;
654                 nCol = sal::static_int_cast<SCCOL>( nCol + nAdd );
655             }
656             else
657             {
658                 nRow = nNextRow;
659                 bFound = true;
660             }
661         }
662         while ( !bFound && IsColValid( nCol ) );
663     }
664     else                                    // by column
665     {
666         SCCOL aColSize = aCol.size();
667         std::vector< SCROW > nNextRows ( aColSize );
668         SCCOL i;
669         for (i=0; i < aColSize; ++i)
670         {
671             SCROW nSRow = nRow;
672             if (bBack)
673             {
674                 if (i>=nCol) --nSRow;
675             }
676             else
677             {
678                 if (i<=nCol) ++nSRow;
679             }
680             nNextRows[i] = aCol[i].SearchStyle( nSRow, pSearchStyle, bBack, bSelect, rMark );
681         }
682         if (bBack)                          // backwards
683         {
684             nRow = -1;
685             for (i = aColSize - 1; i>=0; --i)
686                 if (nNextRows[i]>nRow)
687                 {
688                     nCol = i;
689                     nRow = nNextRows[i];
690                     bFound = true;
691                 }
692         }
693         else                                // forwards
694         {
695             nRow = rDocument.MaxRow()+1;
696             for (i=0; i < aColSize; ++i)
697                 if (nNextRows[i]<nRow)
698                 {
699                     nCol = i;
700                     nRow = nNextRows[i];
701                     bFound = true;
702                 }
703         }
704     }
705 
706     if (bFound)
707     {
708         rCol = nCol;
709         rRow = nRow;
710     }
711     return bFound;
712 }
713 
714 //TODO: return single Pattern for Undo
715 
ReplaceStyle(const SvxSearchItem & rSearchItem,SCCOL & rCol,SCROW & rRow,const ScMarkData & rMark,bool bIsUndo)716 bool ScTable::ReplaceStyle(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
717                            const ScMarkData& rMark, bool bIsUndo)
718 {
719     bool bRet;
720     if (bIsUndo)
721         bRet = true;
722     else
723         bRet = SearchStyle(rSearchItem, rCol, rRow, rMark);
724     if (bRet)
725     {
726         const ScStyleSheet* pReplaceStyle = static_cast<const ScStyleSheet*>(
727                                         rDocument.GetStyleSheetPool()->Find(
728                                         rSearchItem.GetReplaceString(), SfxStyleFamily::Para ));
729 
730         if (pReplaceStyle)
731             ApplyStyle( rCol, rRow, pReplaceStyle );
732         else
733         {
734             OSL_FAIL("pReplaceStyle==0");
735         }
736     }
737 
738     return bRet;
739 }
740 
SearchAllStyle(const SvxSearchItem & rSearchItem,const ScMarkData & rMark,ScRangeList & rMatchedRanges)741 bool ScTable::SearchAllStyle(
742     const SvxSearchItem& rSearchItem, const ScMarkData& rMark, ScRangeList& rMatchedRanges)
743 {
744     const ScStyleSheet* pSearchStyle = static_cast<const ScStyleSheet*>(
745                                         rDocument.GetStyleSheetPool()->Find(
746                                         rSearchItem.GetSearchString(), SfxStyleFamily::Para ));
747     bool bSelect = rSearchItem.GetSelection();
748     bool bBack = rSearchItem.GetBackward();
749     bool bEverFound = false;
750 
751     for (SCCOL i=0; i < aCol.size(); ++i)
752     {
753         bool bFound = true;
754         SCROW nRow = 0;
755         SCROW nEndRow;
756         while (bFound && nRow <= rDocument.MaxRow())
757         {
758             bFound = aCol[i].SearchStyleRange( nRow, nEndRow, pSearchStyle, bBack, bSelect, rMark );
759             if (bFound)
760             {
761                 if (nEndRow<nRow)
762                     std::swap( nRow, nEndRow );
763                 rMatchedRanges.Join(ScRange(i, nRow, nTab, i, nEndRow, nTab));
764                 nRow = nEndRow + 1;
765                 bEverFound = true;
766             }
767         }
768     }
769 
770     return bEverFound;
771 }
772 
ReplaceAllStyle(const SvxSearchItem & rSearchItem,const ScMarkData & rMark,ScRangeList & rMatchedRanges,ScDocument * pUndoDoc)773 bool ScTable::ReplaceAllStyle(
774     const SvxSearchItem& rSearchItem, const ScMarkData& rMark, ScRangeList& rMatchedRanges,
775     ScDocument* pUndoDoc)
776 {
777     bool bRet = SearchAllStyle(rSearchItem, rMark, rMatchedRanges);
778     if (bRet)
779     {
780         const ScStyleSheet* pReplaceStyle = static_cast<const ScStyleSheet*>(
781                                         rDocument.GetStyleSheetPool()->Find(
782                                         rSearchItem.GetReplaceString(), SfxStyleFamily::Para ));
783 
784         if (pReplaceStyle)
785         {
786             if (pUndoDoc)
787                 rDocument.CopyToDocument(0, 0 ,nTab, rDocument.MaxCol(),rDocument.MaxRow(),nTab,
788                                           InsertDeleteFlags::ATTRIB, true, *pUndoDoc, &rMark);
789             ApplySelectionStyle( *pReplaceStyle, rMark );
790         }
791         else
792         {
793             OSL_FAIL("pReplaceStyle==0");
794         }
795     }
796 
797     return bRet;
798 }
799 
SearchAndReplace(const SvxSearchItem & rSearchItem,SCCOL & rCol,SCROW & rRow,const ScMarkData & rMark,ScRangeList & rMatchedRanges,OUString & rUndoStr,ScDocument * pUndoDoc,bool & bMatchedRangesWereClamped)800 bool ScTable::SearchAndReplace(
801     const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow, const ScMarkData& rMark,
802     ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc, bool& bMatchedRangesWereClamped)
803 {
804     SvxSearchCmd nCommand = rSearchItem.GetCommand();
805     bool bFound = false;
806     if ( ValidColRow(rCol, rRow) ||
807          ((nCommand == SvxSearchCmd::FIND || nCommand == SvxSearchCmd::REPLACE) &&
808            (((rCol == GetDoc().GetMaxColCount() || rCol == -1) && ValidRow(rRow)) ||
809             ((rRow == GetDoc().GetMaxRowCount() || rRow == -1) && ValidCol(rCol))
810            )
811          )
812        )
813     {
814         bool bStyles = rSearchItem.GetPattern();
815         if (bStyles)
816         {
817             if (nCommand == SvxSearchCmd::FIND)
818                 bFound = SearchStyle(rSearchItem, rCol, rRow, rMark);
819             else if (nCommand == SvxSearchCmd::REPLACE)
820                 bFound = ReplaceStyle(rSearchItem, rCol, rRow, rMark, false);
821             else if (nCommand == SvxSearchCmd::FIND_ALL)
822                 bFound = SearchAllStyle(rSearchItem, rMark, rMatchedRanges);
823             else if (nCommand == SvxSearchCmd::REPLACE_ALL)
824                 bFound = ReplaceAllStyle(rSearchItem, rMark, rMatchedRanges, pUndoDoc);
825         }
826         else if (ScDocument::IsEmptyCellSearch( rSearchItem))
827         {
828             // Search for empty cells.
829             bFound = SearchAndReplaceEmptyCells(rSearchItem, rCol, rRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
830         }
831         else
832         {
833             //  SearchParam no longer needed - SearchOptions contains all settings
834             i18nutil::SearchOptions2 aSearchOptions = rSearchItem.GetSearchOptions();
835             aSearchOptions.Locale = ScGlobal::GetLocale();
836 
837             //  reflect UseAsianOptions flag in SearchOptions
838             //  (use only ignore case and width if asian options are disabled).
839             //  This is also done in SvxSearchDialog CommandHdl, but not in API object.
840             if ( !rSearchItem.IsUseAsianOptions() )
841                 aSearchOptions.transliterateFlags &=
842                     TransliterationFlags::IGNORE_CASE |
843                       TransliterationFlags::IGNORE_WIDTH;
844 
845             pSearchText.reset( new utl::TextSearch( aSearchOptions ) );
846 
847             if (nCommand == SvxSearchCmd::FIND)
848                 bFound = Search(rSearchItem, rCol, rRow, rMark, rUndoStr, pUndoDoc);
849             else if (nCommand == SvxSearchCmd::FIND_ALL)
850                 bFound = SearchAll(rSearchItem, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
851             else if (nCommand == SvxSearchCmd::REPLACE)
852                 bFound = Replace(rSearchItem, rCol, rRow, rMark, rUndoStr, pUndoDoc);
853             else if (nCommand == SvxSearchCmd::REPLACE_ALL)
854                 bFound = ReplaceAll(rSearchItem, rMark, rMatchedRanges, rUndoStr, pUndoDoc, bMatchedRangesWereClamped);
855 
856             pSearchText.reset();
857         }
858     }
859     return bFound;
860 }
861 
SearchAndReplaceEmptyCells(const SvxSearchItem & rSearchItem,SCCOL & rCol,SCROW & rRow,const ScMarkData & rMark,ScRangeList & rMatchedRanges,OUString & rUndoStr,ScDocument * pUndoDoc)862 bool ScTable::SearchAndReplaceEmptyCells(
863     const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow, const ScMarkData& rMark,
864     ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc)
865 {
866     SCCOL nColStart, nColEnd;
867     SCROW nRowStart, nRowEnd;
868     GetFirstDataPos(nColStart, nRowStart);
869     GetLastDataPos(nColEnd, nRowEnd);
870 
871     ScRangeList aRanges(ScRange(nColStart, nRowStart, nTab, nColEnd, nRowEnd, nTab));
872 
873     if (rSearchItem.GetSelection())
874     {
875         // current selection only.
876         if (!rMark.IsMarked() && !rMark.IsMultiMarked())
877             // There is no selection.  Bail out.
878             return false;
879 
880         ScRangeList aMarkedRanges, aNewRanges;
881         rMark.FillRangeListWithMarks(&aMarkedRanges, true);
882         for ( size_t i = 0, n = aMarkedRanges.size(); i < n; ++i )
883         {
884             ScRange & rRange = aMarkedRanges[ i ];
885             if (rRange.aStart.Col() > nColEnd || rRange.aStart.Row() > nRowEnd || rRange.aEnd.Col() < nColStart || rRange.aEnd.Row() < nRowStart)
886                 // This range is outside the data area.  Skip it.
887                 continue;
888 
889             // Shrink the range into data area only.
890             if (rRange.aStart.Col() < nColStart)
891                 rRange.aStart.SetCol(nColStart);
892             if (rRange.aStart.Row() < nRowStart)
893                 rRange.aStart.SetRow(nRowStart);
894 
895             if (rRange.aEnd.Col() > nColEnd)
896                 rRange.aEnd.SetCol(nColEnd);
897             if (rRange.aEnd.Row() > nRowEnd)
898                 rRange.aEnd.SetRow(nRowEnd);
899 
900             aNewRanges.push_back(rRange);
901         }
902         aRanges = aNewRanges;
903     }
904 
905     SvxSearchCmd nCommand = rSearchItem.GetCommand();
906     if (nCommand == SvxSearchCmd::FIND || nCommand == SvxSearchCmd::REPLACE)
907     {
908         if (rSearchItem.GetBackward())
909         {
910             for ( size_t i = aRanges.size(); i > 0; --i )
911             {
912                 const ScRange & rRange = aRanges[ i - 1 ];
913                 if (SearchRangeForEmptyCell(rRange, rSearchItem, rCol, rRow, rUndoStr))
914                     return true;
915             }
916         }
917         else
918         {
919             for ( size_t i = 0, nListSize = aRanges.size(); i < nListSize; ++i )
920             {
921                 const ScRange & rRange = aRanges[ i ];
922                 if (SearchRangeForEmptyCell(rRange, rSearchItem, rCol, rRow, rUndoStr))
923                     return true;
924             }
925         }
926     }
927     else if (nCommand == SvxSearchCmd::FIND_ALL || nCommand == SvxSearchCmd::REPLACE_ALL)
928     {
929         bool bFound = false;
930         for ( size_t i = 0, nListSize = aRanges.size(); i < nListSize; ++i )
931         {
932             ScRange const & rRange = aRanges[ i ];
933             bFound |= SearchRangeForAllEmptyCells(rRange, rSearchItem, rMatchedRanges, rUndoStr, pUndoDoc);
934         }
935         return bFound;
936     }
937     return false;
938 }
939 
940 namespace {
941 
lcl_maybeReplaceCellString(ScColumn & rColObj,SCCOL & rCol,SCROW & rRow,OUString & rUndoStr,SCCOL nCol,SCROW nRow,const SvxSearchItem & rSearchItem)942 bool lcl_maybeReplaceCellString(
943     ScColumn& rColObj, SCCOL& rCol, SCROW& rRow, OUString& rUndoStr, SCCOL nCol, SCROW nRow, const SvxSearchItem& rSearchItem)
944 {
945     ScRefCellValue aCell = rColObj.GetCellValue(nRow);
946     if (aCell.isEmpty())
947     {
948         // empty cell found.
949         rCol = nCol;
950         rRow = nRow;
951         if (rSearchItem.GetCommand() == SvxSearchCmd::REPLACE &&
952             !rSearchItem.GetReplaceString().isEmpty())
953         {
954             rColObj.SetRawString(nRow, rSearchItem.GetReplaceString());
955             rUndoStr.clear();
956         }
957         return true;
958     }
959     return false;
960 }
961 
962 }
963 
SearchRangeForEmptyCell(const ScRange & rRange,const SvxSearchItem & rSearchItem,SCCOL & rCol,SCROW & rRow,OUString & rUndoStr)964 bool ScTable::SearchRangeForEmptyCell(
965     const ScRange& rRange, const SvxSearchItem& rSearchItem,
966     SCCOL& rCol, SCROW& rRow, OUString& rUndoStr)
967 {
968     SvxSearchCmd nCmd = rSearchItem.GetCommand();
969     bool bSkipFiltered = rSearchItem.IsSearchFiltered();
970     if (rSearchItem.GetBackward())
971     {
972         // backward search
973         if (rSearchItem.GetRowDirection())
974         {
975             // row direction.
976             SCROW nLastNonFilteredRow = rDocument.MaxRow() + 1;
977             SCROW nBeginRow = std::min(rRange.aEnd.Row(), rRow);
978             for (SCROW nRow = nBeginRow; nRow >= rRange.aStart.Row(); --nRow)
979             {
980                 if (bSkipFiltered)
981                     SkipFilteredRows(nRow, nLastNonFilteredRow, false);
982                 if (nRow < rRange.aStart.Row())
983                     break;
984 
985                 SCCOL nBeginCol = rRange.aEnd.Col();
986                 if (nRow == rRow && nBeginCol >= rCol)
987                     // always start from one cell before the cursor.
988                     nBeginCol = rCol - (nCmd == SvxSearchCmd::FIND ? 1 : 0);
989 
990                 for (SCCOL nCol = nBeginCol; nCol >= rRange.aStart.Col(); --nCol)
991                 {
992                     if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
993                         return true;
994                 }
995             }
996         }
997         else
998         {
999             // column direction.
1000             SCCOL nBeginCol = std::min(rRange.aEnd.Col(), rCol);
1001             for (SCCOL nCol = nBeginCol; nCol >= rRange.aStart.Col(); --nCol)
1002             {
1003                 SCROW nLastNonFilteredRow = rDocument.MaxRow() + 1;
1004                 SCROW nBeginRow = rRange.aEnd.Row();
1005                 if (nCol == rCol && nBeginRow >= rRow)
1006                     // always start from one cell before the cursor.
1007                     nBeginRow = rRow - (nCmd == SvxSearchCmd::FIND ? 1 : 0);
1008                 for (SCROW nRow = nBeginRow; nRow >= rRange.aStart.Row(); --nRow)
1009                 {
1010                     if (bSkipFiltered)
1011                         SkipFilteredRows(nRow, nLastNonFilteredRow, false);
1012                     if (nRow < rRange.aStart.Row())
1013                         break;
1014 
1015                     if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
1016                         return true;
1017                 }
1018             }
1019         }
1020     }
1021     else
1022     {
1023         // forward search
1024         if (rSearchItem.GetRowDirection())
1025         {
1026             // row direction.
1027             SCROW nLastNonFilteredRow = -1;
1028             SCROW nBeginRow = rRange.aStart.Row() < rRow ? rRow : rRange.aStart.Row();
1029             for (SCROW nRow = nBeginRow; nRow <= rRange.aEnd.Row(); ++nRow)
1030             {
1031                 if (bSkipFiltered)
1032                     SkipFilteredRows(nRow, nLastNonFilteredRow, true);
1033                 if (nRow > rRange.aEnd.Row())
1034                     break;
1035 
1036                 SCCOL nBeginCol = rRange.aStart.Col();
1037                 if (nRow == rRow && nBeginCol <= rCol)
1038                     // always start from one cell past the cursor.
1039                     nBeginCol = rCol + (nCmd == SvxSearchCmd::FIND ? 1 : 0);
1040                 for (SCCOL nCol = nBeginCol; nCol <= rRange.aEnd.Col(); ++nCol)
1041                 {
1042                     if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
1043                         return true;
1044                 }
1045             }
1046         }
1047         else
1048         {
1049             // column direction.
1050             SCCOL nBeginCol = rRange.aStart.Col() < rCol ? rCol : rRange.aStart.Col();
1051             for (SCCOL nCol = nBeginCol; nCol <= rRange.aEnd.Col(); ++nCol)
1052             {
1053                 SCROW nLastNonFilteredRow = -1;
1054                 SCROW nBeginRow = rRange.aStart.Row();
1055                 if (nCol == rCol && nBeginRow <= rRow)
1056                     // always start from one cell past the cursor.
1057                     nBeginRow = rRow + (nCmd == SvxSearchCmd::FIND ? 1 : 0);
1058                 for (SCROW nRow = nBeginRow; nRow <= rRange.aEnd.Row(); ++nRow)
1059                 {
1060                     if (bSkipFiltered)
1061                         SkipFilteredRows(nRow, nLastNonFilteredRow, true);
1062                     if (nRow > rRange.aEnd.Row())
1063                         break;
1064 
1065                     if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
1066                         return true;
1067                 }
1068             }
1069         }
1070     }
1071     return false;
1072 }
1073 
SearchRangeForAllEmptyCells(const ScRange & rRange,const SvxSearchItem & rSearchItem,ScRangeList & rMatchedRanges,OUString & rUndoStr,ScDocument * pUndoDoc)1074 bool ScTable::SearchRangeForAllEmptyCells(
1075     const ScRange& rRange, const SvxSearchItem& rSearchItem,
1076     ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc)
1077 {
1078     bool bFound = false;
1079     bool bReplace = (rSearchItem.GetCommand() == SvxSearchCmd::REPLACE_ALL) &&
1080                     !rSearchItem.GetReplaceString().isEmpty();
1081     bool bSkipFiltered = rSearchItem.IsSearchFiltered();
1082 
1083     for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
1084     {
1085         SCROW nLastNonFilteredRow = -1;
1086         if (aCol[nCol].IsEmptyData())
1087         {
1088             // The entire column is empty.
1089             const SCROW nEndRow = rRange.aEnd.Row();
1090             for (SCROW nRow = rRange.aStart.Row(); nRow <= nEndRow; ++nRow)
1091             {
1092                 SCROW nLastRow;
1093                 const bool bFiltered = RowFiltered(nRow, nullptr, &nLastRow);
1094                 if (nLastRow > nEndRow)
1095                     nLastRow = nEndRow;
1096                 if (!bFiltered)
1097                 {
1098                     rMatchedRanges.Join(ScRange(nCol, nRow, nTab, nCol, nLastRow, nTab));
1099                     if (bReplace)
1100                     {
1101                         const OUString& rNewStr = rSearchItem.GetReplaceString();
1102                         for (SCROW i = nRow; i <= nLastRow; ++i)
1103                         {
1104                             aCol[nCol].SetRawString(i, rNewStr);
1105                             if (pUndoDoc)
1106                             {
1107                                 // TODO: I'm using a string cell with empty content to
1108                                 // trigger deletion of cell instance on undo.  Maybe I
1109                                 // should create a new cell type for this?
1110                                 pUndoDoc->SetString(ScAddress(nCol, i, nTab), OUString());
1111                             }
1112                         }
1113                         rUndoStr.clear();
1114                     }
1115                 }
1116 
1117                 nRow = nLastRow; // move to the last filtered row.
1118             }
1119             bFound = true;
1120             continue;
1121         }
1122 
1123         for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow)
1124         {
1125             if (bSkipFiltered)
1126                 SkipFilteredRows(nRow, nLastNonFilteredRow, true);
1127             if (nRow > rRange.aEnd.Row())
1128                 break;
1129 
1130             ScRefCellValue aCell = aCol[nCol].GetCellValue(nRow);
1131             if (aCell.isEmpty())
1132             {
1133                 // empty cell found
1134                 rMatchedRanges.Join(ScRange(nCol, nRow, nTab));
1135                 bFound = true;
1136 
1137                 if (bReplace)
1138                 {
1139                     aCol[nCol].SetRawString(nRow, rSearchItem.GetReplaceString());
1140                     if (pUndoDoc)
1141                     {
1142                         // TODO: I'm using a string cell with empty content to
1143                         // trigger deletion of cell instance on undo.  Maybe I
1144                         // should create a new cell type for this?
1145                         pUndoDoc->SetString(ScAddress(nCol, nRow, nTab), OUString());
1146                     }
1147                 }
1148             }
1149         }
1150     }
1151     return bFound;
1152 }
1153 
1154 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1155