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