/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project .
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2 . 0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at http : //mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice :
*
* Licensed to the Apache Software Foundation ( ASF ) under one or more
* contributor license agreements . See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership . The ASF licenses this file to you under the Apache
* License , Version 2 . 0 ( the " License " ) ; you may not use this file
* except in compliance with the License . You may obtain a copy of
* the License at http : //www.apache.org/licenses/LICENSE-2.0 .
*/
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <svl/zformat.hxx>
#include <formula/token.hxx>
#include <sal/log.hxx>
#include <comphelper/configuration.hxx>
#include <osl/diagnose.h>
#include <o3tl/string_view.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <table.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <subtotal.hxx>
#include <docoptio.hxx>
#include <markdata.hxx>
#include <validat.hxx>
#include <scitems.hxx>
#include <stlpool.hxx>
#include <poolhelp.hxx>
#include <detdata.hxx>
#include <patattr.hxx>
#include <chgtrack.hxx>
#include <progress.hxx>
#include <paramisc.hxx>
#include <compiler.hxx>
#include <externalrefmgr.hxx>
#include <attrib.hxx>
#include <formulacell.hxx>
#include <tokenarray.hxx>
#include <tokenstringcontext.hxx>
#include <memory>
using namespace formula;
/** (Goal Seek) Find a value of x that is a root of f(x)
This function is used internally for the goal seek operation . It uses the
Regula Falsi ( aka false position ) algorithm to find a root of f ( x ) . The
start value and the target value are to be given by the user in the
goal seek dialog . The f ( x ) in this case is defined as the formula in the
formula cell minus target value . This function may also perform additional
search in the horizontal directions when the f ( x ) is discrete in order to
ensure a non - zero slope necessary for deriving a subsequent x that is
reasonably close to the root of interest .
@ change 24 . 10 . 2004 by Kohei Yoshida ( kohei @ openoffice . org )
@ see # i28955 #
@ change 6 Aug 2013 , fdo37341
*/
bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab,
SCCOL nVCol, SCROW nVRow, SCTAB nVTab,
const OUString& sValStr, double & nX)
{
bool bRet = false ;
nX = 0 .0 ;
ScFormulaCell* pFormula = nullptr;
double fTargetVal = 0 .0 ;
{
CellType eFType = GetCellType(nFCol, nFRow, nFTab);
// #i108005# convert target value to number using default format,
// as previously done in ScInterpreter::GetDouble
sal_uInt32 nFIndex = 0 ;
if ( eFType == CELLTYPE_FORMULA && FetchTable(nVTab) && ValidColRow(nVCol, nVRow) &&
GetFormatTable()->IsNumberFormat( sValStr, nFIndex, fTargetVal ) )
{
ScAddress aFormulaAdr( nFCol, nFRow, nFTab );
pFormula = GetFormulaCell( aFormulaAdr );
}
}
if (pFormula)
{
bool bDoneIteration = false ;
const ScAddress aValueAdr(nVCol, nVRow, nVTab);
const ScRange aVRange(aValueAdr, aValueAdr); // for SetDirty
const sal_uInt16 nMaxIter = 100 ;
const double fEps = 1 E-10 ;
const double fDelta = 1 E-6 ;
double fXPrev = GetValue(aValueAdr);
double fBestX = fXPrev;
// Original value to be restored later if necessary
const ScCellValue aSaveVal(GetRefCellValue(aValueAdr));
const bool changeCellType = aSaveVal.getType() != CELLTYPE_VALUE;
if (changeCellType)
SetValue(aValueAdr, fXPrev);
double * pVCell = GetValueCell(aValueAdr);
pFormula->Interpret();
bool bError = ( pFormula->GetErrCode() != FormulaError::NONE );
// bError always corresponds with fF
double fFPrev = pFormula->GetValue() - fTargetVal;
double fBestF = fabs( fFPrev );
if ( fBestF < fDelta )
bDoneIteration = true ;
double fX = fXPrev + fEps;
double fF = fFPrev;
double fSlope;
sal_uInt16 nIter = 0 ;
bool bHorMoveError = false ;
// Conform Regula Falsi Method
while ( !bDoneIteration && ( nIter++ < nMaxIter ) )
{
*pVCell = fX;
SetDirty( aVRange, false );
pFormula->Interpret();
bError = ( pFormula->GetErrCode() != FormulaError::NONE );
fF = pFormula->GetValue() - fTargetVal;
if ( fF == fFPrev && !bError )
{
// HORIZONTAL SEARCH: Keep moving x in both directions until the f(x)
// becomes different from the previous f(x). This routine is needed
// when a given function is discrete, in which case the resulting slope
// may become zero which ultimately causes the goal seek operation
// to fail. #i28955#
sal_uInt16 nHorIter = 0 ;
const double fHorStepAngle = 5 .0 ;
const double fHorMaxAngle = 80 .0 ;
int const nHorMaxIter = static_cast <int >( fHorMaxAngle / fHorStepAngle );
bool bDoneHorMove = false ;
while ( !bDoneHorMove && !bHorMoveError && nHorIter++ < nHorMaxIter )
{
double fHorAngle = fHorStepAngle * static_cast <double >( nHorIter );
double fHorTangent = std::tan(basegfx::deg2rad(fHorAngle));
sal_uInt16 nIdx = 0 ;
while ( nIdx++ < 2 && !bDoneHorMove )
{
double fHorX;
if ( nIdx == 1 )
fHorX = fX + fabs( fF ) * fHorTangent;
else
fHorX = fX - fabs( fF ) * fHorTangent;
*pVCell = fHorX;
SetDirty( aVRange, false );
pFormula->Interpret();
bHorMoveError = ( pFormula->GetErrCode() != FormulaError::NONE );
if ( bHorMoveError )
break ;
fF = pFormula->GetValue() - fTargetVal;
if ( fF != fFPrev )
{
fX = fHorX;
bDoneHorMove = true ;
}
}
}
if ( !bDoneHorMove )
bHorMoveError = true ;
}
if ( bError )
{
// move closer to last valid value (fXPrev), keep fXPrev & fFPrev
double fDiff = ( fXPrev - fX ) / 2 ;
if ( fabs( fDiff ) < fEps )
fDiff = ( fDiff < 0 .0 ? - fEps : fEps );
fX += fDiff;
}
else if ( bHorMoveError )
break ;
else if ( fabs(fF) < fDelta )
{
// converged to root
fBestX = fX;
bDoneIteration = true ;
}
else
{
if ( fabs(fF) + fDelta < fBestF )
{
fBestX = fX;
fBestF = fabs( fF );
}
if ( ( fXPrev - fX ) != 0 )
{
fSlope = ( fFPrev - fF ) / ( fXPrev - fX );
if ( fabs( fSlope ) < fEps )
fSlope = fSlope < 0 .0 ? -fEps : fEps;
}
else
fSlope = fEps;
fXPrev = fX;
fFPrev = fF;
fX = fX - ( fF / fSlope );
}
}
// Try a nice rounded input value if possible.
const double fNiceDelta = ( bDoneIteration && fabs( fBestX ) >= 1 e-3 ? 1 e-3 : fDelta );
nX = ::rtl::math::approxFloor( ( fBestX / fNiceDelta ) + 0 .5 ) * fNiceDelta;
if ( bDoneIteration )
{
*pVCell = nX;
SetDirty( aVRange, false );
pFormula->Interpret();
if ( fabs( pFormula->GetValue() - fTargetVal ) > fabs( fF ) )
nX = fBestX;
bRet = true ;
}
else if ( bError || bHorMoveError )
{
nX = fBestX;
}
if (changeCellType)
aSaveVal.commit(*this , aValueAdr);
else
*pVCell = aSaveVal.getDouble();
SetDirty( aVRange, false );
pFormula->Interpret();
}
return bRet;
}
void ScDocument::InsertMatrixFormula(SCCOL nCol1, SCROW nRow1,
SCCOL nCol2, SCROW nRow2,
const ScMarkData& rMark,
const OUString& rFormula,
const ScTokenArray* pArr,
const formula::FormulaGrammar::Grammar eGram )
{
PutInOrder(nCol1, nCol2);
PutInOrder(nRow1, nRow2);
nCol2 = std::min<SCCOL>(nCol2, MaxCol());
nRow2 = std::min<SCROW>(nRow2, MaxRow());
if (!rMark.GetSelectCount())
{
SAL_WARN("sc" , "ScDocument::InsertMatrixFormula: No table marked" );
return ;
}
if (comphelper::IsFuzzing())
{
// just too slow
if (nCol2 - nCol1 > 64 )
return ;
if (nRow2 - nRow1 > 64 )
return ;
}
assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2));
SCTAB nTab1 = *rMark.begin();
ScFormulaCell* pCell;
ScAddress aPos( nCol1, nRow1, nTab1 );
if (pArr)
pCell = new ScFormulaCell(*this , aPos, *pArr, eGram, ScMatrixMode::Formula);
else
pCell = new ScFormulaCell(*this , aPos, rFormula, eGram, ScMatrixMode::Formula);
pCell->SetMatColsRows( nCol2 - nCol1 + 1 , nRow2 - nRow1 + 1 );
SCTAB nMax = GetTableCount();
for (const auto & rTab : rMark)
{
if (rTab >= nMax)
break ;
if (!maTabs[rTab])
continue ;
if (rTab == nTab1)
{
pCell = maTabs[rTab]->SetFormulaCell(nCol1, nRow1, pCell);
if (!pCell) //NULL if nCol1/nRow1 is invalid, which it can't be here
break ;
}
else
maTabs[rTab]->SetFormulaCell(
nCol1, nRow1,
new ScFormulaCell(
*pCell, *this , ScAddress(nCol1, nRow1, rTab), ScCloneFlags::StartListening));
}
ScSingleRefData aRefData;
aRefData.InitFlags();
aRefData.SetRelCol(0 );
aRefData.SetRelRow(0 );
aRefData.SetRelTab(0 ); // 2D matrix, always same sheet
ScTokenArray aArr(*this ); // consists only of one single reference token.
formula::FormulaToken* t = aArr.AddMatrixSingleReference(aRefData);
for (const SCTAB& nTab : rMark)
{
if (nTab >= nMax)
break ;
ScTable* pTab = FetchTable(nTab);
if (!pTab)
continue ;
for (SCCOL nCol : GetWritableColumnsRange(nTab, nCol1, nCol2))
{
aRefData.SetRelCol(nCol1 - nCol);
for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
{
if (nCol == nCol1 && nRow == nRow1)
// Skip the base position.
continue ;
// Reference in each cell must point to the origin cell relative to the current cell.
aRefData.SetRelRow(nRow1 - nRow);
*t->GetSingleRef() = aRefData;
// Token array must be cloned so that each formula cell receives its own copy.
ScTokenArray aTokArr(aArr.CloneValue());
aPos = ScAddress(nCol, nRow, nTab);
pCell = new ScFormulaCell(*this , aPos, aTokArr, eGram, ScMatrixMode::Reference);
pTab->SetFormulaCell(nCol, nRow, pCell);
}
}
}
}
void ScDocument::InsertTableOp(const ScTabOpParam& rParam, // multiple (repeated?) operation
SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
const ScMarkData& rMark)
{
PutInOrder(nCol1, nCol2);
PutInOrder(nRow1, nRow2);
assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2));
SCTAB i, nTab1;
SCCOL j;
SCROW k;
i = 0 ;
bool bStop = false ;
SCTAB nMax = GetTableCount();
for (const auto & rTab : rMark)
{
if (rTab >= nMax)
break ;
if (maTabs[rTab])
{
i = rTab;
bStop = true ;
break ;
}
}
nTab1 = i;
if (!bStop)
{
OSL_FAIL("ScDocument::InsertTableOp: No table marked" );
return ;
}
ScRefAddress aRef;
OUStringBuffer aForString("="
+ ScCompiler::GetNativeSymbol(ocTableOp)
+ ScCompiler::GetNativeSymbol( ocOpen));
const OUString& sSep = ScCompiler::GetNativeSymbol( ocSep);
if (rParam.meMode == ScTabOpParam::Column) // column only
{
aRef.Set( rParam.aRefFormulaCell.GetAddress(), true , false , false );
aForString.append(aRef.GetRefString(*this , nTab1)
+ sSep
+ rParam.aRefColCell.GetRefString(*this , nTab1)
+ sSep);
aRef.Set( nCol1, nRow1, nTab1, false , true , true );
aForString.append(aRef.GetRefString(*this , nTab1));
nCol1++;
nCol2 = std::min( nCol2, static_cast <SCCOL>(rParam.aRefFormulaEnd.Col() -
rParam.aRefFormulaCell.Col() + nCol1 + 1 ));
}
else if (rParam.meMode == ScTabOpParam::Row) // row only
{
aRef.Set( rParam.aRefFormulaCell.GetAddress(), false , true , false );
aForString.append(aRef.GetRefString(*this , nTab1)
+ sSep
+ rParam.aRefRowCell.GetRefString(*this , nTab1)
+ sSep);
aRef.Set( nCol1, nRow1, nTab1, true , false , true );
aForString.append(aRef.GetRefString(*this , nTab1));
nRow1++;
nRow2 = std::min( nRow2, static_cast <SCROW>(rParam.aRefFormulaEnd.Row() -
rParam.aRefFormulaCell.Row() + nRow1 + 1 ));
}
else // both
{
aForString.append(rParam.aRefFormulaCell.GetRefString(*this , nTab1)
+ sSep
+ rParam.aRefColCell.GetRefString(*this , nTab1)
+ sSep);
aRef.Set( nCol1, nRow1 + 1 , nTab1, false , true , true );
aForString.append(aRef.GetRefString(*this , nTab1)
+ sSep
+ rParam.aRefRowCell.GetRefString(*this , nTab1)
+ sSep);
aRef.Set( nCol1 + 1 , nRow1, nTab1, true , false , true );
aForString.append(aRef.GetRefString(*this , nTab1));
nCol1++; nRow1++;
}
aForString.append(ScCompiler::GetNativeSymbol( ocClose ));
ScFormulaCell aRefCell( *this , ScAddress( nCol1, nRow1, nTab1 ), aForString.makeStringAndClear(),
formula::FormulaGrammar::GRAM_NATIVE, ScMatrixMode::NONE );
for ( j = nCol1; j <= nCol2; j++ )
{
for ( k = nRow1; k <= nRow2; k++ )
{
for (const auto & rTab : rMark)
{
if (rTab >= nMax)
break ;
if ( maTabs[rTab] )
maTabs[rTab]->SetFormulaCell(
j, k, new ScFormulaCell(aRefCell, *this , ScAddress(j, k, rTab), ScCloneFlags::StartListening));
}
}
}
}
namespace {
bool setCacheTableReferenced(const ScDocument& rDoc, formula::FormulaToken& rToken, ScExternalRefManager& rRefMgr, const ScAddress& rPos)
{
switch (rToken.GetType())
{
case svExternalSingleRef:
return rRefMgr.setCacheTableReferenced(
rToken.GetIndex(), rToken.GetString().getString(), 1 );
case svExternalDoubleRef:
{
const ScComplexRefData& rRef = *rToken.GetDoubleRef();
ScRange aAbs = rRef.toAbs(rDoc, rPos);
size_t nSheets = aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1 ;
return rRefMgr.setCacheTableReferenced(
rToken.GetIndex(), rToken.GetString().getString(), nSheets);
}
case svExternalName:
/* TODO: external names aren't supported yet, but would
* have to be marked as well , if so . Mechanism would be
* different. */
OSL_FAIL("ScDocument::MarkUsedExternalReferences: implement the svExternalName case!" );
break ;
default :
break ;
}
return false ;
}
}
bool ScDocument::MarkUsedExternalReferences( const ScTokenArray& rArr, const ScAddress& rPos )
{
if (!rArr.GetLen())
return false ;
ScExternalRefManager* pRefMgr = nullptr;
formula::FormulaTokenArrayPlainIterator aIter( rArr );
bool bAllMarked = false ;
while (!bAllMarked)
{
formula::FormulaToken* t = aIter.GetNextReferenceOrName();
if (!t)
break ;
if (t->IsExternalRef())
{
if (!pRefMgr)
pRefMgr = GetExternalRefManager();
bAllMarked = setCacheTableReferenced(*this , *t, *pRefMgr, rPos);
}
else if (t->GetType() == svIndex)
{
// this is a named range. Check if the range contains an external
// reference.
ScRangeData* pRangeData = GetRangeName()->findByIndex(t->GetIndex());
if (!pRangeData)
continue ;
ScTokenArray* pArray = pRangeData->GetCode();
formula::FormulaTokenArrayPlainIterator aArrayIter(*pArray);
for (t = aArrayIter.First(); t; t = aArrayIter.Next())
{
if (!t->IsExternalRef())
continue ;
if (!pRefMgr)
pRefMgr = GetExternalRefManager();
bAllMarked = setCacheTableReferenced(*this , *t, *pRefMgr, rPos);
}
}
}
return bAllMarked;
}
bool ScDocument::GetNextSpellingCell(SCCOL& nCol, SCROW& nRow, SCTAB nTab,
bool bInSel, const ScMarkData& rMark) const
{
if (const ScTable* pTable = FetchTable(nTab))
return pTable->GetNextSpellingCell( nCol, nRow, bInSel, rMark );
return false ;
}
bool ScDocument::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, SCTAB nTab,
const ScMarkData& rMark )
{
if (ScTable* pTable = FetchTable(nTab))
return pTable->GetNextMarkedCell( rCol, rRow, rMark );
return false ;
}
void ScDocument::ReplaceStyle(const SvxSearchItem& rSearchItem,
SCCOL nCol, SCROW nRow, SCTAB nTab,
const ScMarkData& rMark)
{
if (ScTable* pTable = FetchTable(nTab))
pTable->ReplaceStyle(rSearchItem, nCol, nRow, rMark, true /*bIsUndoP*/);
}
void ScDocument::CompileDBFormula()
{
sc::CompileFormulaContext aCxt(*this );
for (auto & rxTab : maTabs)
{
if (rxTab)
rxTab->CompileDBFormula(aCxt);
}
}
void ScDocument::CompileColRowNameFormula()
{
sc::CompileFormulaContext aCxt(*this );
for (auto & rxTab : maTabs)
{
if (rxTab)
rxTab->CompileColRowNameFormula(aCxt);
}
}
void ScDocument::InvalidateTableArea()
{
for (auto & rxTab : maTabs)
{
if (!rxTab)
break ;
rxTab->InvalidateTableArea();
if ( rxTab->IsScenario() )
rxTab->InvalidateScenarioRanges();
}
}
sal_Int32 ScDocument::GetMaxStringLen( SCTAB nTab, SCCOL nCol,
SCROW nRowStart, SCROW nRowEnd, rtl_TextEncoding eCharSet ) const
{
if (const ScTable* pTable = FetchTable(nTab))
return pTable->GetMaxStringLen(nCol, nRowStart, nRowEnd, eCharSet);
return 0 ;
}
sal_Int32 ScDocument::GetMaxNumberStringLen( sal_uInt16& nPrecision, SCTAB nTab,
SCCOL nCol, SCROW nRowStart, SCROW nRowEnd ) const
{
if (const ScTable* pTable = FetchTable(nTab))
return pTable->GetMaxNumberStringLen(nPrecision, nCol, nRowStart, nRowEnd);
return 0 ;
}
bool ScDocument::GetSelectionFunction( ScSubTotalFunc eFunc,
const ScAddress& rCursor, const ScMarkData& rMark,
double & rResult )
{
ScFunctionData aData(eFunc);
ScMarkData aMark(rMark);
aMark.MarkToMulti();
if (!aMark.IsMultiMarked() && !aMark.IsCellMarked(rCursor.Col(), rCursor.Row()))
aMark.SetMarkArea(ScRange(rCursor));
SCTAB nMax = GetTableCount();
ScMarkData::const_iterator itr = aMark.begin(), itrEnd = aMark.end();
for (; itr != itrEnd && *itr < nMax && !aData.getError(); ++itr)
if (maTabs[*itr])
maTabs[*itr]->UpdateSelectionFunction(aData, aMark);
rResult = aData.getResult();
if (aData.getError())
rResult = 0 .0 ;
return !aData.getError();
}
double ScDocument::RoundValueAsShown( double fVal, sal_uInt32 nFormat, const ScInterpreterContext* pContext ) const
{
const SvNumberFormatter* pFormatter = pContext ? pContext->GetFormatTable() : GetFormatTable();
const SvNumberformat* pFormat = pFormatter->GetEntry( nFormat );
if (!pFormat)
return fVal;
SvNumFormatType nType = pFormat->GetMaskedType();
if (nType != SvNumFormatType::DATE && nType != SvNumFormatType::TIME && nType != SvNumFormatType::DATETIME )
{
// MSVC doesn't recognize all paths init nPrecision and wails about
// "potentially uninitialized local variable 'nPrecision' used"
// so init to some random sensible value preserving all decimals.
short nPrecision = 20 ;
bool bStdPrecision = ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 );
if (!bStdPrecision)
{
sal_uInt16 nIdx = pFormat->GetSubformatIndex( fVal );
nPrecision = static_cast <short >(pFormat->GetFormatPrecision( nIdx ));
switch ( nType )
{
case SvNumFormatType::PERCENT: // 0.41% == 0.0041
nPrecision += 2 ;
break ;
case SvNumFormatType::SCIENTIFIC: // 1.23e-3 == 0.00123
{
short nExp = 0 ;
if ( fVal > 0 .0 )
nExp = static_cast <short >(floor( log10( fVal ) ));
else if ( fVal < 0 .0 )
nExp = static_cast <short >(floor( log10( -fVal ) ));
nPrecision -= nExp;
short nInteger = static_cast <short >(pFormat->GetFormatIntegerDigits( nIdx ));
if ( nInteger > 1 ) // Engineering notation
{
short nIncrement = nExp % nInteger;
if ( nIncrement != 0 )
{
nPrecision += nIncrement;
if (nExp < 0 )
nPrecision += nInteger;
}
}
break ;
}
case SvNumFormatType::FRACTION: // get value of fraction representation
{
return pFormat->GetRoundFractionValue( fVal );
}
case SvNumFormatType::NUMBER:
case SvNumFormatType::CURRENCY:
{ // tdf#106253 Thousands divisors for format "0,"
const sal_uInt16 nTD = pFormat->GetThousandDivisorPrecision( nIdx );
if (nTD == SvNumberFormatter::UNLIMITED_PRECISION)
// Format contains General keyword, handled below.
bStdPrecision = true ;
else
nPrecision -= nTD;
break ;
}
default : break ;
}
}
if (bStdPrecision)
{
nPrecision = static_cast <short >(GetDocOptions().GetStdPrecision());
// #i115512# no rounding for automatic decimals
if (nPrecision == static_cast <short >(SvNumberFormatter::UNLIMITED_PRECISION))
return fVal;
}
return ::rtl::math::round( fVal, nPrecision );
}
else
return fVal;
}
// conditional formats and validation ranges
sal_uInt32 ScDocument::AddCondFormat( std::unique_ptr<ScConditionalFormat> pNew, SCTAB nTab )
{
if (!pNew)
return 0 ;
if (ScTable* pTable = FetchTable(nTab))
return pTable->AddCondFormat(std::move(pNew));
return 0 ;
}
sal_uInt32 ScDocument::AddValidationEntry( const ScValidationData& rNew )
{
if (rNew.IsEmpty())
return 0 ; // empty is always 0
if (!pValidationList)
{
ScMutationGuard aGuard(*this , ScMutationGuardFlags::CORE);
pValidationList.reset(new ScValidationDataList);
mnLastValidationListMax = 0 ;
}
sal_uInt32 nMax = 0 ;
if (IsImportingXLSX())
{
// During import, the search becomes an O(n^2) problem.
// Just ignore any duplicates we might find, which seems to be rare.
nMax = mnLastValidationListMax;
++mnLastValidationListMax;
}
else
{
for ( const auto & rxData : *pValidationList )
{
const ScValidationData* pData = rxData.get();
sal_uInt32 nKey = pData->GetKey();
if ( pData->EqualEntries( rNew ) )
{
return nKey;
}
if ( nKey > nMax )
nMax = nKey;
}
}
// might be called from ScPatternAttr::MigrateToDocument; thus clone (real copy)
sal_uInt32 nNewKey = nMax + 1 ;
std::unique_ptr<ScValidationData> pInsert(rNew.Clone(*this ));
pInsert->SetKey( nNewKey );
ScMutationGuard aGuard(*this , ScMutationGuardFlags::CORE);
pValidationList->InsertNew( std::move(pInsert) );
return nNewKey;
}
const SfxPoolItem* ScDocument::GetEffItem(
SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const
{
const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
if ( pPattern )
{
const SfxItemSet& rSet = pPattern->GetItemSet();
const ScCondFormatItem* pConditionalItem = nullptr;
if ( rSet.GetItemState( ATTR_CONDITIONAL, true , &pConditionalItem ) == SfxItemState::SET )
{
const ScCondFormatIndexes& rIndex = pConditionalItem->GetCondFormatData();
ScConditionalFormatList* pCondFormList = GetCondFormList( nTab );
if (!rIndex.empty() && pCondFormList)
{
for (const auto & rItem : rIndex)
{
const ScConditionalFormat* pForm = pCondFormList->GetFormat( rItem );
if ( pForm )
{
ScAddress aPos(nCol, nRow, nTab);
ScRefCellValue aCell(const_cast <ScDocument&>(*this ), aPos);
const OUString aStyle = pForm->GetCellStyle(aCell, aPos);
if (!aStyle.isEmpty())
{
SfxStyleSheetBase* pStyleSheet = mxPoolHelper->GetStylePool()->Find(
aStyle, SfxStyleFamily::Para );
const SfxPoolItem* pItem = nullptr;
if ( pStyleSheet && pStyleSheet->GetItemSet().GetItemState(
nWhich, true , &pItem ) == SfxItemState::SET )
return pItem;
}
}
}
}
}
return &rSet.Get( nWhich );
}
OSL_FAIL("no pattern" );
return nullptr;
}
const SfxItemSet* ScDocument::GetCondResult( SCCOL nCol, SCROW nRow, SCTAB nTab, ScRefCellValue* pCell ) const
{
ScConditionalFormatList* pFormatList = GetCondFormList(nTab);
if (!pFormatList)
return nullptr;
ScAddress aPos(nCol, nRow, nTab);
ScRefCellValue aCell;
if ( pCell == nullptr )
{
aCell.assign(const_cast <ScDocument&>(*this ), aPos);
pCell = &aCell;
}
const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
const ScCondFormatIndexes& rIndex =
pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData();
return GetCondResult(*pCell, aPos, *pFormatList, rIndex);
}
const SfxItemSet* ScDocument::GetCondResult(
const ScRefCellValue& rCell, const ScAddress& rPos, const ScConditionalFormatList& rList,
const ScCondFormatIndexes& rIndex ) const
{
for (const auto & rItem : rIndex)
{
const ScConditionalFormat* pForm = rList.GetFormat(rItem);
if (!pForm)
continue ;
const OUString aStyle = pForm->GetCellStyle(rCell, rPos);
if (!aStyle.isEmpty())
{
SfxStyleSheetBase* pStyleSheet =
mxPoolHelper->GetStylePool()->Find(aStyle, SfxStyleFamily::Para);
if (pStyleSheet)
return &pStyleSheet->GetItemSet();
// if style is not there, treat like no condition
}
}
return nullptr;
}
ScConditionalFormat* ScDocument::GetCondFormat(
SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
sal_uInt32 nIndex = 0 ;
const ScCondFormatIndexes& rCondFormats = GetAttr(nCol, nRow, nTab, ATTR_CONDITIONAL)->GetCondFormatData();
if (!rCondFormats.empty())
nIndex = rCondFormats[0 ];
if (nIndex)
{
ScConditionalFormatList* pCondFormList = GetCondFormList(nTab);
if (pCondFormList)
return pCondFormList->GetFormat( nIndex );
else
{
OSL_FAIL("pCondFormList is 0" );
}
}
return nullptr;
}
ScConditionalFormatList* ScDocument::GetCondFormList(SCTAB nTab) const
{
if (HasTable(nTab))
return maTabs[nTab]->GetCondFormList();
return nullptr;
}
void ScDocument::SetCondFormList( ScConditionalFormatList* pList, SCTAB nTab )
{
if (ScTable* pTable = FetchTable(nTab))
pTable->SetCondFormList(pList);
}
const ScValidationData* ScDocument::GetValidationEntry( sal_uInt32 nIndex ) const
{
if ( pValidationList )
return pValidationList->GetData( nIndex );
else
return nullptr;
}
void ScDocument::DeleteConditionalFormat(sal_uLong nOldIndex, SCTAB nTab)
{
if (ScTable* pTable = FetchTable(nTab))
pTable->DeleteConditionalFormat(nOldIndex);
}
bool ScDocument::HasDetectiveOperations() const
{
return pDetOpList && pDetOpList->Count();
}
void ScDocument::AddDetectiveOperation( const ScDetOpData& rData )
{
if (!pDetOpList)
pDetOpList.reset(new ScDetOpList);
pDetOpList->Append( rData );
}
void ScDocument::ClearDetectiveOperations()
{
pDetOpList.reset(); // deletes also the entries
}
void ScDocument::SetDetOpList(std::unique_ptr<ScDetOpList> pNew)
{
pDetOpList = std::move(pNew);
}
// Comparison of Documents
// Pfriemel-Factors
#define SC_DOCCOMP_MAXDIFF 256
#define SC_DOCCOMP_MINGOOD 128
#define SC_DOCCOMP_COLUMNS 10
#define SC_DOCCOMP_ROWS 100
sal_uInt16 ScDocument::RowDifferences( SCROW nThisRow, SCTAB nThisTab,
ScDocument& rOtherDoc, SCROW nOtherRow, SCTAB nOtherTab,
SCCOL nMaxCol, const SCCOLROW* pOtherCols )
{
sal_uLong nDif = 0 ;
sal_uLong nUsed = 0 ;
for (SCCOL nThisCol=0 ; nThisCol<=nMaxCol; nThisCol++)
{
SCCOL nOtherCol;
if ( pOtherCols )
nOtherCol = static_cast <SCCOL>(pOtherCols[nThisCol]);
else
nOtherCol = nThisCol;
if (ValidCol(nOtherCol)) // only compare columns that are common to both docs
{
ScRefCellValue aThisCell(*this , ScAddress(nThisCol, nThisRow, nThisTab));
ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab));
if (!aThisCell.equalsWithoutFormat(aOtherCell))
{
if (!aThisCell.isEmpty() && !aOtherCell.isEmpty())
nDif += 3 ;
else
nDif += 4 ; // content <-> empty counts more
}
if (!aThisCell.isEmpty() || !aOtherCell.isEmpty())
++nUsed;
}
}
if (nUsed > 0 )
return static_cast <sal_uInt16>((nDif*64 )/nUsed); // max.256 (SC_DOCCOMP_MAXDIFF)
OSL_ENSURE(!nDif,"Diff without Used" );
return 0 ;
}
sal_uInt16 ScDocument::ColDifferences( SCCOL nThisCol, SCTAB nThisTab,
ScDocument& rOtherDoc, SCCOL nOtherCol, SCTAB nOtherTab,
SCROW nMaxRow, const SCCOLROW* pOtherRows )
{
//TODO: optimize e.g. with iterator?
sal_uInt64 nDif = 0 ;
sal_uInt64 nUsed = 0 ;
for (SCROW nThisRow=0 ; nThisRow<=nMaxRow; nThisRow++)
{
SCROW nOtherRow;
if ( pOtherRows )
nOtherRow = pOtherRows[nThisRow];
else
nOtherRow = nThisRow;
if (ValidRow(nOtherRow)) // only compare rows that are common to both docs
{
ScRefCellValue aThisCell(*this , ScAddress(nThisCol, nThisRow, nThisTab));
ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab));
if (!aThisCell.equalsWithoutFormat(aOtherCell))
{
if (!aThisCell.isEmpty() && !aOtherCell.isEmpty())
nDif += 3 ;
else
nDif += 4 ; // content <-> empty counts more
}
if (!aThisCell.isEmpty() || !aOtherCell.isEmpty())
++nUsed;
}
}
if (nUsed > 0 )
return static_cast <sal_uInt16>((nDif*64 )/nUsed); // max.256
OSL_ENSURE(!nDif,"Diff without Used" );
return 0 ;
}
void ScDocument::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 )
{
// bColumns=true: rows are columns and vice versa
SCCOLROW nMaxCont; // continue by how much
SCCOLROW nMinGood; // what is a hit (incl.)
if ( bColumns )
{
nMaxCont = SC_DOCCOMP_COLUMNS; // 10 columns
nMinGood = SC_DOCCOMP_MINGOOD;
//TODO: additional pass with nMinGood = 0 ????
}
else
{
nMaxCont = SC_DOCCOMP_ROWS; // 100 rows
nMinGood = SC_DOCCOMP_MINGOOD;
}
bool bUseTotal = bColumns && !pTranslate; // only for the 1st pass
SCCOLROW nOtherRow = 0 ;
sal_uInt16 nComp;
SCCOLROW nThisRow;
bool bTotal = false ; // hold for several nThisRow
SCCOLROW nUnknown = 0 ;
for (nThisRow = 0 ; nThisRow <= nThisEndRow; nThisRow++)
{
SCCOLROW nTempOther = nOtherRow;
bool bFound = false ;
sal_uInt16 nBest = SC_DOCCOMP_MAXDIFF;
SCCOLROW nMax = std::min( nOtherEndRow, static_cast <SCCOLROW>(( nTempOther + nMaxCont + nUnknown )) );
for (SCCOLROW i=nTempOther; i<=nMax && nBest>0 ; i++) // stop at 0
{
if (bColumns)
nComp = ColDifferences( static_cast <SCCOL>(nThisRow), nThisTab, rOtherDoc, static_cast <SCCOL>(i), nOtherTab, nEndCol, pTranslate );
else
nComp = RowDifferences( nThisRow, nThisTab, rOtherDoc, i, nOtherTab, static_cast <SCCOL>(nEndCol), pTranslate );
if ( nComp < nBest && ( nComp <= nMinGood || bTotal ) )
{
nTempOther = i;
nBest = nComp;
bFound = true ;
}
if ( nComp < SC_DOCCOMP_MAXDIFF || bFound )
bTotal = false ;
else if ( i == nTempOther && bUseTotal )
bTotal = true ; // only at the very top
}
if ( bFound )
{
pOtherRows[nThisRow] = nTempOther;
nOtherRow = nTempOther + 1 ;
nUnknown = 0 ;
}
else
{
pOtherRows[nThisRow] = SCROW_MAX;
++nUnknown;
}
if (pProgress)
pProgress->SetStateOnPercent(nProAdd+static_cast <sal_uLong>(nThisRow));
}
// fill in blocks that don't match
SCROW nFillStart = 0 ;
SCROW nFillPos = 0 ;
bool bInFill = false ;
for (nThisRow = 0 ; nThisRow <= nThisEndRow+1 ; nThisRow++)
{
SCROW nThisOther = ( nThisRow <= nThisEndRow ) ? pOtherRows[nThisRow] : (nOtherEndRow+1 );
if ( ValidRow(nThisOther) )
{
if ( bInFill )
{
if ( nThisOther > nFillStart ) // is there something to distribute?
{
SCROW nDiff1 = nThisOther - nFillStart;
SCROW nDiff2 = nThisRow - nFillPos;
SCROW nMinDiff = std::min(nDiff1, nDiff2);
for (SCROW i=0 ; i<nMinDiff; i++)
pOtherRows[nFillPos+i] = nFillStart+i;
}
bInFill = false ;
}
nFillStart = nThisOther + 1 ;
nFillPos = nThisRow + 1 ;
}
else
bInFill = true ;
}
}
void ScDocument::CompareDocument( ScDocument& rOtherDoc )
{
if (!pChangeTrack)
return ;
SCTAB nThisCount = GetTableCount();
SCTAB nOtherCount = rOtherDoc.GetTableCount();
std::unique_ptr<SCTAB[]> pOtherTabs(new SCTAB[nThisCount]);
SCTAB nThisTab;
// compare tables with identical names
OUString aThisName;
OUString aOtherName;
for (nThisTab=0 ; nThisTab<nThisCount; nThisTab++)
{
SCTAB nOtherTab = SCTAB_MAX;
if (!IsScenario(nThisTab)) // skip scenarios
{
GetName( nThisTab, aThisName );
for (SCTAB nTemp=0 ; nTemp<nOtherCount && nOtherTab>MAXTAB; nTemp++)
if (!rOtherDoc.IsScenario(nTemp))
{
rOtherDoc.GetName( nTemp, aOtherName );
if ( aThisName == aOtherName )
nOtherTab = nTemp;
}
}
pOtherTabs[nThisTab] = nOtherTab;
}
// fill in, so that un-named tables don't get lost
SCTAB nFillStart = 0 ;
SCTAB nFillPos = 0 ;
bool bInFill = false ;
for (nThisTab = 0 ; nThisTab <= nThisCount; nThisTab++)
{
SCTAB nThisOther = ( nThisTab < nThisCount ) ? pOtherTabs[nThisTab] : nOtherCount;
if ( ValidTab(nThisOther) )
{
if ( bInFill )
{
if ( nThisOther > nFillStart ) // is there something to distribute?
{
SCTAB nDiff1 = nThisOther - nFillStart;
SCTAB nDiff2 = nThisTab - nFillPos;
SCTAB nMinDiff = std::min(nDiff1, nDiff2);
for (SCTAB i=0 ; i<nMinDiff; i++)
if ( !IsScenario(nFillPos+i) && !rOtherDoc.IsScenario(nFillStart+i) )
pOtherTabs[nFillPos+i] = nFillStart+i;
}
bInFill = false ;
}
nFillStart = nThisOther + 1 ;
nFillPos = nThisTab + 1 ;
}
else
bInFill = true ;
}
// compare tables in the original order
for (nThisTab=0 ; nThisTab<nThisCount; nThisTab++)
{
SCTAB nOtherTab = pOtherTabs[nThisTab];
if ( ValidTab(nOtherTab) )
{
SCCOL nThisEndCol = 0 ;
SCROW nThisEndRow = 0 ;
SCCOL nOtherEndCol = 0 ;
SCROW nOtherEndRow = 0 ;
GetCellArea( nThisTab, nThisEndCol, nThisEndRow );
rOtherDoc.GetCellArea( nOtherTab, nOtherEndCol, nOtherEndRow );
SCCOL nEndCol = std::max(nThisEndCol, nOtherEndCol);
SCROW nEndRow = std::max(nThisEndRow, nOtherEndRow);
SCCOL nThisCol;
SCROW nThisRow;
sal_uLong n1,n2; // for AppendDeleteRange
//TODO: one Progress over all tables ???
OUString aTabName;
GetName( nThisTab, aTabName );
OUString aTemplate = ScResId(STR_PROGRESS_COMPARING);
sal_Int32 nIndex = 0 ;
OUString aProText = o3tl::getToken(aTemplate, 0 , '#' , nIndex ) +
aTabName +
o3tl::getToken(aTemplate, 0 , '#' , nIndex );
ScProgress aProgress( GetDocumentShell(), aProText, 3 *nThisEndRow, true ); // 2x FindOrder, 1x here
tools::Long nProgressStart = 2 *nThisEndRow; // start for here
std::unique_ptr<SCCOLROW[]> pTempRows(new SCCOLROW[nThisEndRow+1 ]);
std::unique_ptr<SCCOLROW[]> pOtherRows(new SCCOLROW[nThisEndRow+1 ]);
std::unique_ptr<SCCOLROW[]> pOtherCols(new SCCOLROW[nThisEndCol+1 ]);
// find inserted/deleted columns/rows:
// Two attempts:
// 1) compare original rows (pTempRows)
// 2) compare original columns (pOtherCols)
// with this column order compare rows (pOtherRows)
//TODO: compare columns twice with different nMinGood ???
// 1
FindOrder( pTempRows.get(), nThisEndRow, nOtherEndRow, false ,
rOtherDoc, nThisTab, nOtherTab, nEndCol, nullptr, &aProgress, 0 );
// 2
FindOrder( pOtherCols.get(), nThisEndCol, nOtherEndCol, true ,
rOtherDoc, nThisTab, nOtherTab, nEndRow, nullptr, nullptr, 0 );
FindOrder( pOtherRows.get(), nThisEndRow, nOtherEndRow, false ,
rOtherDoc, nThisTab, nOtherTab, nThisEndCol,
pOtherCols.get(), &aProgress, nThisEndRow );
sal_uLong nMatch1 = 0 ; // pTempRows, no columns
for (nThisRow = 0 ; nThisRow<=nThisEndRow; nThisRow++)
if (ValidRow(pTempRows[nThisRow]))
nMatch1 += SC_DOCCOMP_MAXDIFF -
RowDifferences( nThisRow, nThisTab, rOtherDoc, pTempRows[nThisRow],
nOtherTab, nEndCol, nullptr );
sal_uLong nMatch2 = 0 ; // pOtherRows, pOtherCols
for (nThisRow = 0 ; nThisRow<=nThisEndRow; nThisRow++)
if (ValidRow(pOtherRows[nThisRow]))
nMatch2 += SC_DOCCOMP_MAXDIFF -
RowDifferences( nThisRow, nThisTab, rOtherDoc, pOtherRows[nThisRow],
nOtherTab, nThisEndCol, pOtherCols.get() );
if ( nMatch1 >= nMatch2 ) // without columns ?
{
// reset columns
for (nThisCol = 0 ; nThisCol<=nThisEndCol; nThisCol++)
pOtherCols[nThisCol] = nThisCol;
// swap row-arrays (they get both deleted anyway)
pTempRows.swap(pOtherRows);
}
else
{
// remains for pOtherCols, pOtherRows
}
// Generate Change-Actions
// 1) columns from the right
// 2) rows from below
// 3) single cells in normal order
// Actions for inserted/deleted columns
SCCOL nLastOtherCol = static_cast <SCCOL>(nOtherEndCol + 1 );
// nThisEndCol ... 0
for ( nThisCol = nThisEndCol+1 ; nThisCol > 0 ; )
{
--nThisCol;
SCCOL nOtherCol = static_cast <SCCOL>(pOtherCols[nThisCol]);
if ( ValidCol(nOtherCol) && nOtherCol+1 < nLastOtherCol )
{
// gap -> deleted
ScRange aDelRange( nOtherCol+1 , 0 , nOtherTab,
nLastOtherCol-1 , MaxRow(), nOtherTab );
pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
}
if ( nOtherCol > MaxCol() ) // inserted
{
// combine
if ( nThisCol == nThisEndCol || ValidCol(static_cast <SCCOL>(pOtherCols[nThisCol+1 ])) )
{
SCCOL nFirstNew = nThisCol;
while ( nFirstNew > 0 && pOtherCols[nFirstNew-1 ] > MaxCol() )
--nFirstNew;
SCCOL nDiff = nThisCol - nFirstNew;
ScRange aRange( nLastOtherCol, 0 , nOtherTab,
nLastOtherCol+nDiff, MaxRow(), nOtherTab );
pChangeTrack->AppendInsert( aRange );
}
}
else
nLastOtherCol = nOtherCol;
}
if ( nLastOtherCol > 0 ) // deleted at the very top
{
ScRange aDelRange( 0 , 0 , nOtherTab,
nLastOtherCol-1 , MaxRow(), nOtherTab );
pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
}
// Actions for inserted/deleted rows
SCROW nLastOtherRow = nOtherEndRow + 1 ;
// nThisEndRow ... 0
for ( nThisRow = nThisEndRow+1 ; nThisRow > 0 ; )
{
--nThisRow;
SCROW nOtherRow = pOtherRows[nThisRow];
if ( ValidRow(nOtherRow) && nOtherRow+1 < nLastOtherRow )
{
// gap -> deleted
ScRange aDelRange( 0 , nOtherRow+1 , nOtherTab,
MaxCol(), nLastOtherRow-1 , nOtherTab );
pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
}
if ( nOtherRow > MaxRow() ) // inserted
{
// combine
if ( nThisRow == nThisEndRow || ValidRow(pOtherRows[nThisRow+1 ]) )
{
SCROW nFirstNew = nThisRow;
while ( nFirstNew > 0 && pOtherRows[nFirstNew-1 ] > MaxRow() )
--nFirstNew;
SCROW nDiff = nThisRow - nFirstNew;
ScRange aRange( 0 , nLastOtherRow, nOtherTab,
MaxCol(), nLastOtherRow+nDiff, nOtherTab );
pChangeTrack->AppendInsert( aRange );
}
}
else
nLastOtherRow = nOtherRow;
}
if ( nLastOtherRow > 0 ) // deleted at the very top
{
ScRange aDelRange( 0 , 0 , nOtherTab,
MaxCol(), nLastOtherRow-1 , nOtherTab );
pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
}
// walk rows to find single cells
for (nThisRow = 0 ; nThisRow <= nThisEndRow; nThisRow++)
{
SCROW nOtherRow = pOtherRows[nThisRow];
for (nThisCol = 0 ; nThisCol <= nThisEndCol; nThisCol++)
{
SCCOL nOtherCol = static_cast <SCCOL>(pOtherCols[nThisCol]);
ScAddress aThisPos( nThisCol, nThisRow, nThisTab );
ScCellValue aThisCell;
aThisCell.assign(*this , aThisPos);
ScCellValue aOtherCell; // start empty
if ( ValidCol(nOtherCol) && ValidRow(nOtherRow) )
{
ScAddress aOtherPos( nOtherCol, nOtherRow, nOtherTab );
aOtherCell.assign(rOtherDoc, aOtherPos);
}
if (!aThisCell.equalsWithoutFormat(aOtherCell))
{
ScRange aRange( aThisPos );
ScChangeActionContent* pAction = new ScChangeActionContent( aRange );
pAction->SetOldValue(aOtherCell, &rOtherDoc, this );
pAction->SetNewValue(aThisCell, this );
pChangeTrack->Append( pAction );
}
}
aProgress.SetStateOnPercent(nProgressStart+nThisRow);
}
}
}
}
sal_Unicode ScDocument::GetSheetSeparator() const
{
const ScCompiler::Convention* pConv = ScCompiler::GetRefConvention(
FormulaGrammar::extractRefConvention( GetGrammar()));
assert(pConv);
return pConv ? pConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR) : '.' ;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Messung V0.5 in Prozent C=92 H=87 G=89
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-10-02)
¤
*© Formatika GbR, Deutschland