From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- sc/source/core/data/dptabres.cxx | 4117 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 4117 insertions(+) create mode 100644 sc/source/core/data/dptabres.cxx (limited to 'sc/source/core/data/dptabres.cxx') diff --git a/sc/source/core/data/dptabres.cxx b/sc/source/core/data/dptabres.cxx new file mode 100644 index 000000000..1037eac1c --- /dev/null +++ b/sc/source/core/data/dptabres.cxx @@ -0,0 +1,4117 @@ +/* -*- 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; +using ::std::vector; +using ::std::pair; +using ::com::sun::star::uno::Sequence; + +namespace { + +const TranslateId aFuncStrIds[] = // matching enum ScSubTotalFunc +{ + {}, // SUBTOTAL_FUNC_NONE + STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE + STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT + STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2 + STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX + STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN + STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD + STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD + STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP + STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM + STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR + STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VARP + STR_FUN_TEXT_MEDIAN, // SUBTOTAL_FUNC_MED + {} // SUBTOTAL_FUNC_SELECTION_COUNT - not used for pivot table +}; + +bool lcl_SearchMember( const std::vector>& list, SCROW nOrder, SCROW& rIndex) +{ + bool bFound = false; + SCROW nLo = 0; + SCROW nHi = list.size() - 1; + SCROW nIndex; + while (nLo <= nHi) + { + nIndex = (nLo + nHi) / 2; + if ( list[nIndex]->GetOrder() < nOrder ) + nLo = nIndex + 1; + else + { + nHi = nIndex - 1; + if ( list[nIndex]->GetOrder() == nOrder ) + { + bFound = true; + nLo = nIndex; + } + } + } + rIndex = nLo; + return bFound; +} + +class FilterStack +{ + std::vector& mrFilters; +public: + explicit FilterStack(std::vector& rFilters) : mrFilters(rFilters) {} + + void pushDimName(const OUString& rName, bool bDataLayout) + { + mrFilters.emplace_back(rName, bDataLayout); + } + + void pushDimValue(const OUString& rValueName, const OUString& rValue) + { + ScDPResultFilter& rFilter = mrFilters.back(); + rFilter.maValueName = rValueName; + rFilter.maValue = rValue; + rFilter.mbHasValue = true; + } + + ~FilterStack() + { + ScDPResultFilter& rFilter = mrFilters.back(); + if (rFilter.mbHasValue) + rFilter.mbHasValue = false; + else + mrFilters.pop_back(); + } +}; + +// function objects for sorting of the column and row members: + +class ScDPRowMembersOrder +{ + ScDPResultDimension& rDimension; + tools::Long nMeasure; + bool bAscending; + +public: + ScDPRowMembersOrder( ScDPResultDimension& rDim, tools::Long nM, bool bAsc ) : + rDimension(rDim), + nMeasure(nM), + bAscending(bAsc) + {} + + bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const; +}; + +class ScDPColMembersOrder +{ + ScDPDataDimension& rDimension; + tools::Long nMeasure; + bool bAscending; + +public: + ScDPColMembersOrder( ScDPDataDimension& rDim, tools::Long nM, bool bAsc ) : + rDimension(rDim), + nMeasure(nM), + bAscending(bAsc) + {} + + bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const; +}; + +} + +static bool lcl_IsLess( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, tools::Long nMeasure, bool bAscending ) +{ + // members can be NULL if used for rows + + ScDPSubTotalState aEmptyState; + const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : nullptr; + const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : nullptr; + + bool bError1 = pAgg1 && pAgg1->HasError(); + bool bError2 = pAgg2 && pAgg2->HasError(); + if ( bError1 ) + return false; // errors are always sorted at the end + else if ( bError2 ) + return true; // errors are always sorted at the end + else + { + double fVal1 = ( pAgg1 && pAgg1->HasData() ) ? pAgg1->GetResult() : 0.0; // no data is sorted as 0 + double fVal2 = ( pAgg2 && pAgg2->HasData() ) ? pAgg2->GetResult() : 0.0; + + // compare values + // don't have to check approxEqual, as this is the only sort criterion + + return bAscending ? ( fVal1 < fVal2 ) : ( fVal1 > fVal2 ); + } +} + +static bool lcl_IsEqual( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, tools::Long nMeasure ) +{ + // members can be NULL if used for rows + + ScDPSubTotalState aEmptyState; + const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : nullptr; + const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : nullptr; + + bool bError1 = pAgg1 && pAgg1->HasError(); + bool bError2 = pAgg2 && pAgg2->HasError(); + if ( bError1 ) + { + if ( bError2 ) + return true; // equal + else + return false; + } + else if ( bError2 ) + return false; + else + { + double fVal1 = ( pAgg1 && pAgg1->HasData() ) ? pAgg1->GetResult() : 0.0; // no data is sorted as 0 + double fVal2 = ( pAgg2 && pAgg2->HasData() ) ? pAgg2->GetResult() : 0.0; + + // compare values + // this is used to find equal data at the end of the AutoShow range, so approxEqual must be used + + return rtl::math::approxEqual( fVal1, fVal2 ); + } +} + +bool ScDPRowMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const +{ + const ScDPResultMember* pMember1 = rDimension.GetMember(nIndex1); + const ScDPResultMember* pMember2 = rDimension.GetMember(nIndex2); + +// 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 ); +} + +bool ScDPColMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const +{ + const ScDPDataMember* pDataMember1 = rDimension.GetMember(nIndex1); + const ScDPDataMember* pDataMember2 = rDimension.GetMember(nIndex2); + bool bHide1 = pDataMember1 && !pDataMember1->IsVisible(); + bool bHide2 = pDataMember2 && !pDataMember2->IsVisible(); + if ( bHide1 || bHide2 ) + return !bHide1; + return lcl_IsLess( pDataMember1, pDataMember2, nMeasure, bAscending ); +} + +ScDPInitState::Member::Member(tools::Long nSrcIndex, SCROW nNameIndex) : + mnSrcIndex(nSrcIndex), mnNameIndex(nNameIndex) {} + +void ScDPInitState::AddMember( tools::Long nSourceIndex, SCROW nMember ) +{ + maMembers.emplace_back(nSourceIndex, nMember); +} + +void ScDPInitState::RemoveMember() +{ + OSL_ENSURE(!maMembers.empty(), "ScDPInitState::RemoveMember: Attempt to remove member while empty."); + if (!maMembers.empty()) + maMembers.pop_back(); +} + +namespace { + +#if DUMP_PIVOT_TABLE +void dumpRow( + const OUString& rType, const OUString& rName, const ScDPAggData* pAggData, + ScDocument* pDoc, ScAddress& rPos ) +{ + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + pDoc->SetString( nCol++, nRow, nTab, rType ); + pDoc->SetString( nCol++, nRow, nTab, rName ); + while ( pAggData ) + { + pDoc->SetValue( nCol++, nRow, nTab, pAggData->GetResult() ); + pAggData = pAggData->GetExistingChild(); + } + rPos.SetRow( nRow + 1 ); +} + +void indent( ScDocument* pDoc, SCROW nStartRow, const ScAddress& rPos ) +{ + SCCOL nCol = rPos.Col(); + SCTAB nTab = rPos.Tab(); + + OUString aString; + for (SCROW nRow = nStartRow; nRow < rPos.Row(); nRow++) + { + aString = pDoc->GetString(nCol, nRow, nTab); + if (!aString.isEmpty()) + { + aString = " " + aString; + pDoc->SetString( nCol, nRow, nTab, aString ); + } + } +} +#endif + +} + +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); +} + +void ScDPRunningTotalState::AddColIndex( sal_Int32 nVisible, tools::Long nSorted ) +{ + maColVisible.back() = nVisible; + maColVisible.push_back(-1); + + maColSorted.back() = nSorted; + maColSorted.push_back(-1); +} + +void ScDPRunningTotalState::AddRowIndex( sal_Int32 nVisible, tools::Long nSorted ) +{ + maRowVisible.back() = nVisible; + maRowVisible.push_back(-1); + + maRowSorted.back() = nSorted; + maRowSorted.push_back(-1); +} + +void ScDPRunningTotalState::RemoveColIndex() +{ + OSL_ENSURE(!maColVisible.empty() && !maColSorted.empty(), "ScDPRunningTotalState::RemoveColIndex: array is already empty!"); + if (maColVisible.size() >= 2) + { + maColVisible.pop_back(); + maColVisible.back() = -1; + } + + if (maColSorted.size() >= 2) + { + maColSorted.pop_back(); + maColSorted.back() = -1; + } +} + +void ScDPRunningTotalState::RemoveRowIndex() +{ + OSL_ENSURE(!maRowVisible.empty() && !maRowSorted.empty(), "ScDPRunningTotalState::RemoveRowIndex: array is already empty!"); + if (maRowVisible.size() >= 2) + { + maRowVisible.pop_back(); + maRowVisible.back() = -1; + } + + if (maRowSorted.size() >= 2) + { + maRowSorted.pop_back(); + maRowSorted.back() = -1; + } +} + +ScDPRelativePos::ScDPRelativePos( tools::Long nBase, tools::Long nDir ) : + nBasePos( nBase ), + nDirection( nDir ) +{ +} + +void ScDPAggData::Update( const ScDPValue& rNext, ScSubTotalFunc eFunc, const ScDPSubTotalState& rSubState ) +{ + if (nCount<0) // error? + return; // nothing more... + + if (rNext.meType == ScDPValue::Empty) + return; + + if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE && rSubState.eRowForce != SUBTOTAL_FUNC_NONE && + rSubState.eColForce != rSubState.eRowForce ) + return; + if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eColForce; + if ( rSubState.eRowForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eRowForce; + + if ( eFunc == SUBTOTAL_FUNC_NONE ) + return; + + 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; + else if ( !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(maWelford.getCount())); + break; + + case SUBTOTAL_FUNC_STD: + case SUBTOTAL_FUNC_VAR: + bError = ( nCount < 2 ); // need at least 2 values + assert(bError || nCount == static_cast(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(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(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. + + if ( bEmpty ) + nCount = SC_DPAGG_RESULT_EMPTY; + else if ( bError ) + nCount = SC_DPAGG_RESULT_ERROR; + else + nCount = SC_DPAGG_RESULT_VALID; + + if ( bEmpty || bError ) + fResult = 0.0; // default, in case the state is later modified + + fVal = fResult; // used directly from now on + fAux = 0.0; // used for running total or original result of reference value +} + +bool ScDPAggData::IsCalculated() const +{ + return ( nCount <= SC_DPAGG_RESULT_EMPTY ); +} + +double ScDPAggData::GetResult() const +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + return fVal; // use calculated value +} + +bool ScDPAggData::HasError() const +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + return ( nCount == SC_DPAGG_RESULT_ERROR ); +} + +bool ScDPAggData::HasData() const +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + return ( nCount != SC_DPAGG_RESULT_EMPTY ); // values or error +} + +void ScDPAggData::SetResult( double fNew ) +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + fVal = fNew; // don't reset error flag +} + +void ScDPAggData::SetError() +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + nCount = SC_DPAGG_RESULT_ERROR; +} + +void ScDPAggData::SetEmpty( bool bSet ) +{ + assert( IsCalculated() && "ScDPAggData not calculated" ); + + if ( bSet ) + nCount = SC_DPAGG_RESULT_EMPTY; + else + nCount = SC_DPAGG_RESULT_VALID; +} + +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(); +} + +void ScDPAggData::Reset() +{ + maWelford = WelfordRunner(); + fVal = 0.0; + fAux = 0.0; + nCount = SC_DPAGG_EMPTY; + pChild.reset(); +} + +#if DUMP_PIVOT_TABLE +void ScDPAggData::Dump(int nIndent) const +{ + std::string aIndent(nIndent*2, ' '); + std::cout << aIndent << "* "; + if (IsCalculated()) + std::cout << GetResult(); + else + std::cout << "not calculated"; + + std::cout << " [val=" << fVal << "; aux=" << fAux << "; count=" << nCount << "]" << std::endl; +} +#endif + +ScDPRowTotals::ScDPRowTotals() : + bIsInColRoot( false ) +{ +} + +ScDPRowTotals::~ScDPRowTotals() +{ +} + +static ScDPAggData* lcl_GetChildTotal( ScDPAggData* pFirst, tools::Long nMeasure ) +{ + OSL_ENSURE( nMeasure >= 0, "GetColTotal: no measure" ); + + ScDPAggData* pAgg = pFirst; + tools::Long nSkip = nMeasure; + + // subtotal settings are ignored - column/row totals exist once per measure + + for ( tools::Long nPos=0; nPosGetChild(); // 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 ); + } + + return pAgg; +} + +ScDPAggData* ScDPRowTotals::GetRowTotal( tools::Long nMeasure ) +{ + return lcl_GetChildTotal( &aRowTotal, nMeasure ); +} + +ScDPAggData* ScDPRowTotals::GetGrandTotal( tools::Long nMeasure ) +{ + return lcl_GetChildTotal( &aGrandTotal, nMeasure ); +} + +static ScSubTotalFunc lcl_GetForceFunc( const ScDPLevel* pLevel, tools::Long nFuncNo ) +{ + ScSubTotalFunc eRet = SUBTOTAL_FUNC_NONE; + if ( pLevel ) + { + //TODO: direct access via ScDPLevel + + uno::Sequence 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 + } + + if ( nFuncNo >= 0 && nFuncNo < nSequence ) + { + ScGeneralFunction eUser = static_cast(aSeq.getConstArray()[nFuncNo]); + if (eUser != ScGeneralFunction::AUTO) + eRet = ScDPUtil::toSubTotalFunc(eUser); + } + } + return eRet; +} + +ScDPResultData::ScDPResultData( ScDPSource& rSrc ) : + mrSource(rSrc), + bLateInit( false ), + bDataAtCol( false ), + bDataAtRow( false ) +{ +} + +ScDPResultData::~ScDPResultData() +{ +} + +void ScDPResultData::SetMeasureData( + std::vector& rFunctions, std::vector& rRefs, + std::vector& rRefOrient, std::vector& 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)); +} + +void ScDPResultData::SetDataLayoutOrientation( sheet::DataPilotFieldOrientation nOrient ) +{ + bDataAtCol = ( nOrient == sheet::DataPilotFieldOrientation_COLUMN ); + bDataAtRow = ( nOrient == sheet::DataPilotFieldOrientation_ROW ); +} + +void ScDPResultData::SetLateInit( bool bSet ) +{ + bLateInit = bSet; +} + +tools::Long ScDPResultData::GetColStartMeasure() const +{ + if (maMeasureFuncs.size() == 1) + return 0; + + return bDataAtCol ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY; +} + +tools::Long ScDPResultData::GetRowStartMeasure() const +{ + if (maMeasureFuncs.size() == 1) + return 0; + + return bDataAtRow ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY; +} + +ScSubTotalFunc ScDPResultData::GetMeasureFunction(tools::Long nMeasure) const +{ + OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureFuncs.size(), "bumm"); + return maMeasureFuncs[nMeasure]; +} + +const sheet::DataPilotFieldReference& ScDPResultData::GetMeasureRefVal(tools::Long nMeasure) const +{ + OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureRefs.size(), "bumm"); + return maMeasureRefs[nMeasure]; +} + +sheet::DataPilotFieldOrientation ScDPResultData::GetMeasureRefOrient(tools::Long nMeasure) const +{ + OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureRefOrients.size(), "bumm"); + return maMeasureRefOrients[nMeasure]; +} + +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]); + + rbTotalResult = true; + return ScResId(STR_TABLE_ERGEBNIS); + } + else + { + OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureFuncs.size(), "bumm"); + const ScDPDimension* pDataDim = mrSource.GetDataDimension(nMeasure); + if (pDataDim) + { + const std::optional & pLayoutName = pDataDim->GetLayoutName(); + if (pLayoutName) + return *pLayoutName; + } + + ScSubTotalFunc eFunc = ( eForceFunc == SUBTOTAL_FUNC_NONE ) ? + GetMeasureFunction(nMeasure) : eForceFunc; + + return ScDPUtil::getDisplayedMeasureName(maMeasureNames[nMeasure], eFunc); + } +} + +OUString ScDPResultData::GetMeasureDimensionName(tools::Long nMeasure) const +{ + if ( nMeasure < 0 ) + { + OSL_FAIL("GetMeasureDimensionName: negative"); + return "***"; + } + + return mrSource.GetDataDimName(nMeasure); +} + +bool ScDPResultData::IsBaseForGroup( tools::Long nDim ) const +{ + return mrSource.GetData()->IsBaseForGroup(nDim); +} + +tools::Long ScDPResultData::GetGroupBase( tools::Long nGroupDim ) const +{ + return mrSource.GetData()->GetGroupBase(nGroupDim); +} + +bool ScDPResultData::IsNumOrDateGroup( tools::Long nDim ) const +{ + return mrSource.GetData()->IsNumOrDateGroup(nDim); +} + +bool ScDPResultData::IsInGroup( SCROW nGroupDataId, tools::Long nGroupIndex, + const ScDPItemData& rBaseData, tools::Long nBaseIndex ) const +{ + const ScDPItemData* pGroupData = mrSource.GetItemDataById(nGroupIndex , nGroupDataId); + if ( pGroupData ) + return mrSource.GetData()->IsInGroup(*pGroupData, nGroupIndex, rBaseData, nBaseIndex); + else + return false; +} + +bool ScDPResultData::HasCommonElement( SCROW nFirstDataId, tools::Long nFirstIndex, + const ScDPItemData& rSecondData, tools::Long nSecondIndex ) const +{ + const ScDPItemData* pFirstData = mrSource.GetItemDataById(nFirstIndex , nFirstDataId); + if ( pFirstData ) + return mrSource.GetData()->HasCommonElement(*pFirstData, nFirstIndex, rSecondData, nSecondIndex); + else + return false; +} + +ResultMembers& ScDPResultData::GetDimResultMembers(tools::Long nDim, const ScDPDimension* pDim, ScDPLevel* pLevel) const +{ + if (nDim < static_cast(maDimMembers.size()) && maDimMembers[nDim]) + return *maDimMembers[nDim]; + + if (nDim >= static_cast(maDimMembers.size())) + maDimMembers.resize(nDim+1); + + std::unique_ptr 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); + } + } + + maDimMembers[nDim] = std::move(pResultMembers); + return *maDimMembers[nDim]; +} + +ScDPResultMember::ScDPResultMember( + const ScDPResultData* pData, const ScDPParentDimData& rParentDimData ) : + pResultData( pData ), + aParentDimData( rParentDimData ), + bHasElements( false ), + bForceSubTotal( false ), + bHasHiddenDetails( false ), + bInitialized( false ), + bAutoHidden( false ), + nMemberStep( 1 ) +{ + // pParentLevel/pMemberDesc is 0 for root members +} + +ScDPResultMember::ScDPResultMember( + const ScDPResultData* pData, bool bForceSub ) : + pResultData( pData ), + bHasElements( false ), + bForceSubTotal( bForceSub ), + bHasHiddenDetails( false ), + bInitialized( false ), + bAutoHidden( false ), + nMemberStep( 1 ) +{ +} +ScDPResultMember::~ScDPResultMember() +{ +} + +OUString ScDPResultMember::GetName() const +{ + const ScDPMember* pMemberDesc = GetDPMember(); + if (pMemberDesc) + return pMemberDesc->GetNameStr( false ); + else + return ScResId(STR_PIVOT_TOTAL); // root member +} + +OUString ScDPResultMember::GetDisplayName( bool bLocaleIndependent ) const +{ + const ScDPMember* pDPMember = GetDPMember(); + if (!pDPMember) + return OUString(); + + ScDPItemData aItem(pDPMember->FillItemData()); + if (aParentDimData.mpParentDim) + { + tools::Long nDim = aParentDimData.mpParentDim->GetDimension(); + return pResultData->GetSource().GetData()->GetFormattedString(nDim, aItem, bLocaleIndependent); + } + + return aItem.GetString(); +} + +ScDPItemData ScDPResultMember::FillItemData() const +{ + const ScDPMember* pMemberDesc = GetDPMember(); + if (pMemberDesc) + return pMemberDesc->FillItemData(); + return ScDPItemData(ScResId(STR_PIVOT_TOTAL)); // root member +} + +bool ScDPResultMember::IsNamedItem( SCROW nIndex ) const +{ + //TODO: store ScDPMember pointer instead of ScDPMember ??? + const ScDPMember* pMemberDesc = GetDPMember(); + if (pMemberDesc) + return pMemberDesc->IsNamedItem(nIndex); + return false; +} + +bool ScDPResultMember::IsValidEntry( const vector< SCROW >& aMembers ) const +{ + if ( !IsValid() ) + return false; + + const ScDPResultDimension* pChildDim = GetChildDimension(); + if (pChildDim) + { + if (aMembers.size() < 2) + return false; + + vector::const_iterator itr = aMembers.begin(); + vector aChildMembers(++itr, aMembers.end()); + return pChildDim->IsValidEntry(aChildMembers); + } + else + return true; +} + +void ScDPResultMember::InitFrom( const vector& ppDim, const vector& 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& 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; +} + +tools::Long ScDPResultMember::GetSize(tools::Long nMeasure) const +{ + if ( !IsVisible() ) + return 0; + const ScDPLevel* pParentLevel = GetParentLevel(); + tools::Long nExtraSpace = 0; + if ( pParentLevel && pParentLevel->IsAddEmpty() ) + ++nExtraSpace; + + 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; + + tools::Long nSize = pChildDimension->GetSize(nMeasure); + tools::Long nUserSubStart; + tools::Long nUserSubCount = GetSubTotalCount( &nUserSubStart ); + nUserSubCount -= nUserSubStart; // for output size, use visible count + if ( nUserSubCount ) + { + if ( nMeasure == SC_DPMEASURE_ALL ) + nSize += pResultData->GetMeasureCount() * nUserSubCount; + else + nSize += nUserSubCount; + } + return nSize + nExtraSpace; + } + else + { + if ( nMeasure == SC_DPMEASURE_ALL ) + return pResultData->GetMeasureCount() + nExtraSpace; + else + return 1 + nExtraSpace; + } +} + +bool ScDPResultMember::IsVisible() const +{ + if (!bInitialized) + return false; + + if (!IsValid()) + return false; + + if (bHasElements) + return true; + + // not initialized -> shouldn't be there at all + // (allocated only to preserve ordering) + const ScDPLevel* pParentLevel = GetParentLevel(); + + return (pParentLevel && pParentLevel->getShowEmpty()); +} + +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() ) + return false; + + if ( bAutoHidden ) + return false; + + return true; +} + +tools::Long ScDPResultMember::GetSubTotalCount( tools::Long* pUserSubStart ) const +{ + if ( pUserSubStart ) + *pUserSubStart = 0; // default + + const ScDPLevel* pParentLevel = GetParentLevel(); + + if ( bForceSubTotal ) // set if needed for root members + return 1; // grand total is always "automatic" + else if ( pParentLevel ) + { + //TODO: direct access via ScDPLevel + + uno::Sequence 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) + + ++nSequence; + if ( pUserSubStart ) + *pUserSubStart = 1; // visible subtotals start at 1 + } + return nSequence; + } + else + return 0; +} + +void ScDPResultMember::ProcessData( const vector< SCROW >& aChildMembers, const ScDPResultDimension* pDataDim, + const vector< SCROW >& aDataMembers, const vector& aValues ) +{ + SetHasElements(); + + 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