/* -*- 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 .
*/
staticbool lcl_IsLess( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, tools::Long nMeasure, bool bAscending )
{ // members can be NULL if used for rows
staticbool lcl_IsEqual( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, tools::Long nMeasure )
{ // members can be NULL if used for rows
// make the hide item to the largest order. if ( !pMember1->IsVisible() || !pMember2->IsVisible() ) return pMember1->IsVisible(); const ScDPDataMember* pDataMember1 = pMember1->GetDataRoot() ; const ScDPDataMember* pDataMember2 = pMember2->GetDataRoot(); // GetDataRoot can be NULL if there was no data. // IsVisible == false can happen after AutoShow. return lcl_IsLess( pDataMember1, pDataMember2, nMeasure, bAscending );
}
void ScDPInitState::RemoveMember()
{
OSL_ENSURE(!maMembers.empty(), "ScDPInitState::RemoveMember: Attempt to remove member while empty."); if (!maMembers.empty())
maMembers.pop_back();
}
ScDPRunningTotalState::ScDPRunningTotalState( ScDPResultMember* pColRoot, ScDPResultMember* pRowRoot ) :
pColResRoot(pColRoot), pRowResRoot(pRowRoot)
{ // These arrays should never be empty as the terminating value must be present at all times.
maColVisible.push_back(-1);
maColSorted.push_back(-1);
maRowVisible.push_back(-1);
maRowSorted.push_back(-1);
}
if ( eFunc != SUBTOTAL_FUNC_CNT2 ) // CNT2 counts everything, incl. strings and errors
{ if (rNext.meType == ScDPValue::Error)
{
nCount = -1; // -1 for error (not for CNT2) return;
} if (rNext.meType == ScDPValue::String) return; // ignore
}
++nCount; // for all functions
switch (eFunc)
{ case SUBTOTAL_FUNC_SUM: case SUBTOTAL_FUNC_AVE: if ( !SubTotal::SafePlus( fVal, rNext.mfValue ) )
nCount = -1; // -1 for error break; case SUBTOTAL_FUNC_PROD: if ( nCount == 1 ) // copy first value (fVal is initialized to 0)
fVal = rNext.mfValue; elseif ( !SubTotal::SafeMult( fVal, rNext.mfValue ) )
nCount = -1; // -1 for error break; case SUBTOTAL_FUNC_CNT: case SUBTOTAL_FUNC_CNT2: // nothing more than incrementing nCount break; case SUBTOTAL_FUNC_MAX: if ( nCount == 1 || rNext.mfValue > fVal )
fVal = rNext.mfValue; break; case SUBTOTAL_FUNC_MIN: if ( nCount == 1 || rNext.mfValue < fVal )
fVal = rNext.mfValue; break; case SUBTOTAL_FUNC_STD: case SUBTOTAL_FUNC_STDP: case SUBTOTAL_FUNC_VAR: case SUBTOTAL_FUNC_VARP:
maWelford.update( rNext.mfValue); break; case SUBTOTAL_FUNC_MED:
{ auto aIter = std::upper_bound(mSortedValues.begin(), mSortedValues.end(), rNext.mfValue); if (aIter == mSortedValues.end())
mSortedValues.push_back(rNext.mfValue); else
mSortedValues.insert(aIter, rNext.mfValue);
} break; default:
OSL_FAIL("invalid function");
}
}
void ScDPAggData::Calculate( ScSubTotalFunc eFunc, const ScDPSubTotalState& rSubState )
{ // calculate the original result // (without reference value, used as the basis for reference value calculation)
// called several times at the cross-section of several subtotals - don't calculate twice then if ( IsCalculated() ) return;
if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eColForce; if ( rSubState.eRowForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eRowForce;
if ( eFunc == SUBTOTAL_FUNC_NONE ) // this happens when there is no data dimension
{
nCount = SC_DPAGG_RESULT_EMPTY; // make sure there's a valid state for HasData etc. return;
}
// check the error conditions for the selected function
bool bError = false; switch (eFunc)
{ case SUBTOTAL_FUNC_SUM: case SUBTOTAL_FUNC_PROD: case SUBTOTAL_FUNC_CNT: case SUBTOTAL_FUNC_CNT2:
bError = ( nCount < 0 ); // only real errors break;
case SUBTOTAL_FUNC_AVE: case SUBTOTAL_FUNC_MED: case SUBTOTAL_FUNC_MAX: case SUBTOTAL_FUNC_MIN:
bError = ( nCount <= 0 ); // no data is an error break;
case SUBTOTAL_FUNC_STDP: case SUBTOTAL_FUNC_VARP:
bError = ( nCount <= 0 ); // no data is an error
assert(bError || nCount == static_cast<sal_Int64>(maWelford.getCount())); break;
case SUBTOTAL_FUNC_STD: case SUBTOTAL_FUNC_VAR:
bError = ( nCount < 2 ); // need at least 2 values
assert(bError || nCount == static_cast<sal_Int64>(maWelford.getCount())); break;
default:
OSL_FAIL("invalid function");
}
// calculate the selected function
double fResult = 0.0; if ( !bError )
{ switch (eFunc)
{ case SUBTOTAL_FUNC_MAX: case SUBTOTAL_FUNC_MIN: case SUBTOTAL_FUNC_SUM: case SUBTOTAL_FUNC_PROD: // different error conditions are handled above
fResult = fVal; break;
case SUBTOTAL_FUNC_CNT: case SUBTOTAL_FUNC_CNT2:
fResult = nCount; break;
case SUBTOTAL_FUNC_AVE: if ( nCount > 0 )
fResult = fVal / static_cast<double>(nCount); break;
case SUBTOTAL_FUNC_STD: if ( nCount >= 2 )
{
fResult = maWelford.getVarianceSample(); if (fResult < 0.0)
bError = true; else
fResult = sqrt( fResult);
} break; case SUBTOTAL_FUNC_VAR: if ( nCount >= 2 )
fResult = maWelford.getVarianceSample(); break; case SUBTOTAL_FUNC_STDP: if ( nCount > 0 )
{
fResult = maWelford.getVariancePopulation(); if (fResult < 0.0)
bError = true; else
fResult = sqrt( fResult);
} break; case SUBTOTAL_FUNC_VARP: if ( nCount > 0 )
fResult = maWelford.getVariancePopulation(); break; case SUBTOTAL_FUNC_MED:
{
size_t nSize = mSortedValues.size(); if (nSize > 0)
{
assert(nSize == static_cast<size_t>(nCount)); if ((nSize % 2) == 1)
fResult = mSortedValues[nSize / 2]; else
fResult = (mSortedValues[nSize / 2 - 1] + mSortedValues[nSize / 2]) / 2.0;
}
} break; default:
OSL_FAIL("invalid function");
}
}
bool bEmpty = ( nCount == 0 ); // no data
// store the result // Empty is checked first, so empty results are shown empty even for "average" etc. // If these results should be treated as errors in reference value calculations, // a separate state value (EMPTY_ERROR) is needed. // Now, for compatibility, empty "average" results are counted as 0.
double ScDPAggData::GetAuxiliary() const
{ // after Calculate, fAux is used as auxiliary value for running totals and reference values
assert( IsCalculated() && "ScDPAggData not calculated" );
return fAux;
}
void ScDPAggData::SetAuxiliary( double fNew )
{ // after Calculate, fAux is used as auxiliary value for running totals and reference values
assert( IsCalculated() && "ScDPAggData not calculated" );
fAux = fNew;
}
ScDPAggData* ScDPAggData::GetChild()
{ if (!pChild)
pChild.reset( new ScDPAggData ); return pChild.get();
}
// subtotal settings are ignored - column/row totals exist once per measure
for ( tools::Long nPos=0; nPos<nSkip; nPos++ )
pAgg = pAgg->GetChild(); // column total is constructed empty - children need to be created
if ( !pAgg->IsCalculated() )
{ // for first use, simulate an empty calculation
ScDPSubTotalState aEmptyState;
pAgg->Calculate( SUBTOTAL_FUNC_SUM, aEmptyState );
}
static ScSubTotalFunc lcl_GetForceFunc( const ScDPLevel* pLevel, tools::Long nFuncNo )
{
ScSubTotalFunc eRet = SUBTOTAL_FUNC_NONE; if ( pLevel )
{ //TODO: direct access via ScDPLevel
uno::Sequence<sal_Int16> aSeq = pLevel->getSubTotals();
tools::Long nSequence = aSeq.getLength(); if ( nSequence && aSeq[0] != sheet::GeneralFunction2::AUTO )
{ // For manual subtotals, "automatic" is added as first function. // ScDPResultMember::GetSubTotalCount adds to the count, here NONE has to be // returned as the first function then.
--nFuncNo; // keep NONE for first (check below), move the other entries
}
void ScDPResultData::SetMeasureData(
std::vector<ScSubTotalFunc>& rFunctions, std::vector<sheet::DataPilotFieldReference>& rRefs,
std::vector<sheet::DataPilotFieldOrientation>& rRefOrient, std::vector<OUString>& rNames )
{ // We need to have at least one measure data at all times.
maMeasureFuncs.swap(rFunctions); if (maMeasureFuncs.empty())
maMeasureFuncs.push_back(SUBTOTAL_FUNC_NONE);
maMeasureRefs.swap(rRefs); if (maMeasureRefs.empty())
maMeasureRefs.emplace_back(); // default ctor is ok.
maMeasureRefOrients.swap(rRefOrient); if (maMeasureRefOrients.empty())
maMeasureRefOrients.push_back(sheet::DataPilotFieldOrientation_HIDDEN);
maMeasureNames.swap(rNames); if (maMeasureNames.empty())
maMeasureNames.push_back(ScResId(STR_EMPTYDATA));
}
OUString ScDPResultData::GetMeasureString(tools::Long nMeasure, bool bForce, ScSubTotalFunc eForceFunc, bool& rbTotalResult) const
{ // with bForce==true, return function instead of "result" for single measure // with eForceFunc != SUBTOTAL_FUNC_NONE, always use eForceFunc
rbTotalResult = false; if ( nMeasure < 0 || (maMeasureFuncs.size() == 1 && !bForce && eForceFunc == SUBTOTAL_FUNC_NONE) )
{ // for user-specified subtotal function with all measures, // display only function name
assert(unsigned(eForceFunc) < SAL_N_ELEMENTS(aFuncStrIds)); if ( eForceFunc != SUBTOTAL_FUNC_NONE ) return ScResId(aFuncStrIds[eForceFunc]);
if (nDim >= static_cast<tools::Long>(maDimMembers.size()))
maDimMembers.resize(nDim+1);
std::unique_ptr<ResultMembers> pResultMembers(new ResultMembers()); // global order is used to initialize aMembers, so it doesn't have to be looked at later const ScMemberSortOrder& rGlobalOrder = pLevel->GetGlobalOrder();
ScDPMembers* pMembers = pLevel->GetMembersObject();
tools::Long nMembCount = pMembers->getCount(); for (tools::Long i = 0; i < nMembCount; ++i)
{
tools::Long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i];
ScDPMember* pMember = pMembers->getByIndex(nSorted); if (!pResultMembers->FindMember(pMember->GetItemDataId()))
{
ScDPParentDimData aNew(i, pDim, pLevel, pMember);
pResultMembers->InsertMember(aNew);
}
}
void ScDPResultMember::InitFrom( const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLev,
size_t nPos, ScDPInitState& rInitState , bool bInitChild )
{ // with LateInit, initialize only those members that have data if ( pResultData->IsLateInit() ) return;
bInitialized = true;
if (nPos >= ppDim.size()) return;
// skip child dimension if details are not shown if ( GetDPMember() && !GetDPMember()->getShowDetails() )
{ // Show DataLayout dimension
nMemberStep = 1; while ( nPos < ppDim.size() )
{ if ( ppDim[nPos]->getIsDataLayoutDimension() )
{ if ( !pChildDimension )
pChildDimension.reset( new ScDPResultDimension( pResultData ) );
pChildDimension->InitFrom( ppDim, ppLev, nPos, rInitState , false ); return;
} else
{ //find next dim
nPos ++;
nMemberStep ++;
}
}
bHasHiddenDetails = true; // only if there is a next dimension return;
}
if ( bInitChild )
{
pChildDimension.reset( new ScDPResultDimension( pResultData ) );
pChildDimension->InitFrom(ppDim, ppLev, nPos, rInitState);
}
}
void ScDPResultMember::LateInitFrom(
LateInitParams& rParams, const vector<SCROW>& pItemData, size_t nPos, ScDPInitState& rInitState)
{ // without LateInit, everything has already been initialized if ( !pResultData->IsLateInit() ) return;
bInitialized = true;
if ( rParams.IsEnd( nPos ) /*nPos >= ppDim.size()*/) // No next dimension. Bail out. return;
// skip child dimension if details are not shown if ( GetDPMember() && !GetDPMember()->getShowDetails() )
{ // Show DataLayout dimension
nMemberStep = 1; while ( !rParams.IsEnd( nPos ) )
{ if ( rParams.GetDim( nPos )->getIsDataLayoutDimension() )
{ if ( !pChildDimension )
pChildDimension.reset( new ScDPResultDimension( pResultData ) );
// #i111462# reset InitChild flag only for this child dimension's LateInitFrom call, // not for following members of parent dimensions bool bWasInitChild = rParams.GetInitChild();
rParams.SetInitChild( false );
pChildDimension->LateInitFrom( rParams, pItemData, nPos, rInitState );
rParams.SetInitChild( bWasInitChild ); return;
} else
{ //find next dim
nPos ++;
nMemberStep ++;
}
}
bHasHiddenDetails = true; // only if there is a next dimension return;
}
// LateInitFrom is called several times... if ( rParams.GetInitChild() )
{ if ( !pChildDimension )
pChildDimension.reset( new ScDPResultDimension( pResultData ) );
pChildDimension->LateInitFrom( rParams, pItemData, nPos, rInitState );
}
}
bool ScDPResultMember::IsSubTotalInTitle(tools::Long nMeasure) const
{ bool bRet = false; if ( pChildDimension && /*pParentLevel*/GetParentLevel() && /*pParentLevel*/GetParentLevel()->IsOutlineLayout() && /*pParentLevel*/GetParentLevel()->IsSubtotalsAtTop() )
{
tools::Long nUserSubStart;
tools::Long nSubTotals = GetSubTotalCount( &nUserSubStart );
nSubTotals -= nUserSubStart; // visible count if ( nSubTotals )
{ if ( nMeasure == SC_DPMEASURE_ALL )
nSubTotals *= pResultData->GetMeasureCount(); // number of subtotals that will be inserted
// only a single subtotal row will be shown in the outline title row if ( nSubTotals == 1 )
bRet = true;
}
} return bRet;
}
if ( pChildDimension )
{ // outline layout takes up an extra row for the title only if subtotals aren't shown in that row if ( pParentLevel && pParentLevel->IsOutlineLayout() && !IsSubTotalInTitle( nMeasure ) )
++nExtraSpace;
bool ScDPResultMember::IsValid() const
{ // non-Valid members are left out of calculation
// was member set no invisible at the DataPilotSource? const ScDPMember* pMemberDesc = GetDPMember(); if ( pMemberDesc && !pMemberDesc->isVisible() ) returnfalse;
if ( bForceSubTotal ) // set if needed for root members return 1; // grand total is always "automatic" elseif ( pParentLevel )
{ //TODO: direct access via ScDPLevel
uno::Sequence<sal_Int16> aSeq = pParentLevel->getSubTotals();
tools::Long nSequence = aSeq.getLength(); if ( nSequence && aSeq[0] != sheet::GeneralFunction2::AUTO )
{ // For manual subtotals, always add "automatic" as first function // (used for calculation, but not for display, needed for sorting, see lcl_GetForceFunc)
if (pChildDimension)
pChildDimension->ProcessData( aChildMembers, pDataDim, aDataMembers, aValues );
if ( !pDataRoot )
{
pDataRoot.reset( new ScDPDataMember( pResultData, nullptr ) ); if ( pDataDim )
pDataRoot->InitFrom( pDataDim ); // recursive
}
ScDPSubTotalState aSubState; // initial state
tools::Long nUserSubCount = GetSubTotalCount();
// Calculate at least automatic if no subtotals are selected, // show only own values if there's no child dimension (innermost). if ( !nUserSubCount || !pChildDimension )
nUserSubCount = 1;
const ScDPLevel* pParentLevel = GetParentLevel();
for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
{ // #i68338# if nUserSubCount is 1 (automatic only), don't set nRowSubTotalFunc if ( pChildDimension && nUserSubCount > 1 )
{
aSubState.nRowSubTotalFunc = nUserPos;
aSubState.eRowForce = lcl_GetForceFunc( pParentLevel, nUserPos );
}
/** * Parse subtotal string and replace all occurrences of '?' with the caption * string. Do ensure that escaped characters are not translated.
*/ static OUString lcl_parseSubtotalName(std::u16string_view rSubStr, std::u16string_view rCaption)
{
OUStringBuffer aNewStr;
sal_Int32 n = rSubStr.size(); bool bEscaped = false; for (sal_Int32 i = 0; i < n; ++i)
{
sal_Unicode c = rSubStr[i]; if (!bEscaped && c == '\\')
{
bEscaped = true; continue;
}
if (!bEscaped && c == '?')
aNewStr.append(rCaption); else
aNewStr.append(c);
bEscaped = false;
} return aNewStr.makeStringAndClear();
}
void ScDPResultMember::FillMemberResults(
uno::Sequence<sheet::MemberResult>* pSequences, tools::Long& rPos, tools::Long nMeasure, bool bRoot, const OUString* pMemberName, const OUString* pMemberCaption )
{ // IsVisible() test is in ScDPResultDimension::FillMemberResults // (not on data layout dimension)
if (!pSequences->hasElements()) // empty sequence. Bail out. return;
bool bIsNumeric = false; double fValue = std::numeric_limits<double>::quiet_NaN();
OUString aName; if ( pMemberName ) // if pMemberName != NULL, use instead of real member name
{
aName = *pMemberName;
} else
{
ScDPItemData aItemData(FillItemData()); if (aParentDimData.mpParentDim)
{
tools::Long nDim = aParentDimData.mpParentDim->GetDimension();
aName = pResultData->GetSource().GetData()->GetFormattedString(nDim, aItemData, false);
} else
{
tools::Long nDim = -1; const ScDPMember* pMem = GetDPMember(); if (pMem)
nDim = pMem->GetDim();
aName = pResultData->GetSource().GetData()->GetFormattedString(nDim, aItemData, false);
}
ScDPItemData::Type eType = aItemData.GetType();
bIsNumeric = eType == ScDPItemData::Value || eType == ScDPItemData::GroupValue; // IsValue() is not identical to bIsNumeric, i.e. // ScDPItemData::GroupValue is excluded and not stored in the double, // so even if the item is numeric the Value may be NaN. if (aItemData.IsValue())
fValue = aItemData.GetValue();
}
const ScDPDimension* pParentDim = GetParentDim(); if ( bIsNumeric && pParentDim && pResultData->IsNumOrDateGroup( pParentDim->GetDimension() ) )
{ // Numeric group dimensions use numeric entries for proper sorting, // but the group titles must be output as text.
bIsNumeric = false;
}
OUString aCaption = aName; const ScDPMember* pMemberDesc = GetDPMember(); if (pMemberDesc)
{ const std::optional<OUString> & pLayoutName = pMemberDesc->GetLayoutName(); if (pLayoutName)
{
aCaption = *pLayoutName;
bIsNumeric = false; // layout name is always non-numeric.
}
}
if ( pMemberCaption ) // use pMemberCaption if != NULL
aCaption = *pMemberCaption; if (aCaption.isEmpty())
aCaption = ScResId(STR_EMPTYDATA);
if (bIsNumeric)
pArray[rPos].Flags |= sheet::MemberResultFlags::NUMERIC; else
pArray[rPos].Flags &= ~sheet::MemberResultFlags::NUMERIC;
const ScDPLevel* pParentLevel = GetParentLevel(); if ( nSize && !bRoot ) // root is overwritten by first dimension
{
pArray[rPos].Name = aName;
pArray[rPos].Caption = aCaption;
pArray[rPos].Flags |= sheet::MemberResultFlags::HASMEMBER;
pArray[rPos].Value = fValue;
// set "continue" flag (removed for subtotals later) for (tools::Long i=1; i<nSize; i++)
{
pArray[rPos+i].Flags |= sheet::MemberResultFlags::CONTINUE; // tdf#113002 - add numeric flag to recurring data fields if (bIsNumeric)
pArray[rPos + i].Flags |= sheet::MemberResultFlags::NUMERIC;
}
// if the subtotals are shown at the top (title row) in outline layout, // no extra row for the subtotals is needed bool bSubTotalInTitle = IsSubTotalInTitle( nMeasure );
bool bHasChild = ( pChildDimension != nullptr ); if (bHasChild)
{ if ( bTitleLine ) // in tabular layout the title is on a separate row
++rPos; // -> fill child dimension one row below
if (bRoot) // same sequence for root member
pChildDimension->FillMemberResults( pSequences, rPos, nMeasure ); else
pChildDimension->FillMemberResults( pSequences + nMemberStep/*1*/, rPos, nMeasure );
if ( bTitleLine ) // title row is included in GetSize, so the following
--rPos; // positions are calculated with the normal values
}
if ( nMeasure == SC_DPMEASURE_ALL )
{ // data layout dimension is (direct/indirect) child of this. // data layout dimension must have name for all entries.
rPos += nExtraSpace; // add again (subtracted above)
}
void ScDPResultMember::FillDataResults( const ScDPResultMember* pRefMember,
ScDPResultFilterContext& rFilterCxt, uno::Sequence<uno::Sequence<sheet::DataResult> >& rSequence,
tools::Long nMeasure) const
{
std::unique_ptr<FilterStack> pFilterStack; const ScDPMember* pDPMember = GetDPMember(); if (pDPMember)
{ // Root result has no corresponding DP member. Only take the non-root results.
pFilterStack.reset(new FilterStack(rFilterCxt.maFilters));
pFilterStack->pushDimValue( GetDisplayName( false), GetDisplayName( true));
}
// IsVisible() test is in ScDPResultDimension::FillDataResults // (not on data layout dimension) const ScDPLevel* pParentLevel = GetParentLevel();
sal_Int32 nStartRow = rFilterCxt.mnRow;
bool bHasChild = ( pChildDimension != nullptr ); if (bHasChild)
{ if ( bTitleLine ) // in tabular layout the title is on a separate row
++rFilterCxt.mnRow; // -> fill child dimension one row below
sal_Int32 nOldRow = rFilterCxt.mnRow;
pChildDimension->FillDataResults(pRefMember, rFilterCxt, rSequence, nMeasure);
rFilterCxt.mnRow = nOldRow; // Revert to the original row before the call.
rFilterCxt.mnRow += GetSize( nMeasure );
if ( bTitleLine ) // title row is included in GetSize, so the following
--rFilterCxt.mnRow; // positions are calculated with the normal values
}
// Calculate at least automatic if no subtotals are selected, // show only own values if there's no child dimension (innermost). if ( !nUserSubCount || !bHasChild )
{
nUserSubCount = 1;
nUserSubStart = 0;
}
tools::Long nMemberMeasure = nMeasure;
tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure); if (bHasChild)
{
rFilterCxt.mnRow -= nSubSize * ( nUserSubCount - nUserSubStart ); // GetSize includes space for SubTotal
rFilterCxt.mnRow -= nExtraSpace; // GetSize includes the empty line
}
tools::Long nMoveSubTotal = 0; if ( bSubTotalInTitle )
{
nMoveSubTotal = rFilterCxt.mnRow - nStartRow; // force to first (title) row
rFilterCxt.mnRow = nStartRow;
}
if ( pDataRoot )
{
ScDPSubTotalState aSubState; // initial state
// add extra space again if subtracted from GetSize above, // add to own size if no children
rFilterCxt.mnRow += nExtraSpace;
rFilterCxt.mnRow += nMoveSubTotal;
}
void ScDPResultMember::UpdateDataResults( const ScDPResultMember* pRefMember, tools::Long nMeasure ) const
{ // IsVisible() test is in ScDPResultDimension::FillDataResults // (not on data layout dimension)
bool bHasChild = ( pChildDimension != nullptr );
tools::Long nUserSubCount = GetSubTotalCount();
// process subtotals even if not shown
// Calculate at least automatic if no subtotals are selected, // show only own values if there's no child dimension (innermost). if (!nUserSubCount || !bHasChild)
nUserSubCount = 1;
if (bHasChild) // child dimension must be processed last, so the column total is known
{
pChildDimension->UpdateDataResults( pRefMember, nMeasure );
}
}
void ScDPResultMember::SortMembers( ScDPResultMember* pRefMember )
{ bool bHasChild = ( pChildDimension != nullptr ); if (bHasChild)
pChildDimension->SortMembers( pRefMember ); // sorting is done at the dimension
if ( IsRoot() && pDataRoot )
{ // use the row root member to sort columns // sub total count is always 1
pDataRoot->SortMembers( pRefMember );
}
}
void ScDPResultMember::DoAutoShow( ScDPResultMember* pRefMember )
{ bool bHasChild = ( pChildDimension != nullptr ); if (bHasChild)
pChildDimension->DoAutoShow( pRefMember ); // sorting is done at the dimension
if ( IsRoot()&& pDataRoot )
{ // use the row root member to sort columns // sub total count is always 1
pDataRoot->DoAutoShow( pRefMember );
}
}
void ScDPResultMember::ResetResults()
{ if (pDataRoot)
pDataRoot->ResetResults();
if (pChildDimension)
pChildDimension->ResetResults();
}
void ScDPResultMember::UpdateRunningTotals( const ScDPResultMember* pRefMember, tools::Long nMeasure,
ScDPRunningTotalState& rRunning, ScDPRowTotals& rTotals ) const
{ // IsVisible() test is in ScDPResultDimension::FillDataResults // (not on data layout dimension)
rTotals.SetInColRoot( IsRoot() );
bool bHasChild = ( pChildDimension != nullptr );
tools::Long nUserSubCount = GetSubTotalCount(); //if ( nUserSubCount || !bHasChild )
{ // Calculate at least automatic if no subtotals are selected, // show only own values if there's no child dimension (innermost). if ( !nUserSubCount || !bHasChild )
nUserSubCount = 1;
if (bHasChild) // child dimension must be processed last, so the column total is known
{
pChildDimension->UpdateRunningTotals( pRefMember, nMeasure, rRunning, rTotals );
}
}
static tools::Long lcl_GetSubTotalPos( const ScDPSubTotalState& rSubState )
{ if ( rSubState.nColSubTotalFunc >= 0 && rSubState.nRowSubTotalFunc >= 0 &&
rSubState.nColSubTotalFunc != rSubState.nRowSubTotalFunc )
{ // #i68338# don't return the same index for different combinations (leading to repeated updates), // return a "don't use" value instead
void ScDPDataMember::UpdateValues( const vector<ScDPValue>& aValues, const ScDPSubTotalState& rSubState )
{ //TODO: find out how many and which subtotals are used
ScDPAggData* pAgg = &aAggregate;
tools::Long nSubPos = lcl_GetSubTotalPos(rSubState); if (nSubPos == SC_SUBTOTALPOS_SKIP) return; if (nSubPos > 0)
{
tools::Long nSkip = nSubPos * pResultData->GetMeasureCount(); for (tools::Long i=0; i<nSkip; i++)
pAgg = pAgg->GetChild(); // created if not there
}
void ScDPDataMember::ProcessData( const vector< SCROW >& aChildMembers, const vector<ScDPValue>& aValues, const ScDPSubTotalState& rSubState )
{ if ( pResultData->IsLateInit() && !pChildDimension && pResultMember && pResultMember->GetChildDimension() )
{ // if this DataMember doesn't have a child dimension because the ResultMember's // child dimension wasn't there yet during this DataMembers's creation, // create the child dimension now
InitFrom( pResultMember->GetChildDimension() );
}
// Calculate at least automatic if no subtotals are selected, // show only own values if there's no child dimension (innermost). if ( !nUserSubCount || !pChildDimension )
nUserSubCount = 1;
for ( tools::Long nPos=0; nPos<nSkip; nPos++ )
{
pAgg = pAgg->GetExistingChild(); if (!pAgg) return nullptr;
}
return pAgg;
}
void ScDPDataMember::FillDataRow( const ScDPResultMember* pRefMember, ScDPResultFilterContext& rFilterCxt,
uno::Sequence<sheet::DataResult>& rSequence, tools::Long nMeasure, bool bIsSubTotalRow, const ScDPSubTotalState& rSubState) const
{
std::unique_ptr<FilterStack> pFilterStack; if (pResultMember)
{ // Topmost data member (pResultMember=NULL) doesn't need to be handled // since its immediate parent result member is linked to the same // dimension member.
pFilterStack.reset(new FilterStack(rFilterCxt.maFilters));
pFilterStack->pushDimValue( pResultMember->GetDisplayName( false), pResultMember->GetDisplayName( true));
}
// leave space for children even if the DataMember hasn't been initialized // (pDataChild is null then, this happens when no values for it are in this row) bool bHasChild = ( pRefChild != nullptr );
if ( bHasChild )
{ if ( bTitleLine ) // in tabular layout the title is on a separate column
++rFilterCxt.mnCol; // -> fill child dimension one column below
if ( pDataChild )
{
tools::Long nOldCol = rFilterCxt.mnCol;
pDataChild->FillDataRow(pRefChild, rFilterCxt, rSequence, nMeasure, bIsSubTotalRow, rSubState);
rFilterCxt.mnCol = nOldCol; // Revert to the old column value before the call.
}
rFilterCxt.mnCol += static_cast<sal_uInt16>(pRefMember->GetSize( nMeasure ));
if ( bTitleLine ) // title column is included in GetSize, so the following
--rFilterCxt.mnCol; // positions are calculated with the normal values
}
// Calculate at least automatic if no subtotals are selected, // show only own values if there's no child dimension (innermost). if ( !nUserSubCount || !bHasChild )
{
nUserSubCount = 1;
nUserSubStart = 0;
}
// add extra space again if subtracted from GetSize above, // add to own size if no children
rFilterCxt.mnCol += nExtraSpace;
rFilterCxt.mnCol += nMoveSubTotal;
}
// Calculate must be called even if not visible (for use as reference value) const ScDPDataDimension* pDataChild = GetChildDimension(); const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
// leave space for children even if the DataMember hasn't been initialized // (pDataChild is null then, this happens when no values for it are in this row) bool bHasChild = ( pRefChild != nullptr );
// process subtotals even if not shown
tools::Long nUserSubCount = pRefMember->GetSubTotalCount();
// Calculate at least automatic if no subtotals are selected, // show only own values if there's no child dimension (innermost). if ( !nUserSubCount || !bHasChild )
nUserSubCount = 1;
// calculate the result first - for all members, regardless of reference value
pAggData->Calculate( eFunc, aLocalSubState );
if ( eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE ||
eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE ||
eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE )
{ // copy the result into auxiliary value, so differences can be // calculated in any order
pAggData->SetAuxiliary( pAggData->GetResult() );
} // column/row percentage/index is now in UpdateRunningTotals, so it doesn't disturb sorting
}
}
}
if ( bHasChild ) // child dimension must be processed last, so the row total is known
{ if ( pDataChild )
pDataChild->UpdateDataRow( pRefChild, nMeasure, bIsSubTotalRow, rSubState );
}
}
if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataDimension ???
{
ScDPDataDimension* pDataChild = GetChildDimension();
ScDPResultDimension* pRefChild = pRefMember->GetChildDimension(); if ( pRefChild && pDataChild )
pDataChild->SortMembers( pRefChild ); // sorting is done at the dimension
}
}
if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataDimension ???
{
ScDPDataDimension* pDataChild = GetChildDimension();
ScDPResultDimension* pRefChild = pRefMember->GetChildDimension(); if ( pRefChild && pDataChild )
pDataChild->DoAutoShow( pRefChild ); // sorting is done at the dimension
}
}
// leave space for children even if the DataMember hasn't been initialized // (pDataChild is null then, this happens when no values for it are in this row) bool bHasChild = ( pRefChild != nullptr );
tools::Long nUserSubCount = pRefMember->GetSubTotalCount();
{ // Calculate at least automatic if no subtotals are selected, // show only own values if there's no child dimension (innermost). if ( !nUserSubCount || !bHasChild )
nUserSubCount = 1;
bool bNoDetailsInRef = false; if ( pSelectDim && bRunningTotal )
{ // Running totals: // If details are hidden for this member in the reference dimension, // don't show or sum up the value. Otherwise, for following members, // the running totals of details and subtotals wouldn't match.
if ( bRelative )
{ // Difference/Percentage from previous/next: // If details are hidden for this member in the innermost column/row // dimension (the orientation of the reference dimension), show an // error value. // - If the no-details dimension is the reference dimension, its // members will be skipped when finding the previous/next member, // so there must be no results for its members. // - If the no-details dimension is outside of the reference dimension, // no calculation in the reference dimension is possible. // - Otherwise, the error isn't strictly necessary, but shown for // consistency.
bool bInnerNoDetails = bRefDimInCol ? HasHiddenDetails() :
( !bRefDimInRow || rRowParent.HasHiddenDetails() ); if ( bInnerNoDetails )
{
pSelectDim = nullptr;
bNoDetailsInRef = true; // show error, not empty
}
}
if ( !bRefDimInCol && !bRefDimInRow ) // invalid dimension specified
bNoDetailsInRef = true; // pSelectDim is then already NULL
// get the member for the reference item and do the calculation
if ( bRunningTotal )
{ // running total in (dimension) -> find first existing member
if ( pSelectMember )
{ // The running total is kept as the auxiliary value in // the first available member for the reference dimension. // Members are visited in final order, so each one's result // can be used and then modified.
ScDPDataMember* pSelectMember; if ( bRefDimInCol )
{
aRefItemPos.nBasePos = rColVisible[nColPos]; // without sort order applied
pSelectMember = ScDPResultDimension::GetColReferenceMember( pRefPos, pRefName,
nColPos, rRunning );
} else
{
aRefItemPos.nBasePos = rRowVisible[nRowPos]; // without sort order applied const sal_Int32* pRowSorted = rRowSorted.data(); const sal_Int32* pColSorted = rColSorted.data();
pRowSorted += nRowPos + 1; // including the reference dimension
pSelectMember = pSelectDim->GetRowReferenceMember(
pRefPos, pRefName, pRowSorted, pColSorted);
}
// difference or perc.difference is empty for the reference item itself if ( pSelectMember == this &&
eRefType != sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE )
{
pAggData->SetEmpty(true);
} elseif ( pSelectMember )
{ const ScDPAggData* pOtherAggData = pSelectMember->
GetConstAggData( nMemberMeasure, aLocalSubState );
OSL_ENSURE( pOtherAggData, "no agg data" ); if ( pOtherAggData )
{ // Reference member may be visited before or after this one, // so the auxiliary value is used for the original result.
if ( bHasChild ) // child dimension must be processed last, so the row total is known
{ if ( pDataChild )
pDataChild->UpdateRunningTotals( pRefChild, nMeasure,
bIsSubTotalRow, rSubState, rRunning, rTotals, rRowParent );
}
}
ScDPGroupCompare::ScDPGroupCompare( const ScDPResultData* pData, const ScDPInitState& rState, tools::Long nDimension ) :
pResultData( pData ),
rInitState( rState ),
nDimSource( nDimension )
{
bIsBase = pResultData->IsBaseForGroup( nDimSource );
nGroupBase = pResultData->GetGroupBase( nDimSource ); //TODO: get together in one call?
// if bIncludeAll is set, TestIncluded doesn't need to be called
bIncludeAll = !( bIsBase || nGroupBase >= 0 );
}
bool ScDPGroupCompare::TestIncluded( const ScDPMember& rMember )
{ bool bInclude = true; if ( bIsBase )
{ // need to check all previous groups //TODO: get array of groups (or indexes) before loop?
ScDPItemData aMemberData(rMember.FillItemData());
const std::vector<ScDPInitState::Member>& rMemStates = rInitState.GetMembers();
bInclude = std::all_of(rMemStates.begin(), rMemStates.end(),
[this, &aMemberData](const ScDPInitState::Member& rMem) { return (pResultData->GetGroupBase(rMem.mnSrcIndex) != nDimSource)
|| pResultData->IsInGroup(rMem.mnNameIndex, rMem.mnSrcIndex, aMemberData, nDimSource);
});
} elseif ( nGroupBase >= 0 )
{ // base isn't used in preceding fields // -> look for other groups using the same base
//TODO: get array of groups (or indexes) before loop?
ScDPItemData aMemberData(rMember.FillItemData()); const std::vector<ScDPInitState::Member>& rMemStates = rInitState.GetMembers();
bInclude = std::all_of(rMemStates.begin(), rMemStates.end(),
[this, &aMemberData](const ScDPInitState::Member& rMem) { // coverity[copy_paste_error : FALSE] - same base (hierarchy between // the two groups is irrelevant) return (pResultData->GetGroupBase(rMem.mnSrcIndex) != nGroupBase)
|| pResultData->HasCommonElement(rMem.mnNameIndex, rMem.mnSrcIndex, aMemberData, nDimSource);
});
}
MemberHash::const_iterator aRes = maMemberHash.find( iData ); if( aRes != maMemberHash.end()) { if ( aRes->second->IsNamedItem( iData ) ) return aRes->second;
OSL_FAIL("problem! hash result is not the same as IsNamedItem");
}
if (!pThisDim || !pThisLevel)
{
bInitialized = true; return;
}
bIsDataLayout = pThisDim->getIsDataLayoutDimension(); // member
aDimensionName = pThisDim->getName(); // member
// Check the autoshow setting. If it's enabled, store the settings. const sheet::DataPilotFieldAutoShowInfo& rAutoInfo = pThisLevel->GetAutoShow(); if ( rAutoInfo.IsEnabled )
{
bAutoShow = true;
bAutoTopItems = ( rAutoInfo.ShowItemsMode == sheet::DataPilotFieldShowItemsMode::FROM_TOP );
nAutoMeasure = pThisLevel->GetAutoMeasure();
nAutoCount = rAutoInfo.ItemCount;
}
// Check the sort info, and store the settings if appropriate. const sheet::DataPilotFieldSortInfo& rSortInfo = pThisLevel->GetSortInfo(); if ( rSortInfo.Mode == sheet::DataPilotFieldSortMode::DATA )
{
bSortByData = true;
bSortAscending = rSortInfo.IsAscending;
nSortMeasure = pThisLevel->GetSortMeasure();
}
// global order is used to initialize aMembers, so it doesn't have to be looked at later const ScMemberSortOrder& rGlobalOrder = pThisLevel->GetGlobalOrder();
// Now, go through all members and initialize them.
ScDPMembers* pMembers = pThisLevel->GetMembersObject();
tools::Long nMembCount = pMembers->getCount(); for ( tools::Long i=0; i<nMembCount; i++ )
{
tools::Long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i];
if ( !bInitialized )
{ // init some values // create all members at the first call (preserve order)
bIsDataLayout = pThisDim->getIsDataLayoutDimension();
aDimensionName = pThisDim->getName();
if (bNewAllMembers )
{ // global order is used to initialize aMembers, so it doesn't have to be looked at later if ( !bInitialized )
{ //init all members const ScMemberSortOrder& rGlobalOrder = pThisLevel->GetGlobalOrder();
ScDPMember* pMember = pMembers->getByIndex(nSorted); if ( aCompare.IsIncluded( *pMember ) )
{ // add all members
ScDPParentDimData aData( i, pThisDim, pThisLevel, pMember );
AddMember( aData );
}
}
bInitialized = true; // don't call again, even if no members were included
} // initialize only specific member (or all if "show empty" flag is set) if ( bLateInitAllMembers )
{
tools::Long nCount = maMemberArray.size(); for (tools::Long i=0; i<nCount; i++)
{
ScDPResultMember* pResultMember = maMemberArray[i].get();
// for data layout, call only once - sorting measure is always taken from settings
tools::Long nLoopCount = bIsDataLayout ? std::min<tools::Long>(1, nCount) : nCount; for (tools::Long i=0; i<nLoopCount; i++)
{
ScDPResultMember* pMember = maMemberArray[i].get(); if ( pMember->IsVisible() )
pMember->SortMembers( pRefMember );
}
}
// handle children first, before changing the visible state
// for data layout, call only once - sorting measure is always taken from settings
tools::Long nLoopCount = bIsDataLayout ? 1 : nCount; for (tools::Long i=0; i<nLoopCount; i++)
{
ScDPResultMember* pMember = maMemberArray[i].get(); if ( pMember->IsVisible() )
pMember->DoAutoShow( pRefMember );
}
if ( pMember->IsVisible() )
{ if ( bIsDataLayout )
rRunning.AddRowIndex( 0, 0 ); else
rRunning.AddRowIndex( i, nSorted );
pMember->UpdateRunningTotals( pRefMember, nMemberMeasure, rRunning, rTotals );
rRunning.RemoveRowIndex();
}
}
}
ScDPDataMember* ScDPResultDimension::GetRowReferenceMember( const ScDPRelativePos* pRelativePos, const OUString* pName, const sal_Int32* pRowIndexes, const sal_Int32* pColIndexes ) const
{ // get named, previous/next, or first member of this dimension (first existing if pRelativePos and pName are NULL)
OSL_ENSURE( pRelativePos == nullptr || pName == nullptr, "can't use position and name" );
ScDPDataMember* pColMember = nullptr;
bool bFirstExisting = ( pRelativePos == nullptr && pName == nullptr );
tools::Long nMemberCount = maMemberArray.size();
tools::Long nMemberIndex = 0; // unsorted
tools::Long nDirection = 1; // forward if no relative position is used if ( pRelativePos )
{
nDirection = pRelativePos->nDirection;
nMemberIndex = pRelativePos->nBasePos + nDirection; // bounds are handled below
OSL_ENSURE( nDirection == 1 || nDirection == -1, "Direction must be 1 or -1" );
} elseif ( pName )
{ // search for named member
if ( pRowMember && pRelativePos )
{ // Skip the member if it has hidden details // (because when looking for the details, it is skipped, too). // Also skip if the member is invisible because it has no data, // for consistent ordering. if ( pRowMember->HasHiddenDetails() || !pRowMember->IsVisible() )
pRowMember = nullptr;
}
if ( pRowMember )
{
pColMember = pRowMember->GetDataRoot();
// continue searching only if looking for first existing or relative position
bContinue = ( pColMember == nullptr && ( bFirstExisting || pRelativePos ) );
nMemberIndex += nDirection;
}
return pColMember;
}
ScDPDataMember* ScDPResultDimension::GetColReferenceMember( const ScDPRelativePos* pRelativePos, const OUString* pName,
sal_Int32 nRefDimPos, const ScDPRunningTotalState& rRunning )
{
OSL_ENSURE( pRelativePos == nullptr || pName == nullptr, "can't use position and name" );
if ( pColMember && pRelativePos )
{ // Skip the member if it has hidden details // (because when looking for the details, it is skipped, too). // Also skip if the member is invisible because it has no data, // for consistent ordering. if ( pColMember->HasHiddenDetails() || !pColMember->IsVisible() )
pColMember = nullptr;
}
// continue searching only if looking for first existing or relative position
bContinue = ( pColMember == nullptr && ( bFirstExisting || pRelativePos ) );
nMemberIndex += nDirection;
}
} else
pColMember = nullptr;
}
// Go through all result members under the given result dimension, and // create a new data member instance for each result member.
tools::Long nCount = pDim->GetMemberCount(); for (tools::Long i=0; i<nCount; i++)
{ const ScDPResultMember* pResMem = pDim->GetMember(i);
ScDPDataMember* pNew = new ScDPDataMember( pResultData, pResMem );
maMembers.emplace_back( pNew);
if ( !pResultData->IsLateInit() )
{ // with LateInit, pResMem hasn't necessarily been initialized yet, // so InitFrom for the new result member is called from its ProcessData method
// Calculate must be called even if the member is not visible (for use as reference value) const ScDPResultMember* pRefMember = pRefDim->GetMember(nMemberPos);
ScDPDataMember* pDataMember = maMembers[static_cast<sal_uInt16>(nMemberPos)].get();
pDataMember->UpdateDataRow( pRefMember, nMemberMeasure, bIsSubTotalRow, rSubState );
}
}
// for data layout, call only once - sorting measure is always taken from settings
tools::Long nLoopCount = bIsDataLayout ? 1 : nCount; for (tools::Long i=0; i<nLoopCount; i++)
{
ScDPResultMember* pRefMember = pRefDim->GetMember(i); if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataMember ???
{
ScDPDataMember* pDataMember = maMembers[static_cast<sal_uInt16>(i)].get();
pDataMember->SortMembers( pRefMember );
}
}
}
// for data layout, call only once - sorting measure is always taken from settings
tools::Long nLoopCount = bIsDataLayout ? 1 : nCount; for (tools::Long i=0; i<nLoopCount; i++)
{
ScDPResultMember* pRefMember = pRefDim->GetMember(i); if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataMember ???
{
ScDPDataMember* pDataMember = maMembers[i].get();
pDataMember->DoAutoShow( pRefMember );
}
}
if ( lcl_IsEqual( pDataMember1, pDataMember2, pRefDim->GetAutoMeasure() ) )
{
++nIncluded; // include more members if values are equal
bContinue = true;
}
}
}
// create all members at the first call (preserve order)
ResultMembers& rMembers = pResultData->GetDimResultMembers(nDimSource, pThisDim, pThisLevel);
ScDPGroupCompare aCompare( pResultData, rInitState, nDimSource ); // initialize only specific member (or all if "show empty" flag is set)
ScDPResultMember* pResultMember = nullptr; if ( bInitialized )
pResultMember = FindMember( nDataID ); else
bInitialized = true;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.