xref: /core/sc/source/core/data/documen4.cxx (revision 98c74adf)
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 <svl/numformat.hxx>
21 #include <svl/zforlist.hxx>
22 #include <svl/zformat.hxx>
23 #include <formula/token.hxx>
24 #include <sal/log.hxx>
25 #include <comphelper/configuration.hxx>
26 #include <osl/diagnose.h>
27 #include <o3tl/string_view.hxx>
28 
29 #include <document.hxx>
30 #include <docsh.hxx>
31 #include <table.hxx>
32 #include <globstr.hrc>
33 #include <scresid.hxx>
34 #include <subtotal.hxx>
35 #include <docoptio.hxx>
36 #include <markdata.hxx>
37 #include <validat.hxx>
38 #include <scitems.hxx>
39 #include <stlpool.hxx>
40 #include <poolhelp.hxx>
41 #include <detdata.hxx>
42 #include <patattr.hxx>
43 #include <chgtrack.hxx>
44 #include <progress.hxx>
45 #include <paramisc.hxx>
46 #include <compiler.hxx>
47 #include <externalrefmgr.hxx>
48 #include <attrib.hxx>
49 #include <formulacell.hxx>
50 #include <tokenarray.hxx>
51 #include <tokenstringcontext.hxx>
52 #include <memory>
53 
54 using namespace formula;
55 
56 /** (Goal Seek) Find a value of x that is a root of f(x)
57 
58     This function is used internally for the goal seek operation.  It uses the
59     Regula Falsi (aka false position) algorithm to find a root of f(x).  The
60     start value and the target value are to be given by the user in the
61     goal seek dialog.  The f(x) in this case is defined as the formula in the
62     formula cell minus target value.  This function may also perform additional
63     search in the horizontal directions when the f(x) is discrete in order to
64     ensure a non-zero slope necessary for deriving a subsequent x that is
65     reasonably close to the root of interest.
66 
67     @change 24.10.2004 by Kohei Yoshida (kohei@openoffice.org)
68 
69     @see #i28955#
70 
71     @change 6 Aug 2013, fdo37341
72 */
Solver(SCCOL nFCol,SCROW nFRow,SCTAB nFTab,SCCOL nVCol,SCROW nVRow,SCTAB nVTab,const OUString & sValStr,double & nX)73 bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab,
74                         SCCOL nVCol, SCROW nVRow, SCTAB nVTab,
75                         const OUString& sValStr, double& nX)
76 {
77     bool bRet = false;
78     nX = 0.0;
79     ScFormulaCell* pFormula = nullptr;
80     double fTargetVal = 0.0;
81     {
82         CellType eFType = GetCellType(nFCol, nFRow, nFTab);
83         // #i108005# convert target value to number using default format,
84         // as previously done in ScInterpreter::GetDouble
85         sal_uInt32 nFIndex = 0;
86         if ( eFType == CELLTYPE_FORMULA && FetchTable(nVTab) && ValidColRow(nVCol, nVRow) &&
87              GetFormatTable()->IsNumberFormat( sValStr, nFIndex, fTargetVal ) )
88         {
89             ScAddress aFormulaAdr( nFCol, nFRow, nFTab );
90             pFormula = GetFormulaCell( aFormulaAdr );
91         }
92     }
93     if (pFormula)
94     {
95         bool bDoneIteration = false;
96         const ScAddress aValueAdr(nVCol, nVRow, nVTab);
97         const ScRange aVRange(aValueAdr, aValueAdr); // for SetDirty
98 
99         const sal_uInt16 nMaxIter = 100;
100         const double fEps = 1E-10;
101         const double fDelta = 1E-6;
102 
103         double fXPrev = GetValue(aValueAdr);
104         double fBestX = fXPrev;
105 
106         // Original value to be restored later if necessary
107         const ScCellValue aSaveVal(GetRefCellValue(aValueAdr));
108         const bool changeCellType = aSaveVal.getType() != CELLTYPE_VALUE;
109         if (changeCellType)
110             SetValue(aValueAdr, fXPrev);
111         double* pVCell = GetValueCell(aValueAdr);
112 
113         pFormula->Interpret();
114         bool bError = ( pFormula->GetErrCode() != FormulaError::NONE );
115         // bError always corresponds with fF
116 
117         double fFPrev = pFormula->GetValue() - fTargetVal;
118 
119         double fBestF = fabs( fFPrev );
120         if ( fBestF < fDelta )
121             bDoneIteration = true;
122 
123         double fX = fXPrev + fEps;
124         double fF = fFPrev;
125         double fSlope;
126 
127         sal_uInt16 nIter = 0;
128 
129         bool bHorMoveError = false;
130         // Conform Regula Falsi Method
131         while ( !bDoneIteration && ( nIter++ < nMaxIter ) )
132         {
133             *pVCell = fX;
134             SetDirty( aVRange, false );
135             pFormula->Interpret();
136             bError = ( pFormula->GetErrCode() != FormulaError::NONE );
137             fF = pFormula->GetValue() - fTargetVal;
138 
139             if ( fF == fFPrev && !bError )
140             {
141                 // HORIZONTAL SEARCH: Keep moving x in both directions until the f(x)
142                 // becomes different from the previous f(x).  This routine is needed
143                 // when a given function is discrete, in which case the resulting slope
144                 // may become zero which ultimately causes the goal seek operation
145                 // to fail. #i28955#
146 
147                 sal_uInt16 nHorIter = 0;
148                 const double fHorStepAngle = 5.0;
149                 const double fHorMaxAngle = 80.0;
150                 int const nHorMaxIter = static_cast<int>( fHorMaxAngle / fHorStepAngle );
151                 bool bDoneHorMove = false;
152 
153                 while ( !bDoneHorMove && !bHorMoveError && nHorIter++ < nHorMaxIter )
154                 {
155                     double fHorAngle = fHorStepAngle * static_cast<double>( nHorIter );
156                     double fHorTangent = std::tan(basegfx::deg2rad(fHorAngle));
157 
158                     sal_uInt16 nIdx = 0;
159                     while( nIdx++ < 2 && !bDoneHorMove )
160                     {
161                         double fHorX;
162                         if ( nIdx == 1 )
163                             fHorX = fX + fabs( fF ) * fHorTangent;
164                         else
165                             fHorX = fX - fabs( fF ) * fHorTangent;
166 
167                         *pVCell = fHorX;
168                         SetDirty( aVRange, false );
169                         pFormula->Interpret();
170                         bHorMoveError = ( pFormula->GetErrCode() != FormulaError::NONE );
171                         if ( bHorMoveError )
172                             break;
173 
174                         fF = pFormula->GetValue() - fTargetVal;
175                         if ( fF != fFPrev )
176                         {
177                             fX = fHorX;
178                             bDoneHorMove = true;
179                         }
180                     }
181                 }
182                 if ( !bDoneHorMove )
183                     bHorMoveError = true;
184             }
185 
186             if ( bError )
187             {
188                 // move closer to last valid value (fXPrev), keep fXPrev & fFPrev
189                 double fDiff = ( fXPrev - fX ) / 2;
190                 if ( fabs( fDiff ) < fEps )
191                     fDiff = ( fDiff < 0.0 ? - fEps : fEps );
192                 fX += fDiff;
193             }
194             else if ( bHorMoveError )
195                 break;
196             else if ( fabs(fF) < fDelta )
197             {
198                 // converged to root
199                 fBestX = fX;
200                 bDoneIteration = true;
201             }
202             else
203             {
204                 if ( fabs(fF) + fDelta < fBestF )
205                 {
206                     fBestX = fX;
207                     fBestF = fabs( fF );
208                 }
209 
210                 if ( ( fXPrev - fX ) != 0 )
211                 {
212                     fSlope = ( fFPrev - fF ) / ( fXPrev - fX );
213                     if ( fabs( fSlope ) < fEps )
214                         fSlope = fSlope < 0.0 ? -fEps : fEps;
215                 }
216                 else
217                     fSlope = fEps;
218 
219                 fXPrev = fX;
220                 fFPrev = fF;
221                 fX = fX - ( fF / fSlope );
222             }
223         }
224 
225         // Try a nice rounded input value if possible.
226         const double fNiceDelta = ( bDoneIteration && fabs( fBestX ) >= 1e-3 ? 1e-3 : fDelta );
227         nX = ::rtl::math::approxFloor( ( fBestX / fNiceDelta ) + 0.5 ) * fNiceDelta;
228 
229         if ( bDoneIteration )
230         {
231             *pVCell = nX;
232             SetDirty( aVRange, false );
233             pFormula->Interpret();
234             if ( fabs( pFormula->GetValue() - fTargetVal ) > fabs( fF ) )
235                 nX = fBestX;
236             bRet = true;
237         }
238         else if ( bError || bHorMoveError )
239         {
240             nX = fBestX;
241         }
242         if (changeCellType)
243             aSaveVal.commit(*this, aValueAdr);
244         else
245             *pVCell = aSaveVal.getDouble();
246         SetDirty( aVRange, false );
247         pFormula->Interpret();
248     }
249     return bRet;
250 }
251 
InsertMatrixFormula(SCCOL nCol1,SCROW nRow1,SCCOL nCol2,SCROW nRow2,const ScMarkData & rMark,const OUString & rFormula,const ScTokenArray * pArr,const formula::FormulaGrammar::Grammar eGram)252 void ScDocument::InsertMatrixFormula(SCCOL nCol1, SCROW nRow1,
253                                      SCCOL nCol2, SCROW nRow2,
254                                      const ScMarkData& rMark,
255                                      const OUString& rFormula,
256                                      const ScTokenArray* pArr,
257                                      const formula::FormulaGrammar::Grammar eGram )
258 {
259     PutInOrder(nCol1, nCol2);
260     PutInOrder(nRow1, nRow2);
261     nCol2 = std::min<SCCOL>(nCol2, MaxCol());
262     nRow2 = std::min<SCROW>(nRow2, MaxRow());
263     if (!rMark.GetSelectCount())
264     {
265         SAL_WARN("sc", "ScDocument::InsertMatrixFormula: No table marked");
266         return;
267     }
268     if (comphelper::IsFuzzing())
269     {
270         // just too slow
271         if (nCol2 - nCol1 > 64)
272             return;
273         if (nRow2 - nRow1 > 64)
274             return;
275     }
276     assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2));
277 
278     SCTAB nTab1 = *rMark.begin();
279 
280     ScFormulaCell* pCell;
281     ScAddress aPos( nCol1, nRow1, nTab1 );
282     if (pArr)
283         pCell = new ScFormulaCell(*this, aPos, *pArr, eGram, ScMatrixMode::Formula);
284     else
285         pCell = new ScFormulaCell(*this, aPos, rFormula, eGram, ScMatrixMode::Formula);
286     pCell->SetMatColsRows( nCol2 - nCol1 + 1, nRow2 - nRow1 + 1 );
287     SCTAB nMax = GetTableCount();
288     for (const auto& rTab : rMark)
289     {
290         if (rTab >= nMax)
291             break;
292 
293         if (!maTabs[rTab])
294             continue;
295 
296         if (rTab == nTab1)
297         {
298             pCell = maTabs[rTab]->SetFormulaCell(nCol1, nRow1, pCell);
299             if (!pCell) //NULL if nCol1/nRow1 is invalid, which it can't be here
300                 break;
301         }
302         else
303             maTabs[rTab]->SetFormulaCell(
304                 nCol1, nRow1,
305                 new ScFormulaCell(
306                     *pCell, *this, ScAddress(nCol1, nRow1, rTab), ScCloneFlags::StartListening));
307     }
308 
309     ScSingleRefData aRefData;
310     aRefData.InitFlags();
311     aRefData.SetRelCol(0);
312     aRefData.SetRelRow(0);
313     aRefData.SetRelTab(0);  // 2D matrix, always same sheet
314 
315     ScTokenArray aArr(*this); // consists only of one single reference token.
316     formula::FormulaToken* t = aArr.AddMatrixSingleReference(aRefData);
317 
318     for (const SCTAB& nTab : rMark)
319     {
320         if (nTab >= nMax)
321             break;
322 
323         ScTable* pTab = FetchTable(nTab);
324         if (!pTab)
325             continue;
326 
327         for (SCCOL nCol : GetWritableColumnsRange(nTab, nCol1, nCol2))
328         {
329             aRefData.SetRelCol(nCol1 - nCol);
330             for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
331             {
332                 if (nCol == nCol1 && nRow == nRow1)
333                     // Skip the base position.
334                     continue;
335 
336                 // Reference in each cell must point to the origin cell relative to the current cell.
337                 aRefData.SetRelRow(nRow1 - nRow);
338                 *t->GetSingleRef() = aRefData;
339                 // Token array must be cloned so that each formula cell receives its own copy.
340                 ScTokenArray aTokArr(aArr.CloneValue());
341                 aPos = ScAddress(nCol, nRow, nTab);
342                 pCell = new ScFormulaCell(*this, aPos, aTokArr, eGram, ScMatrixMode::Reference);
343                 pTab->SetFormulaCell(nCol, nRow, pCell);
344             }
345         }
346     }
347 }
348 
InsertTableOp(const ScTabOpParam & rParam,SCCOL nCol1,SCROW nRow1,SCCOL nCol2,SCROW nRow2,const ScMarkData & rMark)349 void ScDocument::InsertTableOp(const ScTabOpParam& rParam,  // multiple (repeated?) operation
350                                SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
351                                const ScMarkData& rMark)
352 {
353     PutInOrder(nCol1, nCol2);
354     PutInOrder(nRow1, nRow2);
355     assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2));
356     SCTAB i, nTab1;
357     SCCOL j;
358     SCROW k;
359     i = 0;
360     bool bStop = false;
361     SCTAB nMax = GetTableCount();
362     for (const auto& rTab : rMark)
363     {
364         if (rTab >= nMax)
365             break;
366 
367         if (maTabs[rTab])
368         {
369             i = rTab;
370             bStop = true;
371             break;
372         }
373     }
374     nTab1 = i;
375     if (!bStop)
376     {
377         OSL_FAIL("ScDocument::InsertTableOp: No table marked");
378         return;
379     }
380 
381     ScRefAddress aRef;
382     OUStringBuffer aForString("="
383         + ScCompiler::GetNativeSymbol(ocTableOp)
384         + ScCompiler::GetNativeSymbol( ocOpen));
385 
386     const OUString& sSep = ScCompiler::GetNativeSymbol( ocSep);
387     if (rParam.meMode == ScTabOpParam::Column) // column only
388     {
389         aRef.Set( rParam.aRefFormulaCell.GetAddress(), true, false, false );
390         aForString.append(aRef.GetRefString(*this, nTab1)
391             + sSep
392             + rParam.aRefColCell.GetRefString(*this, nTab1)
393             + sSep);
394         aRef.Set( nCol1, nRow1, nTab1, false, true, true );
395         aForString.append(aRef.GetRefString(*this, nTab1));
396         nCol1++;
397         nCol2 = std::min( nCol2, static_cast<SCCOL>(rParam.aRefFormulaEnd.Col() -
398                     rParam.aRefFormulaCell.Col() + nCol1 + 1));
399     }
400     else if (rParam.meMode == ScTabOpParam::Row) // row only
401     {
402         aRef.Set( rParam.aRefFormulaCell.GetAddress(), false, true, false );
403         aForString.append(aRef.GetRefString(*this, nTab1)
404             + sSep
405             + rParam.aRefRowCell.GetRefString(*this, nTab1)
406             + sSep);
407         aRef.Set( nCol1, nRow1, nTab1, true, false, true );
408         aForString.append(aRef.GetRefString(*this, nTab1));
409         nRow1++;
410         nRow2 = std::min( nRow2, static_cast<SCROW>(rParam.aRefFormulaEnd.Row() -
411                     rParam.aRefFormulaCell.Row() + nRow1 + 1));
412     }
413     else // both
414     {
415         aForString.append(rParam.aRefFormulaCell.GetRefString(*this, nTab1)
416             + sSep
417             + rParam.aRefColCell.GetRefString(*this, nTab1)
418             + sSep);
419         aRef.Set( nCol1, nRow1 + 1, nTab1, false, true, true );
420         aForString.append(aRef.GetRefString(*this, nTab1)
421             + sSep
422             + rParam.aRefRowCell.GetRefString(*this, nTab1)
423             + sSep);
424         aRef.Set( nCol1 + 1, nRow1, nTab1, true, false, true );
425         aForString.append(aRef.GetRefString(*this, nTab1));
426         nCol1++; nRow1++;
427     }
428     aForString.append(ScCompiler::GetNativeSymbol( ocClose ));
429 
430     ScFormulaCell aRefCell( *this, ScAddress( nCol1, nRow1, nTab1 ), aForString.makeStringAndClear(),
431            formula::FormulaGrammar::GRAM_NATIVE, ScMatrixMode::NONE );
432     for( j = nCol1; j <= nCol2; j++ )
433         for( k = nRow1; k <= nRow2; k++ )
434             for (i = 0; i < GetTableCount(); i++)
435             {
436                 for (const auto& rTab : rMark)
437                 {
438                     if (rTab >= nMax)
439                         break;
440                     if( maTabs[rTab] )
441                         maTabs[rTab]->SetFormulaCell(
442                             j, k, new ScFormulaCell(aRefCell, *this, ScAddress(j, k, rTab), ScCloneFlags::StartListening));
443                 }
444             }
445 }
446 
447 namespace {
448 
setCacheTableReferenced(const ScDocument & rDoc,formula::FormulaToken & rToken,ScExternalRefManager & rRefMgr,const ScAddress & rPos)449 bool setCacheTableReferenced(const ScDocument& rDoc, formula::FormulaToken& rToken, ScExternalRefManager& rRefMgr, const ScAddress& rPos)
450 {
451     switch (rToken.GetType())
452     {
453         case svExternalSingleRef:
454             return rRefMgr.setCacheTableReferenced(
455                 rToken.GetIndex(), rToken.GetString().getString(), 1);
456         case svExternalDoubleRef:
457         {
458             const ScComplexRefData& rRef = *rToken.GetDoubleRef();
459             ScRange aAbs = rRef.toAbs(rDoc, rPos);
460             size_t nSheets = aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1;
461             return rRefMgr.setCacheTableReferenced(
462                     rToken.GetIndex(), rToken.GetString().getString(), nSheets);
463         }
464         case svExternalName:
465             /* TODO: external names aren't supported yet, but would
466              * have to be marked as well, if so. Mechanism would be
467              * different. */
468             OSL_FAIL("ScDocument::MarkUsedExternalReferences: implement the svExternalName case!");
469             break;
470         default:
471             break;
472     }
473     return false;
474 }
475 
476 }
477 
MarkUsedExternalReferences(const ScTokenArray & rArr,const ScAddress & rPos)478 bool ScDocument::MarkUsedExternalReferences( const ScTokenArray& rArr, const ScAddress& rPos )
479 {
480     if (!rArr.GetLen())
481         return false;
482 
483     ScExternalRefManager* pRefMgr = nullptr;
484     formula::FormulaTokenArrayPlainIterator aIter( rArr );
485     bool bAllMarked = false;
486     while (!bAllMarked)
487     {
488         formula::FormulaToken* t = aIter.GetNextReferenceOrName();
489         if (!t)
490             break;
491         if (t->IsExternalRef())
492         {
493             if (!pRefMgr)
494                 pRefMgr = GetExternalRefManager();
495 
496             bAllMarked = setCacheTableReferenced(*this, *t, *pRefMgr, rPos);
497         }
498         else if (t->GetType() == svIndex)
499         {
500             // this is a named range.  Check if the range contains an external
501             // reference.
502             ScRangeData* pRangeData = GetRangeName()->findByIndex(t->GetIndex());
503             if (!pRangeData)
504                 continue;
505 
506             ScTokenArray* pArray = pRangeData->GetCode();
507             formula::FormulaTokenArrayPlainIterator aArrayIter(*pArray);
508             for (t = aArrayIter.First(); t; t = aArrayIter.Next())
509             {
510                 if (!t->IsExternalRef())
511                     continue;
512 
513                 if (!pRefMgr)
514                     pRefMgr = GetExternalRefManager();
515 
516                 bAllMarked = setCacheTableReferenced(*this, *t, *pRefMgr, rPos);
517             }
518         }
519     }
520     return bAllMarked;
521 }
522 
GetNextSpellingCell(SCCOL & nCol,SCROW & nRow,SCTAB nTab,bool bInSel,const ScMarkData & rMark) const523 bool ScDocument::GetNextSpellingCell(SCCOL& nCol, SCROW& nRow, SCTAB nTab,
524                         bool bInSel, const ScMarkData& rMark) const
525 {
526     if (const ScTable* pTable = FetchTable(nTab))
527         return pTable->GetNextSpellingCell( nCol, nRow, bInSel, rMark );
528     return false;
529 }
530 
GetNextMarkedCell(SCCOL & rCol,SCROW & rRow,SCTAB nTab,const ScMarkData & rMark)531 bool ScDocument::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, SCTAB nTab,
532                                         const ScMarkData& rMark )
533 {
534     if (ScTable* pTable = FetchTable(nTab))
535         return pTable->GetNextMarkedCell( rCol, rRow, rMark );
536     return false;
537 }
538 
ReplaceStyle(const SvxSearchItem & rSearchItem,SCCOL nCol,SCROW nRow,SCTAB nTab,const ScMarkData & rMark)539 void ScDocument::ReplaceStyle(const SvxSearchItem& rSearchItem,
540                               SCCOL nCol, SCROW nRow, SCTAB nTab,
541                               const ScMarkData& rMark)
542 {
543     if (ScTable* pTable = FetchTable(nTab))
544         pTable->ReplaceStyle(rSearchItem, nCol, nRow, rMark, true/*bIsUndoP*/);
545 }
546 
CompileDBFormula()547 void ScDocument::CompileDBFormula()
548 {
549     sc::CompileFormulaContext aCxt(*this);
550     for (auto& rxTab : maTabs)
551     {
552         if (rxTab)
553             rxTab->CompileDBFormula(aCxt);
554     }
555 }
556 
CompileColRowNameFormula()557 void ScDocument::CompileColRowNameFormula()
558 {
559     sc::CompileFormulaContext aCxt(*this);
560     for (auto& rxTab : maTabs)
561     {
562         if (rxTab)
563             rxTab->CompileColRowNameFormula(aCxt);
564     }
565 }
566 
InvalidateTableArea()567 void ScDocument::InvalidateTableArea()
568 {
569     for (auto& rxTab : maTabs)
570     {
571         if (!rxTab)
572             break;
573         rxTab->InvalidateTableArea();
574         if ( rxTab->IsScenario() )
575             rxTab->InvalidateScenarioRanges();
576     }
577 }
578 
GetMaxStringLen(SCTAB nTab,SCCOL nCol,SCROW nRowStart,SCROW nRowEnd,rtl_TextEncoding eCharSet) const579 sal_Int32 ScDocument::GetMaxStringLen( SCTAB nTab, SCCOL nCol,
580         SCROW nRowStart, SCROW nRowEnd, rtl_TextEncoding eCharSet ) const
581 {
582     if (const ScTable* pTable = FetchTable(nTab))
583         return pTable->GetMaxStringLen(nCol, nRowStart, nRowEnd, eCharSet);
584     return 0;
585 }
586 
GetMaxNumberStringLen(sal_uInt16 & nPrecision,SCTAB nTab,SCCOL nCol,SCROW nRowStart,SCROW nRowEnd) const587 sal_Int32 ScDocument::GetMaxNumberStringLen( sal_uInt16& nPrecision, SCTAB nTab,
588                                     SCCOL nCol, SCROW nRowStart, SCROW nRowEnd ) const
589 {
590     if (const ScTable* pTable = FetchTable(nTab))
591         return pTable->GetMaxNumberStringLen(nPrecision, nCol, nRowStart, nRowEnd);
592     return 0;
593 }
594 
GetSelectionFunction(ScSubTotalFunc eFunc,const ScAddress & rCursor,const ScMarkData & rMark,double & rResult)595 bool ScDocument::GetSelectionFunction( ScSubTotalFunc eFunc,
596                                         const ScAddress& rCursor, const ScMarkData& rMark,
597                                         double& rResult )
598 {
599     ScFunctionData aData(eFunc);
600 
601     ScMarkData aMark(rMark);
602     aMark.MarkToMulti();
603     if (!aMark.IsMultiMarked() && !aMark.IsCellMarked(rCursor.Col(), rCursor.Row()))
604         aMark.SetMarkArea(rCursor);
605 
606     SCTAB nMax = GetTableCount();
607     ScMarkData::const_iterator itr = aMark.begin(), itrEnd = aMark.end();
608 
609     for (; itr != itrEnd && *itr < nMax && !aData.getError(); ++itr)
610         if (maTabs[*itr])
611             maTabs[*itr]->UpdateSelectionFunction(aData, aMark);
612 
613     rResult = aData.getResult();
614     if (aData.getError())
615         rResult = 0.0;
616 
617     return !aData.getError();
618 }
619 
RoundValueAsShown(double fVal,sal_uInt32 nFormat,const ScInterpreterContext * pContext) const620 double ScDocument::RoundValueAsShown( double fVal, sal_uInt32 nFormat, const ScInterpreterContext* pContext ) const
621 {
622     const SvNumberFormatter* pFormatter = pContext ? pContext->GetFormatTable() : GetFormatTable();
623     const SvNumberformat* pFormat = pFormatter->GetEntry( nFormat );
624     if (!pFormat)
625         return fVal;
626     SvNumFormatType nType = pFormat->GetMaskedType();
627     if (nType != SvNumFormatType::DATE && nType != SvNumFormatType::TIME && nType != SvNumFormatType::DATETIME )
628     {
629         // MSVC doesn't recognize all paths init nPrecision and wails about
630         // "potentially uninitialized local variable 'nPrecision' used"
631         // so init to some random sensible value preserving all decimals.
632         short nPrecision = 20;
633         bool bStdPrecision = ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0);
634         if (!bStdPrecision)
635         {
636             sal_uInt16 nIdx = pFormat->GetSubformatIndex( fVal );
637             nPrecision = static_cast<short>(pFormat->GetFormatPrecision( nIdx ));
638             switch ( nType )
639             {
640                 case SvNumFormatType::PERCENT:      // 0.41% == 0.0041
641                     nPrecision += 2;
642                     break;
643                 case SvNumFormatType::SCIENTIFIC:   // 1.23e-3 == 0.00123
644                 {
645                     short nExp = 0;
646                     if ( fVal > 0.0 )
647                         nExp = static_cast<short>(floor( log10( fVal ) ));
648                     else if ( fVal < 0.0 )
649                         nExp = static_cast<short>(floor( log10( -fVal ) ));
650                     nPrecision -= nExp;
651                     short nInteger = static_cast<short>(pFormat->GetFormatIntegerDigits( nIdx ));
652                     if ( nInteger > 1 ) // Engineering notation
653                     {
654                         short nIncrement = nExp % nInteger;
655                         if ( nIncrement != 0 )
656                         {
657                             nPrecision += nIncrement;
658                             if (nExp < 0 )
659                                 nPrecision += nInteger;
660                         }
661                     }
662                     break;
663                 }
664                 case SvNumFormatType::FRACTION:     // get value of fraction representation
665                 {
666                     return pFormat->GetRoundFractionValue( fVal );
667                 }
668                 case SvNumFormatType::NUMBER:
669                 case SvNumFormatType::CURRENCY:
670                 {   // tdf#106253 Thousands divisors for format "0,"
671                     const sal_uInt16 nTD = pFormat->GetThousandDivisorPrecision( nIdx );
672                     if (nTD == SvNumberFormatter::UNLIMITED_PRECISION)
673                         // Format contains General keyword, handled below.
674                         bStdPrecision = true;
675                     else
676                         nPrecision -= nTD;
677                     break;
678                 }
679                 default: break;
680             }
681         }
682         if (bStdPrecision)
683         {
684             nPrecision = static_cast<short>(GetDocOptions().GetStdPrecision());
685             // #i115512# no rounding for automatic decimals
686             if (nPrecision == static_cast<short>(SvNumberFormatter::UNLIMITED_PRECISION))
687                 return fVal;
688         }
689         double fRound = ::rtl::math::round( fVal, nPrecision );
690         if ( ::rtl::math::approxEqual( fVal, fRound ) )
691             return fVal;        // rounding might introduce some error
692         else
693             return fRound;
694     }
695     else
696         return fVal;
697 }
698 
699 // conditional formats and validation ranges
700 
AddCondFormat(std::unique_ptr<ScConditionalFormat> pNew,SCTAB nTab)701 sal_uInt32 ScDocument::AddCondFormat( std::unique_ptr<ScConditionalFormat> pNew, SCTAB nTab )
702 {
703     if(!pNew)
704         return 0;
705 
706     if (ScTable* pTable = FetchTable(nTab))
707         return pTable->AddCondFormat(std::move(pNew));
708 
709     return 0;
710 }
711 
AddValidationEntry(const ScValidationData & rNew)712 sal_uInt32 ScDocument::AddValidationEntry( const ScValidationData& rNew )
713 {
714     if (rNew.IsEmpty())
715         return 0;                   // empty is always 0
716 
717     if (!pValidationList)
718     {
719         ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
720         pValidationList.reset(new ScValidationDataList);
721     }
722 
723     sal_uInt32 nMax = 0;
724     for( const auto& rxData : *pValidationList )
725     {
726         const ScValidationData* pData = rxData.get();
727         sal_uInt32 nKey = pData->GetKey();
728         if ( pData->EqualEntries( rNew ) )
729             return nKey;
730         if ( nKey > nMax )
731             nMax = nKey;
732     }
733 
734     // might be called from ScPatternAttr::MigrateToDocument; thus clone (real copy)
735     sal_uInt32 nNewKey = nMax + 1;
736     std::unique_ptr<ScValidationData> pInsert(rNew.Clone(this));
737     pInsert->SetKey( nNewKey );
738     ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
739     pValidationList->InsertNew( std::move(pInsert) );
740     return nNewKey;
741 }
742 
GetEffItem(SCCOL nCol,SCROW nRow,SCTAB nTab,sal_uInt16 nWhich) const743 const SfxPoolItem* ScDocument::GetEffItem(
744                         SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const
745 {
746     const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
747     if ( pPattern )
748     {
749         const SfxItemSet& rSet = pPattern->GetItemSet();
750         if ( rSet.GetItemState( ATTR_CONDITIONAL ) == SfxItemState::SET )
751         {
752             const ScCondFormatIndexes& rIndex = pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData();
753             ScConditionalFormatList* pCondFormList = GetCondFormList( nTab );
754             if (!rIndex.empty() && pCondFormList)
755             {
756                 for(const auto& rItem : rIndex)
757                 {
758                     const ScConditionalFormat* pForm = pCondFormList->GetFormat( rItem );
759                     if ( pForm )
760                     {
761                         ScAddress aPos(nCol, nRow, nTab);
762                         ScRefCellValue aCell(const_cast<ScDocument&>(*this), aPos);
763                         const OUString& aStyle = pForm->GetCellStyle(aCell, aPos);
764                         if (!aStyle.isEmpty())
765                         {
766                             SfxStyleSheetBase* pStyleSheet = mxPoolHelper->GetStylePool()->Find(
767                                     aStyle, SfxStyleFamily::Para );
768                             const SfxPoolItem* pItem = nullptr;
769                             if ( pStyleSheet && pStyleSheet->GetItemSet().GetItemState(
770                                         nWhich, true, &pItem ) == SfxItemState::SET )
771                                 return pItem;
772                         }
773                     }
774                 }
775             }
776         }
777         return &rSet.Get( nWhich );
778     }
779     OSL_FAIL("no pattern");
780     return nullptr;
781 }
782 
GetCondResult(SCCOL nCol,SCROW nRow,SCTAB nTab,ScRefCellValue * pCell) const783 const SfxItemSet* ScDocument::GetCondResult( SCCOL nCol, SCROW nRow, SCTAB nTab, ScRefCellValue* pCell ) const
784 {
785     ScConditionalFormatList* pFormatList = GetCondFormList(nTab);
786     if (!pFormatList)
787         return nullptr;
788 
789     ScAddress aPos(nCol, nRow, nTab);
790     ScRefCellValue aCell;
791     if( pCell == nullptr )
792     {
793         aCell.assign(const_cast<ScDocument&>(*this), aPos);
794         pCell = &aCell;
795     }
796     const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
797     const ScCondFormatIndexes& rIndex =
798         pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData();
799 
800     return GetCondResult(*pCell, aPos, *pFormatList, rIndex);
801 }
802 
GetCondResult(ScRefCellValue & rCell,const ScAddress & rPos,const ScConditionalFormatList & rList,const ScCondFormatIndexes & rIndex) const803 const SfxItemSet* ScDocument::GetCondResult(
804     ScRefCellValue& rCell, const ScAddress& rPos, const ScConditionalFormatList& rList,
805     const ScCondFormatIndexes& rIndex ) const
806 {
807     for (const auto& rItem : rIndex)
808     {
809         const ScConditionalFormat* pForm = rList.GetFormat(rItem);
810         if (!pForm)
811             continue;
812 
813         const OUString& aStyle = pForm->GetCellStyle(rCell, rPos);
814         if (!aStyle.isEmpty())
815         {
816             SfxStyleSheetBase* pStyleSheet =
817                 mxPoolHelper->GetStylePool()->Find(aStyle, SfxStyleFamily::Para);
818 
819             if (pStyleSheet)
820                 return &pStyleSheet->GetItemSet();
821 
822             // if style is not there, treat like no condition
823         }
824     }
825 
826     return nullptr;
827 }
828 
GetCondFormat(SCCOL nCol,SCROW nRow,SCTAB nTab) const829 ScConditionalFormat* ScDocument::GetCondFormat(
830                             SCCOL nCol, SCROW nRow, SCTAB nTab ) const
831 {
832     sal_uInt32 nIndex = 0;
833     const ScCondFormatIndexes& rCondFormats = GetAttr(nCol, nRow, nTab, ATTR_CONDITIONAL)->GetCondFormatData();
834 
835     if(!rCondFormats.empty())
836         nIndex = rCondFormats[0];
837 
838     if (nIndex)
839     {
840         ScConditionalFormatList* pCondFormList = GetCondFormList(nTab);
841         if (pCondFormList)
842             return pCondFormList->GetFormat( nIndex );
843         else
844         {
845             OSL_FAIL("pCondFormList is 0");
846         }
847     }
848 
849     return nullptr;
850 }
851 
GetCondFormList(SCTAB nTab) const852 ScConditionalFormatList* ScDocument::GetCondFormList(SCTAB nTab) const
853 {
854     if (HasTable(nTab))
855         return maTabs[nTab]->GetCondFormList();
856     return nullptr;
857 }
858 
SetCondFormList(ScConditionalFormatList * pList,SCTAB nTab)859 void ScDocument::SetCondFormList( ScConditionalFormatList* pList, SCTAB nTab )
860 {
861     if (ScTable* pTable = FetchTable(nTab))
862         pTable->SetCondFormList(pList);
863 }
864 
GetValidationEntry(sal_uInt32 nIndex) const865 const ScValidationData* ScDocument::GetValidationEntry( sal_uInt32 nIndex ) const
866 {
867     if ( pValidationList )
868         return pValidationList->GetData( nIndex );
869     else
870         return nullptr;
871 }
872 
DeleteConditionalFormat(sal_uLong nOldIndex,SCTAB nTab)873 void ScDocument::DeleteConditionalFormat(sal_uLong nOldIndex, SCTAB nTab)
874 {
875     if (ScTable* pTable = FetchTable(nTab))
876         pTable->DeleteConditionalFormat(nOldIndex);
877 }
878 
HasDetectiveOperations() const879 bool ScDocument::HasDetectiveOperations() const
880 {
881     return pDetOpList && pDetOpList->Count();
882 }
883 
AddDetectiveOperation(const ScDetOpData & rData)884 void ScDocument::AddDetectiveOperation( const ScDetOpData& rData )
885 {
886     if (!pDetOpList)
887         pDetOpList.reset(new ScDetOpList);
888 
889     pDetOpList->Append( rData );
890 }
891 
ClearDetectiveOperations()892 void ScDocument::ClearDetectiveOperations()
893 {
894     pDetOpList.reset();      // deletes also the entries
895 }
896 
SetDetOpList(std::unique_ptr<ScDetOpList> pNew)897 void ScDocument::SetDetOpList(std::unique_ptr<ScDetOpList> pNew)
898 {
899     pDetOpList = std::move(pNew);
900 }
901 
902 // Comparison of Documents
903 
904 //  Pfriemel-Factors
905 #define SC_DOCCOMP_MAXDIFF  256
906 #define SC_DOCCOMP_MINGOOD  128
907 #define SC_DOCCOMP_COLUMNS  10
908 #define SC_DOCCOMP_ROWS     100
909 
RowDifferences(SCROW nThisRow,SCTAB nThisTab,ScDocument & rOtherDoc,SCROW nOtherRow,SCTAB nOtherTab,SCCOL nMaxCol,const SCCOLROW * pOtherCols)910 sal_uInt16 ScDocument::RowDifferences( SCROW nThisRow, SCTAB nThisTab,
911                                     ScDocument& rOtherDoc, SCROW nOtherRow, SCTAB nOtherTab,
912                                     SCCOL nMaxCol, const SCCOLROW* pOtherCols )
913 {
914     sal_uLong nDif = 0;
915     sal_uLong nUsed = 0;
916     for (SCCOL nThisCol=0; nThisCol<=nMaxCol; nThisCol++)
917     {
918         SCCOL nOtherCol;
919         if ( pOtherCols )
920             nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
921         else
922             nOtherCol = nThisCol;
923 
924         if (ValidCol(nOtherCol))    // only compare columns that are common to both docs
925         {
926             ScRefCellValue aThisCell(*this, ScAddress(nThisCol, nThisRow, nThisTab));
927             ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab));
928             if (!aThisCell.equalsWithoutFormat(aOtherCell))
929             {
930                 if (!aThisCell.isEmpty() && !aOtherCell.isEmpty())
931                     nDif += 3;
932                 else
933                     nDif += 4;      // content <-> empty counts more
934             }
935 
936             if (!aThisCell.isEmpty() || !aOtherCell.isEmpty())
937                 ++nUsed;
938         }
939     }
940 
941     if (nUsed > 0)
942         return static_cast<sal_uInt16>((nDif*64)/nUsed);            // max.256 (SC_DOCCOMP_MAXDIFF)
943 
944     OSL_ENSURE(!nDif,"Diff without Used");
945     return 0;
946 }
947 
ColDifferences(SCCOL nThisCol,SCTAB nThisTab,ScDocument & rOtherDoc,SCCOL nOtherCol,SCTAB nOtherTab,SCROW nMaxRow,const SCCOLROW * pOtherRows)948 sal_uInt16 ScDocument::ColDifferences( SCCOL nThisCol, SCTAB nThisTab,
949                                     ScDocument& rOtherDoc, SCCOL nOtherCol, SCTAB nOtherTab,
950                                     SCROW nMaxRow, const SCCOLROW* pOtherRows )
951 {
952 
953     //TODO: optimize e.g. with iterator?
954 
955     sal_uInt64 nDif = 0;
956     sal_uInt64 nUsed = 0;
957     for (SCROW nThisRow=0; nThisRow<=nMaxRow; nThisRow++)
958     {
959         SCROW nOtherRow;
960         if ( pOtherRows )
961             nOtherRow = pOtherRows[nThisRow];
962         else
963             nOtherRow = nThisRow;
964 
965         if (ValidRow(nOtherRow))    // only compare rows that are common to both docs
966         {
967             ScRefCellValue aThisCell(*this, ScAddress(nThisCol, nThisRow, nThisTab));
968             ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab));
969             if (!aThisCell.equalsWithoutFormat(aOtherCell))
970             {
971                 if (!aThisCell.isEmpty() && !aOtherCell.isEmpty())
972                     nDif += 3;
973                 else
974                     nDif += 4;      // content <-> empty counts more
975             }
976 
977             if (!aThisCell.isEmpty() || !aOtherCell.isEmpty())
978                 ++nUsed;
979         }
980     }
981 
982     if (nUsed > 0)
983         return static_cast<sal_uInt16>((nDif*64)/nUsed);    // max.256
984 
985     OSL_ENSURE(!nDif,"Diff without Used");
986     return 0;
987 }
988 
FindOrder(SCCOLROW * pOtherRows,SCCOLROW nThisEndRow,SCCOLROW nOtherEndRow,bool bColumns,ScDocument & rOtherDoc,SCTAB nThisTab,SCTAB nOtherTab,SCCOLROW nEndCol,const SCCOLROW * pTranslate,ScProgress * pProgress,sal_uInt64 nProAdd)989 void ScDocument::FindOrder( SCCOLROW* pOtherRows, SCCOLROW nThisEndRow, SCCOLROW nOtherEndRow,
990                             bool bColumns, ScDocument& rOtherDoc, SCTAB nThisTab, SCTAB nOtherTab,
991                             SCCOLROW nEndCol, const SCCOLROW* pTranslate, ScProgress* pProgress, sal_uInt64 nProAdd )
992 {
993     //  bColumns=true: rows are columns and vice versa
994 
995     SCCOLROW nMaxCont;                      // continue by how much
996     SCCOLROW nMinGood;                      // what is a hit (incl.)
997     if ( bColumns )
998     {
999         nMaxCont = SC_DOCCOMP_COLUMNS;      // 10 columns
1000         nMinGood = SC_DOCCOMP_MINGOOD;
1001 
1002         //TODO: additional pass with nMinGood = 0 ????
1003 
1004     }
1005     else
1006     {
1007         nMaxCont = SC_DOCCOMP_ROWS;         // 100 rows
1008         nMinGood = SC_DOCCOMP_MINGOOD;
1009     }
1010     bool bUseTotal = bColumns && !pTranslate;       // only for the 1st pass
1011 
1012     SCCOLROW nOtherRow = 0;
1013     sal_uInt16 nComp;
1014     SCCOLROW nThisRow;
1015     bool bTotal = false;        // hold for several nThisRow
1016     SCCOLROW nUnknown = 0;
1017     for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
1018     {
1019         SCCOLROW nTempOther = nOtherRow;
1020         bool bFound = false;
1021         sal_uInt16 nBest = SC_DOCCOMP_MAXDIFF;
1022         SCCOLROW nMax = std::min( nOtherEndRow, static_cast<SCCOLROW>(( nTempOther + nMaxCont + nUnknown )) );
1023         for (SCCOLROW i=nTempOther; i<=nMax && nBest>0; i++)    // stop at 0
1024         {
1025             if (bColumns)
1026                 nComp = ColDifferences( static_cast<SCCOL>(nThisRow), nThisTab, rOtherDoc, static_cast<SCCOL>(i), nOtherTab, nEndCol, pTranslate );
1027             else
1028                 nComp = RowDifferences( nThisRow, nThisTab, rOtherDoc, i, nOtherTab, static_cast<SCCOL>(nEndCol), pTranslate );
1029             if ( nComp < nBest && ( nComp <= nMinGood || bTotal ) )
1030             {
1031                 nTempOther = i;
1032                 nBest = nComp;
1033                 bFound = true;
1034             }
1035             if ( nComp < SC_DOCCOMP_MAXDIFF || bFound )
1036                 bTotal = false;
1037             else if ( i == nTempOther && bUseTotal )
1038                 bTotal = true;                          // only at the very top
1039         }
1040         if ( bFound )
1041         {
1042             pOtherRows[nThisRow] = nTempOther;
1043             nOtherRow = nTempOther + 1;
1044             nUnknown = 0;
1045         }
1046         else
1047         {
1048             pOtherRows[nThisRow] = SCROW_MAX;
1049             ++nUnknown;
1050         }
1051 
1052         if (pProgress)
1053             pProgress->SetStateOnPercent(nProAdd+static_cast<sal_uLong>(nThisRow));
1054     }
1055 
1056     // fill in blocks that don't match
1057 
1058     SCROW nFillStart = 0;
1059     SCROW nFillPos = 0;
1060     bool bInFill = false;
1061     for (nThisRow = 0; nThisRow <= nThisEndRow+1; nThisRow++)
1062     {
1063         SCROW nThisOther = ( nThisRow <= nThisEndRow ) ? pOtherRows[nThisRow] : (nOtherEndRow+1);
1064         if ( ValidRow(nThisOther) )
1065         {
1066             if ( bInFill )
1067             {
1068                 if ( nThisOther > nFillStart )      // is there something to distribute?
1069                 {
1070                     SCROW nDiff1 = nThisOther - nFillStart;
1071                     SCROW nDiff2 = nThisRow   - nFillPos;
1072                     SCROW nMinDiff = std::min(nDiff1, nDiff2);
1073                     for (SCROW i=0; i<nMinDiff; i++)
1074                         pOtherRows[nFillPos+i] = nFillStart+i;
1075                 }
1076 
1077                 bInFill = false;
1078             }
1079             nFillStart = nThisOther + 1;
1080             nFillPos = nThisRow + 1;
1081         }
1082         else
1083             bInFill = true;
1084     }
1085 }
1086 
CompareDocument(ScDocument & rOtherDoc)1087 void ScDocument::CompareDocument( ScDocument& rOtherDoc )
1088 {
1089     if (!pChangeTrack)
1090         return;
1091 
1092     SCTAB nThisCount = GetTableCount();
1093     SCTAB nOtherCount = rOtherDoc.GetTableCount();
1094     std::unique_ptr<SCTAB[]> pOtherTabs(new SCTAB[nThisCount]);
1095     SCTAB nThisTab;
1096 
1097     //  compare tables with identical names
1098     OUString aThisName;
1099     OUString aOtherName;
1100     for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
1101     {
1102         SCTAB nOtherTab = SCTAB_MAX;
1103         if (!IsScenario(nThisTab))  // skip scenarios
1104         {
1105             GetName( nThisTab, aThisName );
1106             for (SCTAB nTemp=0; nTemp<nOtherCount && nOtherTab>MAXTAB; nTemp++)
1107                 if (!rOtherDoc.IsScenario(nTemp))
1108                 {
1109                     rOtherDoc.GetName( nTemp, aOtherName );
1110                     if ( aThisName == aOtherName )
1111                         nOtherTab = nTemp;
1112                 }
1113         }
1114         pOtherTabs[nThisTab] = nOtherTab;
1115     }
1116     //  fill in, so that un-named tables don't get lost
1117     SCTAB nFillStart = 0;
1118     SCTAB nFillPos = 0;
1119     bool bInFill = false;
1120     for (nThisTab = 0; nThisTab <= nThisCount; nThisTab++)
1121     {
1122         SCTAB nThisOther = ( nThisTab < nThisCount ) ? pOtherTabs[nThisTab] : nOtherCount;
1123         if ( ValidTab(nThisOther) )
1124         {
1125             if ( bInFill )
1126             {
1127                 if ( nThisOther > nFillStart )      // is there something to distribute?
1128                 {
1129                     SCTAB nDiff1 = nThisOther - nFillStart;
1130                     SCTAB nDiff2 = nThisTab   - nFillPos;
1131                     SCTAB nMinDiff = std::min(nDiff1, nDiff2);
1132                     for (SCTAB i=0; i<nMinDiff; i++)
1133                         if ( !IsScenario(nFillPos+i) && !rOtherDoc.IsScenario(nFillStart+i) )
1134                             pOtherTabs[nFillPos+i] = nFillStart+i;
1135                 }
1136 
1137                 bInFill = false;
1138             }
1139             nFillStart = nThisOther + 1;
1140             nFillPos = nThisTab + 1;
1141         }
1142         else
1143             bInFill = true;
1144     }
1145 
1146     //  compare tables in the original order
1147 
1148     for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
1149     {
1150         SCTAB nOtherTab = pOtherTabs[nThisTab];
1151         if ( ValidTab(nOtherTab) )
1152         {
1153             SCCOL nThisEndCol = 0;
1154             SCROW nThisEndRow = 0;
1155             SCCOL nOtherEndCol = 0;
1156             SCROW nOtherEndRow = 0;
1157             GetCellArea( nThisTab, nThisEndCol, nThisEndRow );
1158             rOtherDoc.GetCellArea( nOtherTab, nOtherEndCol, nOtherEndRow );
1159             SCCOL nEndCol = std::max(nThisEndCol, nOtherEndCol);
1160             SCROW nEndRow = std::max(nThisEndRow, nOtherEndRow);
1161             SCCOL nThisCol;
1162             SCROW nThisRow;
1163             sal_uLong n1,n2;    // for AppendDeleteRange
1164 
1165             //TODO: one Progress over all tables ???
1166 
1167             OUString aTabName;
1168             GetName( nThisTab, aTabName );
1169             OUString aTemplate = ScResId(STR_PROGRESS_COMPARING);
1170             sal_Int32 nIndex = 0;
1171             OUString aProText = o3tl::getToken(aTemplate, 0, '#', nIndex ) +
1172                 aTabName +
1173                 o3tl::getToken(aTemplate, 0, '#', nIndex );
1174             ScProgress aProgress( GetDocumentShell(), aProText, 3*nThisEndRow, true );  // 2x FindOrder, 1x here
1175             tools::Long nProgressStart = 2*nThisEndRow;                    // start for here
1176 
1177             std::unique_ptr<SCCOLROW[]> pTempRows(new SCCOLROW[nThisEndRow+1]);
1178             std::unique_ptr<SCCOLROW[]> pOtherRows(new SCCOLROW[nThisEndRow+1]);
1179             std::unique_ptr<SCCOLROW[]> pOtherCols(new SCCOLROW[nThisEndCol+1]);
1180 
1181             //  find inserted/deleted columns/rows:
1182             //  Two attempts:
1183             //  1) compare original rows                    (pTempRows)
1184             //  2) compare original columns                 (pOtherCols)
1185             //     with this column order compare rows      (pOtherRows)
1186 
1187             //TODO: compare columns twice with different nMinGood ???
1188 
1189             // 1
1190             FindOrder( pTempRows.get(), nThisEndRow, nOtherEndRow, false,
1191                         rOtherDoc, nThisTab, nOtherTab, nEndCol, nullptr, &aProgress, 0 );
1192             // 2
1193             FindOrder( pOtherCols.get(), nThisEndCol, nOtherEndCol, true,
1194                         rOtherDoc, nThisTab, nOtherTab, nEndRow, nullptr, nullptr, 0 );
1195             FindOrder( pOtherRows.get(), nThisEndRow, nOtherEndRow, false,
1196                         rOtherDoc, nThisTab, nOtherTab, nThisEndCol,
1197                        pOtherCols.get(), &aProgress, nThisEndRow );
1198 
1199             sal_uLong nMatch1 = 0;  // pTempRows, no columns
1200             for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
1201                 if (ValidRow(pTempRows[nThisRow]))
1202                     nMatch1 += SC_DOCCOMP_MAXDIFF -
1203                                RowDifferences( nThisRow, nThisTab, rOtherDoc, pTempRows[nThisRow],
1204                                                 nOtherTab, nEndCol, nullptr );
1205 
1206             sal_uLong nMatch2 = 0;  // pOtherRows, pOtherCols
1207             for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
1208                 if (ValidRow(pOtherRows[nThisRow]))
1209                     nMatch2 += SC_DOCCOMP_MAXDIFF -
1210                                RowDifferences( nThisRow, nThisTab, rOtherDoc, pOtherRows[nThisRow],
1211                                                nOtherTab, nThisEndCol, pOtherCols.get() );
1212 
1213             if ( nMatch1 >= nMatch2 )           // without columns ?
1214             {
1215                 //  reset columns
1216                 for (nThisCol = 0; nThisCol<=nThisEndCol; nThisCol++)
1217                     pOtherCols[nThisCol] = nThisCol;
1218 
1219                 //  swap row-arrays (they get both deleted anyway)
1220                 pTempRows.swap(pOtherRows);
1221             }
1222             else
1223             {
1224                 //  remains for pOtherCols, pOtherRows
1225             }
1226 
1227             //  Generate Change-Actions
1228             //  1) columns from the right
1229             //  2) rows from below
1230             //  3) single cells in normal order
1231 
1232             //  Actions for inserted/deleted columns
1233 
1234             SCCOL nLastOtherCol = static_cast<SCCOL>(nOtherEndCol + 1);
1235             //  nThisEndCol ... 0
1236             for ( nThisCol = nThisEndCol+1; nThisCol > 0; )
1237             {
1238                 --nThisCol;
1239                 SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
1240                 if ( ValidCol(nOtherCol) && nOtherCol+1 < nLastOtherCol )
1241                 {
1242                     // gap -> deleted
1243                     ScRange aDelRange( nOtherCol+1, 0, nOtherTab,
1244                                         nLastOtherCol-1, MaxRow(), nOtherTab );
1245                     pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
1246                 }
1247                 if ( nOtherCol > MaxCol() )                       // inserted
1248                 {
1249                     //  combine
1250                     if ( nThisCol == nThisEndCol || ValidCol(static_cast<SCCOL>(pOtherCols[nThisCol+1])) )
1251                     {
1252                         SCCOL nFirstNew = nThisCol;
1253                         while ( nFirstNew > 0 && pOtherCols[nFirstNew-1] > MaxCol() )
1254                             --nFirstNew;
1255                         SCCOL nDiff = nThisCol - nFirstNew;
1256                         ScRange aRange( nLastOtherCol, 0, nOtherTab,
1257                                         nLastOtherCol+nDiff, MaxRow(), nOtherTab );
1258                         pChangeTrack->AppendInsert( aRange );
1259                     }
1260                 }
1261                 else
1262                     nLastOtherCol = nOtherCol;
1263             }
1264             if ( nLastOtherCol > 0 )                            // deleted at the very top
1265             {
1266                 ScRange aDelRange( 0, 0, nOtherTab,
1267                                     nLastOtherCol-1, MaxRow(), nOtherTab );
1268                 pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
1269             }
1270 
1271             //  Actions for inserted/deleted rows
1272 
1273             SCROW nLastOtherRow = nOtherEndRow + 1;
1274             //  nThisEndRow ... 0
1275             for ( nThisRow = nThisEndRow+1; nThisRow > 0; )
1276             {
1277                 --nThisRow;
1278                 SCROW nOtherRow = pOtherRows[nThisRow];
1279                 if ( ValidRow(nOtherRow) && nOtherRow+1 < nLastOtherRow )
1280                 {
1281                     // gap -> deleted
1282                     ScRange aDelRange( 0, nOtherRow+1, nOtherTab,
1283                                         MaxCol(), nLastOtherRow-1, nOtherTab );
1284                     pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
1285                 }
1286                 if ( nOtherRow > MaxRow() )                       // inserted
1287                 {
1288                     //  combine
1289                     if ( nThisRow == nThisEndRow || ValidRow(pOtherRows[nThisRow+1]) )
1290                     {
1291                         SCROW nFirstNew = nThisRow;
1292                         while ( nFirstNew > 0 && pOtherRows[nFirstNew-1] > MaxRow() )
1293                             --nFirstNew;
1294                         SCROW nDiff = nThisRow - nFirstNew;
1295                         ScRange aRange( 0, nLastOtherRow, nOtherTab,
1296                                         MaxCol(), nLastOtherRow+nDiff, nOtherTab );
1297                         pChangeTrack->AppendInsert( aRange );
1298                     }
1299                 }
1300                 else
1301                     nLastOtherRow = nOtherRow;
1302             }
1303             if ( nLastOtherRow > 0 )                            // deleted at the very top
1304             {
1305                 ScRange aDelRange( 0, 0, nOtherTab,
1306                                     MaxCol(), nLastOtherRow-1, nOtherTab );
1307                 pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
1308             }
1309 
1310              //  walk rows to find single cells
1311 
1312             for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
1313             {
1314                 SCROW nOtherRow = pOtherRows[nThisRow];
1315                 for (nThisCol = 0; nThisCol <= nThisEndCol; nThisCol++)
1316                 {
1317                     SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
1318                     ScAddress aThisPos( nThisCol, nThisRow, nThisTab );
1319                     ScCellValue aThisCell;
1320                     aThisCell.assign(*this, aThisPos);
1321                     ScCellValue aOtherCell; // start empty
1322                     if ( ValidCol(nOtherCol) && ValidRow(nOtherRow) )
1323                     {
1324                         ScAddress aOtherPos( nOtherCol, nOtherRow, nOtherTab );
1325                         aOtherCell.assign(rOtherDoc, aOtherPos);
1326                     }
1327 
1328                     if (!aThisCell.equalsWithoutFormat(aOtherCell))
1329                     {
1330                         ScRange aRange( aThisPos );
1331                         ScChangeActionContent* pAction = new ScChangeActionContent( aRange );
1332                         pAction->SetOldValue(aOtherCell, &rOtherDoc, this);
1333                         pAction->SetNewValue(aThisCell, this);
1334                         pChangeTrack->Append( pAction );
1335                     }
1336                 }
1337                 aProgress.SetStateOnPercent(nProgressStart+nThisRow);
1338             }
1339         }
1340     }
1341 }
1342 
GetSheetSeparator() const1343 sal_Unicode ScDocument::GetSheetSeparator() const
1344 {
1345     const ScCompiler::Convention* pConv = ScCompiler::GetRefConvention(
1346             FormulaGrammar::extractRefConvention( GetGrammar()));
1347     assert(pConv);
1348     return pConv ? pConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR) : '.';
1349 }
1350 
1351 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1352