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