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