1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
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>
29 #include <document.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>
49 #include <formulacell.hxx>
50 #include <tokenarray.hxx>
51 #include <tokenstringcontext.hxx>
54 using namespace formula
;
56 /** (Goal Seek) Find a value of x that is a root of f(x)
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.
67 @change 24.10.2004 by Kohei Yoshida (kohei@openoffice.org)
71 @change 6 Aug 2013, fdo37341
73 bool ScDocument::Solver(SCCOL nFCol
, SCROW nFRow
, SCTAB nFTab
,
74 SCCOL nVCol
, SCROW nVRow
, SCTAB nVTab
,
75 const OUString
& sValStr
, double& nX
)
79 ScFormulaCell
* pFormula
= nullptr;
80 double fTargetVal
= 0.0;
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
) )
89 ScAddress
aFormulaAdr( nFCol
, nFRow
, nFTab
);
90 pFormula
= GetFormulaCell( aFormulaAdr
);
95 bool bDoneIteration
= false;
96 const ScAddress
aValueAdr(nVCol
, nVRow
, nVTab
);
97 const ScRange
aVRange(aValueAdr
, aValueAdr
); // for SetDirty
99 const sal_uInt16 nMaxIter
= 100;
100 const double fEps
= 1E-10;
101 const double fDelta
= 1E-6;
103 double fXPrev
= GetValue(aValueAdr
);
104 double fBestX
= fXPrev
;
106 // Original value to be restored later if necessary
107 const ScCellValue
aSaveVal(GetRefCellValue(aValueAdr
));
108 const bool changeCellType
= aSaveVal
.getType() != CELLTYPE_VALUE
;
110 SetValue(aValueAdr
, fXPrev
);
111 double* pVCell
= GetValueCell(aValueAdr
);
113 pFormula
->Interpret();
114 bool bError
= ( pFormula
->GetErrCode() != FormulaError::NONE
);
115 // bError always corresponds with fF
117 double fFPrev
= pFormula
->GetValue() - fTargetVal
;
119 double fBestF
= fabs( fFPrev
);
120 if ( fBestF
< fDelta
)
121 bDoneIteration
= true;
123 double fX
= fXPrev
+ fEps
;
127 sal_uInt16 nIter
= 0;
129 bool bHorMoveError
= false;
130 // Conform Regula Falsi Method
131 while ( !bDoneIteration
&& ( nIter
++ < nMaxIter
) )
134 SetDirty( aVRange
, false );
135 pFormula
->Interpret();
136 bError
= ( pFormula
->GetErrCode() != FormulaError::NONE
);
137 fF
= pFormula
->GetValue() - fTargetVal
;
139 if ( fF
== fFPrev
&& !bError
)
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
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;
153 while ( !bDoneHorMove
&& !bHorMoveError
&& nHorIter
++ < nHorMaxIter
)
155 double fHorAngle
= fHorStepAngle
* static_cast<double>( nHorIter
);
156 double fHorTangent
= std::tan(basegfx::deg2rad(fHorAngle
));
159 while( nIdx
++ < 2 && !bDoneHorMove
)
163 fHorX
= fX
+ fabs( fF
) * fHorTangent
;
165 fHorX
= fX
- fabs( fF
) * fHorTangent
;
168 SetDirty( aVRange
, false );
169 pFormula
->Interpret();
170 bHorMoveError
= ( pFormula
->GetErrCode() != FormulaError::NONE
);
174 fF
= pFormula
->GetValue() - fTargetVal
;
183 bHorMoveError
= true;
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
);
194 else if ( bHorMoveError
)
196 else if ( fabs(fF
) < fDelta
)
200 bDoneIteration
= true;
204 if ( fabs(fF
) + fDelta
< fBestF
)
210 if ( ( fXPrev
- fX
) != 0 )
212 fSlope
= ( fFPrev
- fF
) / ( fXPrev
- fX
);
213 if ( fabs( fSlope
) < fEps
)
214 fSlope
= fSlope
< 0.0 ? -fEps
: fEps
;
221 fX
= fX
- ( fF
/ fSlope
);
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
;
229 if ( bDoneIteration
)
232 SetDirty( aVRange
, false );
233 pFormula
->Interpret();
234 if ( fabs( pFormula
->GetValue() - fTargetVal
) > fabs( fF
) )
238 else if ( bError
|| bHorMoveError
)
243 aSaveVal
.commit(*this, aValueAdr
);
245 *pVCell
= aSaveVal
.getDouble();
246 SetDirty( aVRange
, false );
247 pFormula
->Interpret();
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
)
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())
265 SAL_WARN("sc", "ScDocument::InsertMatrixFormula: No table marked");
268 if (comphelper::IsFuzzing())
271 if (nCol2
- nCol1
> 64)
273 if (nRow2
- nRow1
> 64)
276 assert( ValidColRow( nCol1
, nRow1
) && ValidColRow( nCol2
, nRow2
));
278 SCTAB nTab1
= *rMark
.begin();
280 ScFormulaCell
* pCell
;
281 ScAddress
aPos( nCol1
, nRow1
, nTab1
);
283 pCell
= new ScFormulaCell(*this, aPos
, *pArr
, eGram
, ScMatrixMode::Formula
);
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
)
298 pCell
= maTabs
[rTab
]->SetFormulaCell(nCol1
, nRow1
, pCell
);
299 if (!pCell
) //NULL if nCol1/nRow1 is invalid, which it can't be here
303 maTabs
[rTab
]->SetFormulaCell(
306 *pCell
, *this, ScAddress(nCol1
, nRow1
, rTab
), ScCloneFlags::StartListening
));
309 ScSingleRefData aRefData
;
310 aRefData
.InitFlags();
311 aRefData
.SetRelCol(0);
312 aRefData
.SetRelRow(0);
313 aRefData
.SetRelTab(0); // 2D matrix, always same sheet
315 ScTokenArray
aArr(*this); // consists only of one single reference token.
316 formula::FormulaToken
* t
= aArr
.AddMatrixSingleReference(aRefData
);
318 for (const SCTAB
& nTab
: rMark
)
323 ScTable
* pTab
= FetchTable(nTab
);
327 for (SCCOL nCol
: GetWritableColumnsRange(nTab
, nCol1
, nCol2
))
329 aRefData
.SetRelCol(nCol1
- nCol
);
330 for (SCROW nRow
= nRow1
; nRow
<= nRow2
; ++nRow
)
332 if (nCol
== nCol1
&& nRow
== nRow1
)
333 // Skip the base position.
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
);
349 void ScDocument::InsertTableOp(const ScTabOpParam
& rParam
, // multiple (repeated?) operation
350 SCCOL nCol1
, SCROW nRow1
, SCCOL nCol2
, SCROW nRow2
,
351 const ScMarkData
& rMark
)
353 PutInOrder(nCol1
, nCol2
);
354 PutInOrder(nRow1
, nRow2
);
355 assert( ValidColRow( nCol1
, nRow1
) && ValidColRow( nCol2
, nRow2
));
361 SCTAB nMax
= GetTableCount();
362 for (const auto& rTab
: rMark
)
377 OSL_FAIL("ScDocument::InsertTableOp: No table marked");
382 OUStringBuffer
aForString("="
383 + ScCompiler::GetNativeSymbol(ocTableOp
)
384 + ScCompiler::GetNativeSymbol( ocOpen
));
386 const OUString
& sSep
= ScCompiler::GetNativeSymbol( ocSep
);
387 if (rParam
.meMode
== ScTabOpParam::Column
) // column only
389 aRef
.Set( rParam
.aRefFormulaCell
.GetAddress(), true, false, false );
390 aForString
.append(aRef
.GetRefString(*this, nTab1
)
392 + rParam
.aRefColCell
.GetRefString(*this, nTab1
)
394 aRef
.Set( nCol1
, nRow1
, nTab1
, false, true, true );
395 aForString
.append(aRef
.GetRefString(*this, nTab1
));
397 nCol2
= std::min( nCol2
, static_cast<SCCOL
>(rParam
.aRefFormulaEnd
.Col() -
398 rParam
.aRefFormulaCell
.Col() + nCol1
+ 1));
400 else if (rParam
.meMode
== ScTabOpParam::Row
) // row only
402 aRef
.Set( rParam
.aRefFormulaCell
.GetAddress(), false, true, false );
403 aForString
.append(aRef
.GetRefString(*this, nTab1
)
405 + rParam
.aRefRowCell
.GetRefString(*this, nTab1
)
407 aRef
.Set( nCol1
, nRow1
, nTab1
, true, false, true );
408 aForString
.append(aRef
.GetRefString(*this, nTab1
));
410 nRow2
= std::min( nRow2
, static_cast<SCROW
>(rParam
.aRefFormulaEnd
.Row() -
411 rParam
.aRefFormulaCell
.Row() + nRow1
+ 1));
415 aForString
.append(rParam
.aRefFormulaCell
.GetRefString(*this, nTab1
)
417 + rParam
.aRefColCell
.GetRefString(*this, nTab1
)
419 aRef
.Set( nCol1
, nRow1
+ 1, nTab1
, false, true, true );
420 aForString
.append(aRef
.GetRefString(*this, nTab1
)
422 + rParam
.aRefRowCell
.GetRefString(*this, nTab1
)
424 aRef
.Set( nCol1
+ 1, nRow1
, nTab1
, true, false, true );
425 aForString
.append(aRef
.GetRefString(*this, nTab1
));
428 aForString
.append(ScCompiler::GetNativeSymbol( ocClose
));
430 ScFormulaCell
aRefCell( *this, ScAddress( nCol1
, nRow1
, nTab1
), aForString
.makeStringAndClear(),
431 formula::FormulaGrammar::GRAM_NATIVE
, ScMatrixMode::NONE
);
432 for( j
= nCol1
; j
<= nCol2
; j
++ )
434 for( k
= nRow1
; k
<= nRow2
; k
++ )
436 for (const auto& rTab
: rMark
)
441 maTabs
[rTab
]->SetFormulaCell(
442 j
, k
, new ScFormulaCell(aRefCell
, *this, ScAddress(j
, k
, rTab
), ScCloneFlags::StartListening
));
450 bool setCacheTableReferenced(const ScDocument
& rDoc
, formula::FormulaToken
& rToken
, ScExternalRefManager
& rRefMgr
, const ScAddress
& rPos
)
452 switch (rToken
.GetType())
454 case svExternalSingleRef
:
455 return rRefMgr
.setCacheTableReferenced(
456 rToken
.GetIndex(), rToken
.GetString().getString(), 1);
457 case svExternalDoubleRef
:
459 const ScComplexRefData
& rRef
= *rToken
.GetDoubleRef();
460 ScRange aAbs
= rRef
.toAbs(rDoc
, rPos
);
461 size_t nSheets
= aAbs
.aEnd
.Tab() - aAbs
.aStart
.Tab() + 1;
462 return rRefMgr
.setCacheTableReferenced(
463 rToken
.GetIndex(), rToken
.GetString().getString(), nSheets
);
466 /* TODO: external names aren't supported yet, but would
467 * have to be marked as well, if so. Mechanism would be
469 OSL_FAIL("ScDocument::MarkUsedExternalReferences: implement the svExternalName case!");
479 bool ScDocument::MarkUsedExternalReferences( const ScTokenArray
& rArr
, const ScAddress
& rPos
)
484 ScExternalRefManager
* pRefMgr
= nullptr;
485 formula::FormulaTokenArrayPlainIterator
aIter( rArr
);
486 bool bAllMarked
= false;
489 formula::FormulaToken
* t
= aIter
.GetNextReferenceOrName();
492 if (t
->IsExternalRef())
495 pRefMgr
= GetExternalRefManager();
497 bAllMarked
= setCacheTableReferenced(*this, *t
, *pRefMgr
, rPos
);
499 else if (t
->GetType() == svIndex
)
501 // this is a named range. Check if the range contains an external
503 ScRangeData
* pRangeData
= GetRangeName()->findByIndex(t
->GetIndex());
507 ScTokenArray
* pArray
= pRangeData
->GetCode();
508 formula::FormulaTokenArrayPlainIterator
aArrayIter(*pArray
);
509 for (t
= aArrayIter
.First(); t
; t
= aArrayIter
.Next())
511 if (!t
->IsExternalRef())
515 pRefMgr
= GetExternalRefManager();
517 bAllMarked
= setCacheTableReferenced(*this, *t
, *pRefMgr
, rPos
);
524 bool ScDocument::GetNextSpellingCell(SCCOL
& nCol
, SCROW
& nRow
, SCTAB nTab
,
525 bool bInSel
, const ScMarkData
& rMark
) const
527 if (const ScTable
* pTable
= FetchTable(nTab
))
528 return pTable
->GetNextSpellingCell( nCol
, nRow
, bInSel
, rMark
);
532 bool ScDocument::GetNextMarkedCell( SCCOL
& rCol
, SCROW
& rRow
, SCTAB nTab
,
533 const ScMarkData
& rMark
)
535 if (ScTable
* pTable
= FetchTable(nTab
))
536 return pTable
->GetNextMarkedCell( rCol
, rRow
, rMark
);
540 void ScDocument::ReplaceStyle(const SvxSearchItem
& rSearchItem
,
541 SCCOL nCol
, SCROW nRow
, SCTAB nTab
,
542 const ScMarkData
& rMark
)
544 if (ScTable
* pTable
= FetchTable(nTab
))
545 pTable
->ReplaceStyle(rSearchItem
, nCol
, nRow
, rMark
, true/*bIsUndoP*/);
548 void ScDocument::CompileDBFormula()
550 sc::CompileFormulaContext
aCxt(*this);
551 for (auto& rxTab
: maTabs
)
554 rxTab
->CompileDBFormula(aCxt
);
558 void ScDocument::CompileColRowNameFormula()
560 sc::CompileFormulaContext
aCxt(*this);
561 for (auto& rxTab
: maTabs
)
564 rxTab
->CompileColRowNameFormula(aCxt
);
568 void ScDocument::InvalidateTableArea()
570 for (auto& rxTab
: maTabs
)
574 rxTab
->InvalidateTableArea();
575 if ( rxTab
->IsScenario() )
576 rxTab
->InvalidateScenarioRanges();
580 sal_Int32
ScDocument::GetMaxStringLen( SCTAB nTab
, SCCOL nCol
,
581 SCROW nRowStart
, SCROW nRowEnd
, rtl_TextEncoding eCharSet
) const
583 if (const ScTable
* pTable
= FetchTable(nTab
))
584 return pTable
->GetMaxStringLen(nCol
, nRowStart
, nRowEnd
, eCharSet
);
588 sal_Int32
ScDocument::GetMaxNumberStringLen( sal_uInt16
& nPrecision
, SCTAB nTab
,
589 SCCOL nCol
, SCROW nRowStart
, SCROW nRowEnd
) const
591 if (const ScTable
* pTable
= FetchTable(nTab
))
592 return pTable
->GetMaxNumberStringLen(nPrecision
, nCol
, nRowStart
, nRowEnd
);
596 bool ScDocument::GetSelectionFunction( ScSubTotalFunc eFunc
,
597 const ScAddress
& rCursor
, const ScMarkData
& rMark
,
600 ScFunctionData
aData(eFunc
);
602 ScMarkData
aMark(rMark
);
604 if (!aMark
.IsMultiMarked() && !aMark
.IsCellMarked(rCursor
.Col(), rCursor
.Row()))
605 aMark
.SetMarkArea(ScRange(rCursor
));
607 SCTAB nMax
= GetTableCount();
608 ScMarkData::const_iterator itr
= aMark
.begin(), itrEnd
= aMark
.end();
610 for (; itr
!= itrEnd
&& *itr
< nMax
&& !aData
.getError(); ++itr
)
612 maTabs
[*itr
]->UpdateSelectionFunction(aData
, aMark
);
614 rResult
= aData
.getResult();
615 if (aData
.getError())
618 return !aData
.getError();
621 double ScDocument::RoundValueAsShown( double fVal
, sal_uInt32 nFormat
, const ScInterpreterContext
* pContext
) const
623 const SvNumberFormatter
* pFormatter
= pContext
? pContext
->GetFormatTable() : GetFormatTable();
624 const SvNumberformat
* pFormat
= pFormatter
->GetEntry( nFormat
);
627 SvNumFormatType nType
= pFormat
->GetMaskedType();
628 if (nType
!= SvNumFormatType::DATE
&& nType
!= SvNumFormatType::TIME
&& nType
!= SvNumFormatType::DATETIME
)
630 // MSVC doesn't recognize all paths init nPrecision and wails about
631 // "potentially uninitialized local variable 'nPrecision' used"
632 // so init to some random sensible value preserving all decimals.
633 short nPrecision
= 20;
634 bool bStdPrecision
= ((nFormat
% SV_COUNTRY_LANGUAGE_OFFSET
) == 0);
637 sal_uInt16 nIdx
= pFormat
->GetSubformatIndex( fVal
);
638 nPrecision
= static_cast<short>(pFormat
->GetFormatPrecision( nIdx
));
641 case SvNumFormatType::PERCENT
: // 0.41% == 0.0041
644 case SvNumFormatType::SCIENTIFIC
: // 1.23e-3 == 0.00123
648 nExp
= static_cast<short>(floor( log10( fVal
) ));
649 else if ( fVal
< 0.0 )
650 nExp
= static_cast<short>(floor( log10( -fVal
) ));
652 short nInteger
= static_cast<short>(pFormat
->GetFormatIntegerDigits( nIdx
));
653 if ( nInteger
> 1 ) // Engineering notation
655 short nIncrement
= nExp
% nInteger
;
656 if ( nIncrement
!= 0 )
658 nPrecision
+= nIncrement
;
660 nPrecision
+= nInteger
;
665 case SvNumFormatType::FRACTION
: // get value of fraction representation
667 return pFormat
->GetRoundFractionValue( fVal
);
669 case SvNumFormatType::NUMBER
:
670 case SvNumFormatType::CURRENCY
:
671 { // tdf#106253 Thousands divisors for format "0,"
672 const sal_uInt16 nTD
= pFormat
->GetThousandDivisorPrecision( nIdx
);
673 if (nTD
== SvNumberFormatter::UNLIMITED_PRECISION
)
674 // Format contains General keyword, handled below.
675 bStdPrecision
= true;
685 nPrecision
= static_cast<short>(GetDocOptions().GetStdPrecision());
686 // #i115512# no rounding for automatic decimals
687 if (nPrecision
== static_cast<short>(SvNumberFormatter::UNLIMITED_PRECISION
))
690 double fRound
= ::rtl::math::round( fVal
, nPrecision
);
691 if ( ::rtl::math::approxEqual( fVal
, fRound
) )
692 return fVal
; // rounding might introduce some error
700 // conditional formats and validation ranges
702 sal_uInt32
ScDocument::AddCondFormat( std::unique_ptr
<ScConditionalFormat
> pNew
, SCTAB nTab
)
707 if (ScTable
* pTable
= FetchTable(nTab
))
708 return pTable
->AddCondFormat(std::move(pNew
));
713 sal_uInt32
ScDocument::AddValidationEntry( const ScValidationData
& rNew
)
716 return 0; // empty is always 0
718 if (!pValidationList
)
720 ScMutationGuard
aGuard(*this, ScMutationGuardFlags::CORE
);
721 pValidationList
.reset(new ScValidationDataList
);
725 for( const auto& rxData
: *pValidationList
)
727 const ScValidationData
* pData
= rxData
.get();
728 sal_uInt32 nKey
= pData
->GetKey();
729 if ( pData
->EqualEntries( rNew
) )
735 // might be called from ScPatternAttr::MigrateToDocument; thus clone (real copy)
736 sal_uInt32 nNewKey
= nMax
+ 1;
737 std::unique_ptr
<ScValidationData
> pInsert(rNew
.Clone(this));
738 pInsert
->SetKey( nNewKey
);
739 ScMutationGuard
aGuard(*this, ScMutationGuardFlags::CORE
);
740 pValidationList
->InsertNew( std::move(pInsert
) );
744 const SfxPoolItem
* ScDocument::GetEffItem(
745 SCCOL nCol
, SCROW nRow
, SCTAB nTab
, sal_uInt16 nWhich
) const
747 const ScPatternAttr
* pPattern
= GetPattern( nCol
, nRow
, nTab
);
750 const SfxItemSet
& rSet
= pPattern
->GetItemSet();
751 const ScCondFormatItem
* pConditionalItem
= nullptr;
752 if ( rSet
.GetItemState( ATTR_CONDITIONAL
, true, &pConditionalItem
) == SfxItemState::SET
)
754 const ScCondFormatIndexes
& rIndex
= pConditionalItem
->GetCondFormatData();
755 ScConditionalFormatList
* pCondFormList
= GetCondFormList( nTab
);
756 if (!rIndex
.empty() && pCondFormList
)
758 for(const auto& rItem
: rIndex
)
760 const ScConditionalFormat
* pForm
= pCondFormList
->GetFormat( rItem
);
763 ScAddress
aPos(nCol
, nRow
, nTab
);
764 ScRefCellValue
aCell(const_cast<ScDocument
&>(*this), aPos
);
765 const OUString aStyle
= pForm
->GetCellStyle(aCell
, aPos
);
766 if (!aStyle
.isEmpty())
768 SfxStyleSheetBase
* pStyleSheet
= mxPoolHelper
->GetStylePool()->Find(
769 aStyle
, SfxStyleFamily::Para
);
770 const SfxPoolItem
* pItem
= nullptr;
771 if ( pStyleSheet
&& pStyleSheet
->GetItemSet().GetItemState(
772 nWhich
, true, &pItem
) == SfxItemState::SET
)
779 return &rSet
.Get( nWhich
);
781 OSL_FAIL("no pattern");
785 const SfxItemSet
* ScDocument::GetCondResult( SCCOL nCol
, SCROW nRow
, SCTAB nTab
, ScRefCellValue
* pCell
) const
787 ScConditionalFormatList
* pFormatList
= GetCondFormList(nTab
);
791 ScAddress
aPos(nCol
, nRow
, nTab
);
792 ScRefCellValue aCell
;
793 if( pCell
== nullptr )
795 aCell
.assign(const_cast<ScDocument
&>(*this), aPos
);
798 const ScPatternAttr
* pPattern
= GetPattern( nCol
, nRow
, nTab
);
799 const ScCondFormatIndexes
& rIndex
=
800 pPattern
->GetItem(ATTR_CONDITIONAL
).GetCondFormatData();
802 return GetCondResult(*pCell
, aPos
, *pFormatList
, rIndex
);
805 const SfxItemSet
* ScDocument::GetCondResult(
806 ScRefCellValue
& rCell
, const ScAddress
& rPos
, const ScConditionalFormatList
& rList
,
807 const ScCondFormatIndexes
& rIndex
) const
809 for (const auto& rItem
: rIndex
)
811 const ScConditionalFormat
* pForm
= rList
.GetFormat(rItem
);
815 const OUString aStyle
= pForm
->GetCellStyle(rCell
, rPos
);
816 if (!aStyle
.isEmpty())
818 SfxStyleSheetBase
* pStyleSheet
=
819 mxPoolHelper
->GetStylePool()->Find(aStyle
, SfxStyleFamily::Para
);
822 return &pStyleSheet
->GetItemSet();
824 // if style is not there, treat like no condition
831 ScConditionalFormat
* ScDocument::GetCondFormat(
832 SCCOL nCol
, SCROW nRow
, SCTAB nTab
) const
834 sal_uInt32 nIndex
= 0;
835 const ScCondFormatIndexes
& rCondFormats
= GetAttr(nCol
, nRow
, nTab
, ATTR_CONDITIONAL
)->GetCondFormatData();
837 if(!rCondFormats
.empty())
838 nIndex
= rCondFormats
[0];
842 ScConditionalFormatList
* pCondFormList
= GetCondFormList(nTab
);
844 return pCondFormList
->GetFormat( nIndex
);
847 OSL_FAIL("pCondFormList is 0");
854 ScConditionalFormatList
* ScDocument::GetCondFormList(SCTAB nTab
) const
857 return maTabs
[nTab
]->GetCondFormList();
861 void ScDocument::SetCondFormList( ScConditionalFormatList
* pList
, SCTAB nTab
)
863 if (ScTable
* pTable
= FetchTable(nTab
))
864 pTable
->SetCondFormList(pList
);
867 const ScValidationData
* ScDocument::GetValidationEntry( sal_uInt32 nIndex
) const
869 if ( pValidationList
)
870 return pValidationList
->GetData( nIndex
);
875 void ScDocument::DeleteConditionalFormat(sal_uLong nOldIndex
, SCTAB nTab
)
877 if (ScTable
* pTable
= FetchTable(nTab
))
878 pTable
->DeleteConditionalFormat(nOldIndex
);
881 bool ScDocument::HasDetectiveOperations() const
883 return pDetOpList
&& pDetOpList
->Count();
886 void ScDocument::AddDetectiveOperation( const ScDetOpData
& rData
)
889 pDetOpList
.reset(new ScDetOpList
);
891 pDetOpList
->Append( rData
);
894 void ScDocument::ClearDetectiveOperations()
896 pDetOpList
.reset(); // deletes also the entries
899 void ScDocument::SetDetOpList(std::unique_ptr
<ScDetOpList
> pNew
)
901 pDetOpList
= std::move(pNew
);
904 // Comparison of Documents
907 #define SC_DOCCOMP_MAXDIFF 256
908 #define SC_DOCCOMP_MINGOOD 128
909 #define SC_DOCCOMP_COLUMNS 10
910 #define SC_DOCCOMP_ROWS 100
912 sal_uInt16
ScDocument::RowDifferences( SCROW nThisRow
, SCTAB nThisTab
,
913 ScDocument
& rOtherDoc
, SCROW nOtherRow
, SCTAB nOtherTab
,
914 SCCOL nMaxCol
, const SCCOLROW
* pOtherCols
)
918 for (SCCOL nThisCol
=0; nThisCol
<=nMaxCol
; nThisCol
++)
922 nOtherCol
= static_cast<SCCOL
>(pOtherCols
[nThisCol
]);
924 nOtherCol
= nThisCol
;
926 if (ValidCol(nOtherCol
)) // only compare columns that are common to both docs
928 ScRefCellValue
aThisCell(*this, ScAddress(nThisCol
, nThisRow
, nThisTab
));
929 ScRefCellValue
aOtherCell(rOtherDoc
, ScAddress(nOtherCol
, nOtherRow
, nOtherTab
));
930 if (!aThisCell
.equalsWithoutFormat(aOtherCell
))
932 if (!aThisCell
.isEmpty() && !aOtherCell
.isEmpty())
935 nDif
+= 4; // content <-> empty counts more
938 if (!aThisCell
.isEmpty() || !aOtherCell
.isEmpty())
944 return static_cast<sal_uInt16
>((nDif
*64)/nUsed
); // max.256 (SC_DOCCOMP_MAXDIFF)
946 OSL_ENSURE(!nDif
,"Diff without Used");
950 sal_uInt16
ScDocument::ColDifferences( SCCOL nThisCol
, SCTAB nThisTab
,
951 ScDocument
& rOtherDoc
, SCCOL nOtherCol
, SCTAB nOtherTab
,
952 SCROW nMaxRow
, const SCCOLROW
* pOtherRows
)
955 //TODO: optimize e.g. with iterator?
958 sal_uInt64 nUsed
= 0;
959 for (SCROW nThisRow
=0; nThisRow
<=nMaxRow
; nThisRow
++)
963 nOtherRow
= pOtherRows
[nThisRow
];
965 nOtherRow
= nThisRow
;
967 if (ValidRow(nOtherRow
)) // only compare rows that are common to both docs
969 ScRefCellValue
aThisCell(*this, ScAddress(nThisCol
, nThisRow
, nThisTab
));
970 ScRefCellValue
aOtherCell(rOtherDoc
, ScAddress(nOtherCol
, nOtherRow
, nOtherTab
));
971 if (!aThisCell
.equalsWithoutFormat(aOtherCell
))
973 if (!aThisCell
.isEmpty() && !aOtherCell
.isEmpty())
976 nDif
+= 4; // content <-> empty counts more
979 if (!aThisCell
.isEmpty() || !aOtherCell
.isEmpty())
985 return static_cast<sal_uInt16
>((nDif
*64)/nUsed
); // max.256
987 OSL_ENSURE(!nDif
,"Diff without Used");
991 void ScDocument::FindOrder( SCCOLROW
* pOtherRows
, SCCOLROW nThisEndRow
, SCCOLROW nOtherEndRow
,
992 bool bColumns
, ScDocument
& rOtherDoc
, SCTAB nThisTab
, SCTAB nOtherTab
,
993 SCCOLROW nEndCol
, const SCCOLROW
* pTranslate
, ScProgress
* pProgress
, sal_uInt64 nProAdd
)
995 // bColumns=true: rows are columns and vice versa
997 SCCOLROW nMaxCont
; // continue by how much
998 SCCOLROW nMinGood
; // what is a hit (incl.)
1001 nMaxCont
= SC_DOCCOMP_COLUMNS
; // 10 columns
1002 nMinGood
= SC_DOCCOMP_MINGOOD
;
1004 //TODO: additional pass with nMinGood = 0 ????
1009 nMaxCont
= SC_DOCCOMP_ROWS
; // 100 rows
1010 nMinGood
= SC_DOCCOMP_MINGOOD
;
1012 bool bUseTotal
= bColumns
&& !pTranslate
; // only for the 1st pass
1014 SCCOLROW nOtherRow
= 0;
1017 bool bTotal
= false; // hold for several nThisRow
1018 SCCOLROW nUnknown
= 0;
1019 for (nThisRow
= 0; nThisRow
<= nThisEndRow
; nThisRow
++)
1021 SCCOLROW nTempOther
= nOtherRow
;
1022 bool bFound
= false;
1023 sal_uInt16 nBest
= SC_DOCCOMP_MAXDIFF
;
1024 SCCOLROW nMax
= std::min( nOtherEndRow
, static_cast<SCCOLROW
>(( nTempOther
+ nMaxCont
+ nUnknown
)) );
1025 for (SCCOLROW i
=nTempOther
; i
<=nMax
&& nBest
>0; i
++) // stop at 0
1028 nComp
= ColDifferences( static_cast<SCCOL
>(nThisRow
), nThisTab
, rOtherDoc
, static_cast<SCCOL
>(i
), nOtherTab
, nEndCol
, pTranslate
);
1030 nComp
= RowDifferences( nThisRow
, nThisTab
, rOtherDoc
, i
, nOtherTab
, static_cast<SCCOL
>(nEndCol
), pTranslate
);
1031 if ( nComp
< nBest
&& ( nComp
<= nMinGood
|| bTotal
) )
1037 if ( nComp
< SC_DOCCOMP_MAXDIFF
|| bFound
)
1039 else if ( i
== nTempOther
&& bUseTotal
)
1040 bTotal
= true; // only at the very top
1044 pOtherRows
[nThisRow
] = nTempOther
;
1045 nOtherRow
= nTempOther
+ 1;
1050 pOtherRows
[nThisRow
] = SCROW_MAX
;
1055 pProgress
->SetStateOnPercent(nProAdd
+static_cast<sal_uLong
>(nThisRow
));
1058 // fill in blocks that don't match
1060 SCROW nFillStart
= 0;
1062 bool bInFill
= false;
1063 for (nThisRow
= 0; nThisRow
<= nThisEndRow
+1; nThisRow
++)
1065 SCROW nThisOther
= ( nThisRow
<= nThisEndRow
) ? pOtherRows
[nThisRow
] : (nOtherEndRow
+1);
1066 if ( ValidRow(nThisOther
) )
1070 if ( nThisOther
> nFillStart
) // is there something to distribute?
1072 SCROW nDiff1
= nThisOther
- nFillStart
;
1073 SCROW nDiff2
= nThisRow
- nFillPos
;
1074 SCROW nMinDiff
= std::min(nDiff1
, nDiff2
);
1075 for (SCROW i
=0; i
<nMinDiff
; i
++)
1076 pOtherRows
[nFillPos
+i
] = nFillStart
+i
;
1081 nFillStart
= nThisOther
+ 1;
1082 nFillPos
= nThisRow
+ 1;
1089 void ScDocument::CompareDocument( ScDocument
& rOtherDoc
)
1094 SCTAB nThisCount
= GetTableCount();
1095 SCTAB nOtherCount
= rOtherDoc
.GetTableCount();
1096 std::unique_ptr
<SCTAB
[]> pOtherTabs(new SCTAB
[nThisCount
]);
1099 // compare tables with identical names
1101 OUString aOtherName
;
1102 for (nThisTab
=0; nThisTab
<nThisCount
; nThisTab
++)
1104 SCTAB nOtherTab
= SCTAB_MAX
;
1105 if (!IsScenario(nThisTab
)) // skip scenarios
1107 GetName( nThisTab
, aThisName
);
1108 for (SCTAB nTemp
=0; nTemp
<nOtherCount
&& nOtherTab
>MAXTAB
; nTemp
++)
1109 if (!rOtherDoc
.IsScenario(nTemp
))
1111 rOtherDoc
.GetName( nTemp
, aOtherName
);
1112 if ( aThisName
== aOtherName
)
1116 pOtherTabs
[nThisTab
] = nOtherTab
;
1118 // fill in, so that un-named tables don't get lost
1119 SCTAB nFillStart
= 0;
1121 bool bInFill
= false;
1122 for (nThisTab
= 0; nThisTab
<= nThisCount
; nThisTab
++)
1124 SCTAB nThisOther
= ( nThisTab
< nThisCount
) ? pOtherTabs
[nThisTab
] : nOtherCount
;
1125 if ( ValidTab(nThisOther
) )
1129 if ( nThisOther
> nFillStart
) // is there something to distribute?
1131 SCTAB nDiff1
= nThisOther
- nFillStart
;
1132 SCTAB nDiff2
= nThisTab
- nFillPos
;
1133 SCTAB nMinDiff
= std::min(nDiff1
, nDiff2
);
1134 for (SCTAB i
=0; i
<nMinDiff
; i
++)
1135 if ( !IsScenario(nFillPos
+i
) && !rOtherDoc
.IsScenario(nFillStart
+i
) )
1136 pOtherTabs
[nFillPos
+i
] = nFillStart
+i
;
1141 nFillStart
= nThisOther
+ 1;
1142 nFillPos
= nThisTab
+ 1;
1148 // compare tables in the original order
1150 for (nThisTab
=0; nThisTab
<nThisCount
; nThisTab
++)
1152 SCTAB nOtherTab
= pOtherTabs
[nThisTab
];
1153 if ( ValidTab(nOtherTab
) )
1155 SCCOL nThisEndCol
= 0;
1156 SCROW nThisEndRow
= 0;
1157 SCCOL nOtherEndCol
= 0;
1158 SCROW nOtherEndRow
= 0;
1159 GetCellArea( nThisTab
, nThisEndCol
, nThisEndRow
);
1160 rOtherDoc
.GetCellArea( nOtherTab
, nOtherEndCol
, nOtherEndRow
);
1161 SCCOL nEndCol
= std::max(nThisEndCol
, nOtherEndCol
);
1162 SCROW nEndRow
= std::max(nThisEndRow
, nOtherEndRow
);
1165 sal_uLong n1
,n2
; // for AppendDeleteRange
1167 //TODO: one Progress over all tables ???
1170 GetName( nThisTab
, aTabName
);
1171 OUString aTemplate
= ScResId(STR_PROGRESS_COMPARING
);
1172 sal_Int32 nIndex
= 0;
1173 OUString aProText
= o3tl::getToken(aTemplate
, 0, '#', nIndex
) +
1175 o3tl::getToken(aTemplate
, 0, '#', nIndex
);
1176 ScProgress
aProgress( GetDocumentShell(), aProText
, 3*nThisEndRow
, true ); // 2x FindOrder, 1x here
1177 tools::Long nProgressStart
= 2*nThisEndRow
; // start for here
1179 std::unique_ptr
<SCCOLROW
[]> pTempRows(new SCCOLROW
[nThisEndRow
+1]);
1180 std::unique_ptr
<SCCOLROW
[]> pOtherRows(new SCCOLROW
[nThisEndRow
+1]);
1181 std::unique_ptr
<SCCOLROW
[]> pOtherCols(new SCCOLROW
[nThisEndCol
+1]);
1183 // find inserted/deleted columns/rows:
1185 // 1) compare original rows (pTempRows)
1186 // 2) compare original columns (pOtherCols)
1187 // with this column order compare rows (pOtherRows)
1189 //TODO: compare columns twice with different nMinGood ???
1192 FindOrder( pTempRows
.get(), nThisEndRow
, nOtherEndRow
, false,
1193 rOtherDoc
, nThisTab
, nOtherTab
, nEndCol
, nullptr, &aProgress
, 0 );
1195 FindOrder( pOtherCols
.get(), nThisEndCol
, nOtherEndCol
, true,
1196 rOtherDoc
, nThisTab
, nOtherTab
, nEndRow
, nullptr, nullptr, 0 );
1197 FindOrder( pOtherRows
.get(), nThisEndRow
, nOtherEndRow
, false,
1198 rOtherDoc
, nThisTab
, nOtherTab
, nThisEndCol
,
1199 pOtherCols
.get(), &aProgress
, nThisEndRow
);
1201 sal_uLong nMatch1
= 0; // pTempRows, no columns
1202 for (nThisRow
= 0; nThisRow
<=nThisEndRow
; nThisRow
++)
1203 if (ValidRow(pTempRows
[nThisRow
]))
1204 nMatch1
+= SC_DOCCOMP_MAXDIFF
-
1205 RowDifferences( nThisRow
, nThisTab
, rOtherDoc
, pTempRows
[nThisRow
],
1206 nOtherTab
, nEndCol
, nullptr );
1208 sal_uLong nMatch2
= 0; // pOtherRows, pOtherCols
1209 for (nThisRow
= 0; nThisRow
<=nThisEndRow
; nThisRow
++)
1210 if (ValidRow(pOtherRows
[nThisRow
]))
1211 nMatch2
+= SC_DOCCOMP_MAXDIFF
-
1212 RowDifferences( nThisRow
, nThisTab
, rOtherDoc
, pOtherRows
[nThisRow
],
1213 nOtherTab
, nThisEndCol
, pOtherCols
.get() );
1215 if ( nMatch1
>= nMatch2
) // without columns ?
1218 for (nThisCol
= 0; nThisCol
<=nThisEndCol
; nThisCol
++)
1219 pOtherCols
[nThisCol
] = nThisCol
;
1221 // swap row-arrays (they get both deleted anyway)
1222 pTempRows
.swap(pOtherRows
);
1226 // remains for pOtherCols, pOtherRows
1229 // Generate Change-Actions
1230 // 1) columns from the right
1231 // 2) rows from below
1232 // 3) single cells in normal order
1234 // Actions for inserted/deleted columns
1236 SCCOL nLastOtherCol
= static_cast<SCCOL
>(nOtherEndCol
+ 1);
1237 // nThisEndCol ... 0
1238 for ( nThisCol
= nThisEndCol
+1; nThisCol
> 0; )
1241 SCCOL nOtherCol
= static_cast<SCCOL
>(pOtherCols
[nThisCol
]);
1242 if ( ValidCol(nOtherCol
) && nOtherCol
+1 < nLastOtherCol
)
1245 ScRange
aDelRange( nOtherCol
+1, 0, nOtherTab
,
1246 nLastOtherCol
-1, MaxRow(), nOtherTab
);
1247 pChangeTrack
->AppendDeleteRange( aDelRange
, &rOtherDoc
, n1
, n2
);
1249 if ( nOtherCol
> MaxCol() ) // inserted
1252 if ( nThisCol
== nThisEndCol
|| ValidCol(static_cast<SCCOL
>(pOtherCols
[nThisCol
+1])) )
1254 SCCOL nFirstNew
= nThisCol
;
1255 while ( nFirstNew
> 0 && pOtherCols
[nFirstNew
-1] > MaxCol() )
1257 SCCOL nDiff
= nThisCol
- nFirstNew
;
1258 ScRange
aRange( nLastOtherCol
, 0, nOtherTab
,
1259 nLastOtherCol
+nDiff
, MaxRow(), nOtherTab
);
1260 pChangeTrack
->AppendInsert( aRange
);
1264 nLastOtherCol
= nOtherCol
;
1266 if ( nLastOtherCol
> 0 ) // deleted at the very top
1268 ScRange
aDelRange( 0, 0, nOtherTab
,
1269 nLastOtherCol
-1, MaxRow(), nOtherTab
);
1270 pChangeTrack
->AppendDeleteRange( aDelRange
, &rOtherDoc
, n1
, n2
);
1273 // Actions for inserted/deleted rows
1275 SCROW nLastOtherRow
= nOtherEndRow
+ 1;
1276 // nThisEndRow ... 0
1277 for ( nThisRow
= nThisEndRow
+1; nThisRow
> 0; )
1280 SCROW nOtherRow
= pOtherRows
[nThisRow
];
1281 if ( ValidRow(nOtherRow
) && nOtherRow
+1 < nLastOtherRow
)
1284 ScRange
aDelRange( 0, nOtherRow
+1, nOtherTab
,
1285 MaxCol(), nLastOtherRow
-1, nOtherTab
);
1286 pChangeTrack
->AppendDeleteRange( aDelRange
, &rOtherDoc
, n1
, n2
);
1288 if ( nOtherRow
> MaxRow() ) // inserted
1291 if ( nThisRow
== nThisEndRow
|| ValidRow(pOtherRows
[nThisRow
+1]) )
1293 SCROW nFirstNew
= nThisRow
;
1294 while ( nFirstNew
> 0 && pOtherRows
[nFirstNew
-1] > MaxRow() )
1296 SCROW nDiff
= nThisRow
- nFirstNew
;
1297 ScRange
aRange( 0, nLastOtherRow
, nOtherTab
,
1298 MaxCol(), nLastOtherRow
+nDiff
, nOtherTab
);
1299 pChangeTrack
->AppendInsert( aRange
);
1303 nLastOtherRow
= nOtherRow
;
1305 if ( nLastOtherRow
> 0 ) // deleted at the very top
1307 ScRange
aDelRange( 0, 0, nOtherTab
,
1308 MaxCol(), nLastOtherRow
-1, nOtherTab
);
1309 pChangeTrack
->AppendDeleteRange( aDelRange
, &rOtherDoc
, n1
, n2
);
1312 // walk rows to find single cells
1314 for (nThisRow
= 0; nThisRow
<= nThisEndRow
; nThisRow
++)
1316 SCROW nOtherRow
= pOtherRows
[nThisRow
];
1317 for (nThisCol
= 0; nThisCol
<= nThisEndCol
; nThisCol
++)
1319 SCCOL nOtherCol
= static_cast<SCCOL
>(pOtherCols
[nThisCol
]);
1320 ScAddress
aThisPos( nThisCol
, nThisRow
, nThisTab
);
1321 ScCellValue aThisCell
;
1322 aThisCell
.assign(*this, aThisPos
);
1323 ScCellValue aOtherCell
; // start empty
1324 if ( ValidCol(nOtherCol
) && ValidRow(nOtherRow
) )
1326 ScAddress
aOtherPos( nOtherCol
, nOtherRow
, nOtherTab
);
1327 aOtherCell
.assign(rOtherDoc
, aOtherPos
);
1330 if (!aThisCell
.equalsWithoutFormat(aOtherCell
))
1332 ScRange
aRange( aThisPos
);
1333 ScChangeActionContent
* pAction
= new ScChangeActionContent( aRange
);
1334 pAction
->SetOldValue(aOtherCell
, &rOtherDoc
, this);
1335 pAction
->SetNewValue(aThisCell
, this);
1336 pChangeTrack
->Append( pAction
);
1339 aProgress
.SetStateOnPercent(nProgressStart
+nThisRow
);
1345 sal_Unicode
ScDocument::GetSheetSeparator() const
1347 const ScCompiler::Convention
* pConv
= ScCompiler::GetRefConvention(
1348 FormulaGrammar::extractRefConvention( GetGrammar()));
1350 return pConv
? pConv
->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR
) : '.';
1353 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */