Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sc/source/core/data/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 146 kB image not shown  

Quelle  dptabres.cxx   Sprache: C

 
/* -*- 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 <dptabres.hxx>

#include <dptabdat.hxx>
#include <dptabsrc.hxx>
#include <global.hxx>
#include <subtotal.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <dpitemdata.hxx>
#include <generalfunction.hxx>

#include <document.hxx>
#include <dpresfilter.hxx>
#include <dputil.hxx>

#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <rtl/math.hxx>
#include <sal/log.hxx>

#include <math.h>
#include <float.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <unordered_map>

#include <com/sun/star/sheet/DataResultFlags.hpp>
#include <com/sun/star/sheet/MemberResultFlags.hpp>
#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp>
#include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp>
#include <com/sun/star/sheet/DataPilotFieldShowItemsMode.hpp>
#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
#include <com/sun/star/sheet/GeneralFunction2.hpp>

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<std::unique_ptr<ScDPResultMember>>& 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<ScDPResultFilter>& mrFilters;
public:
    explicit FilterStack(std::vector<ScDPResultFilter>& 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<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.

    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; 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 );
    }

    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<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
        }

        if ( nFuncNo >= 0 && nFuncNo < nSequence )
        {
            ScGeneralFunction eUser = static_cast<ScGeneralFunction>(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<ScSubTotalFunc>& rFunctions, std::vector<sheet::DataPilotFieldReference>& rRefs,
    std::vector<sheet::DataPilotFieldOrientation>& rRefOrient, std::vector<OUString>&&nbsp;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<OUString> & 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 u"***"_ustr;
    }

    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<tools::Long>(maDimMembers.size()) && maDimMembers[nDim])
        return *maDimMembers[nDim];

    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);
        }
    }

    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<SCROW>::const_iterator itr = aMembers.begin();
        vector<SCROW> aChildMembers(++itr, aMembers.end());
        return pChildDim->IsValidEntry(aChildMembers);
    }
    else
        return true;
}

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;
}

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<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)

            ++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<ScDPValue>& 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<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 );
        }

        pDataRoot->ProcessData( aDataMembers, aValues, aSubState );
    }
}

/**
 * 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;

    tools::Long nSize = GetSize(nMeasure);
    sheet::MemberResult* pArray = pSequences->getArray();
    OSL_ENSURE( rPos+nSize <= pSequences->getLength(), "bumm" );

    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 ( pParentLevel && pParentLevel->getRepeatItemLabels() )
        {
            tools::Long nSizeNonEmpty = nSize;
            if ( pParentLevel->IsAddEmpty() )
                --nSizeNonEmpty;
            for (tools::Long i=1; i<nSizeNonEmpty; i++)
            {
                pArray[rPos+i].Name = aName;
                pArray[rPos+i].Caption = aCaption;
                pArray[rPos+i].Flags  |= sheet::MemberResultFlags::HASMEMBER;
                pArray[rPos+i].Value   = fValue;
            }
        }
    }

    tools::Long nExtraSpace = 0;
    if ( pParentLevel && pParentLevel->IsAddEmpty() )
        ++nExtraSpace;

    bool bTitleLine = false;
    if ( pParentLevel && pParentLevel->IsOutlineLayout() )
        bTitleLine = true;

    // 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
    }

    rPos += nSize;

    tools::Long nUserSubStart;
    tools::Long nUserSubCount = GetSubTotalCount(&nUserSubStart);
    if ( !nUserSubCount || !pChildDimension || bSubTotalInTitle )
        return;

    tools::Long nMemberMeasure = nMeasure;
    tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);

    rPos -= nSubSize * (nUserSubCount - nUserSubStart);     // GetSize includes space for SubTotal
    rPos -= nExtraSpace;                                    // GetSize includes the empty line

    for (tools::Long nUserPos=nUserSubStart; nUserPos<nUserSubCount; nUserPos++)
    {
        for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
        {
            if ( nMeasure == SC_DPMEASURE_ALL )
                nMemberMeasure = nSubCount;

            ScSubTotalFunc eForce = SUBTOTAL_FUNC_NONE;
            if (bHasChild)
                eForce = lcl_GetForceFunc( pParentLevel, nUserPos );

            bool bTotalResult = false;
            OUString aSubStr = aCaption + " " + pResultData->GetMeasureString(nMemberMeasure, false, eForce, bTotalResult);

            if (bTotalResult)
            {
                if (pMemberDesc)
                {
                    // single data field layout.
                    const std::optional<OUString> & pSubtotalName = pParentDim->GetSubtotalName();
                    if (pSubtotalName)
                        aSubStr = lcl_parseSubtotalName(*pSubtotalName, aCaption);
                    pArray[rPos].Flags &= ~sheet::MemberResultFlags::GRANDTOTAL;
                }
                else
                {
                    // root member - subtotal (grand total?) for multi-data field layout.
                    const std::optional<OUString> & pGrandTotalName = pResultData->GetSource().GetGrandTotalName();
                    if (pGrandTotalName)
                        aSubStr = *pGrandTotalName;
                    pArray[rPos].Flags |= sheet::MemberResultFlags::GRANDTOTAL;
                }
            }

            fValue = std::numeric_limits<double>::quiet_NaN(); /* TODO: any numeric value to obtain? */
            pArray[rPos].Name    = aName;
            pArray[rPos].Caption = aSubStr;
            pArray[rPos].Flags = ( pArray[rPos].Flags |
                                ( sheet::MemberResultFlags::HASMEMBER | sheet::MemberResultFlags::SUBTOTAL) ) &
                                ~sheet::MemberResultFlags::CONTINUE;
            pArray[rPos].Value   = fValue;

            if ( nMeasure == SC_DPMEASURE_ALL )
            {
                //  data layout dimension is (direct/indirect) child of this.
                //  data layout dimension must have name for all entries.

                uno::Sequence<sheet::MemberResult>* pLayoutSeq = pSequences;
                if (!bRoot)
                    ++pLayoutSeq;
                ScDPResultDimension* pLayoutDim = pChildDimension.get();
                while ( pLayoutDim && !pLayoutDim->IsDataLayout() )
                {
                    pLayoutDim = pLayoutDim->GetFirstChildDimension();
                    ++pLayoutSeq;
                }
                if ( pLayoutDim )
                {
                    sheet::MemberResult* pLayoutArray = pLayoutSeq->getArray();
                    pLayoutArray[rPos].Name = pResultData->GetMeasureDimensionName(nMemberMeasure);
                }
            }

            rPos += 1;
        }
    }

    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;

    tools::Long nExtraSpace = 0;
    if ( pParentLevel && pParentLevel->IsAddEmpty() )
        ++nExtraSpace;

    bool bTitleLine = false;
    if ( pParentLevel && pParentLevel->IsOutlineLayout() )
        bTitleLine = true;

    bool bSubTotalInTitle = IsSubTotalInTitle( nMeasure );

    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
    }

    tools::Long nUserSubStart;
    tools::Long nUserSubCount = GetSubTotalCount(&nUserSubStart);
    if ( !nUserSubCount && bHasChild )
        return;

    // 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

        for (tools::Long nUserPos=nUserSubStart; nUserPos<nUserSubCount; nUserPos++)
        {
            if ( bHasChild && nUserSubCount > 1 )
            {
                aSubState.nRowSubTotalFunc = nUserPos;
                aSubState.eRowForce = lcl_GetForceFunc( /*pParentLevel*/GetParentLevel() , nUserPos );
            }

            for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
            {
                if ( nMeasure == SC_DPMEASURE_ALL )
                    nMemberMeasure = nSubCount;
                else if ( pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL )
                    nMemberMeasure = SC_DPMEASURE_ALL;

                OSL_ENSURE( rFilterCxt.mnRow < rSequence.getLength(), "bumm" );
                rFilterCxt.mnCol = 0;
                if (pRefMember->IsVisible())
                {
                    uno::Sequence<sheet::DataResult>& rSubSeq = rSequence.getArray()[rFilterCxt.mnRow];
                    pDataRoot->FillDataRow(pRefMember, rFilterCxt, rSubSeq, nMemberMeasure, bHasChild, aSubState);
                }
                rFilterCxt.mnRow += 1;
            }
        }
    }
    else
        rFilterCxt.mnRow += nSubSize * ( nUserSubCount - nUserSubStart );   // empty rows occur when ShowEmpty is true

    // 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;

    tools::Long nMemberMeasure = nMeasure;
    tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);

    if (pDataRoot)
    {
        ScDPSubTotalState aSubState;        // initial state

        for (tools::Long nUserPos = 0; nUserPos < nUserSubCount; ++nUserPos)   // including hidden "automatic"
        {
            if (bHasChild && nUserSubCount > 1)
            {
                aSubState.nRowSubTotalFunc = nUserPos;
                aSubState.eRowForce = lcl_GetForceFunc(GetParentLevel(), nUserPos);
            }

            for (tools::Long nSubCount = 0; nSubCount < nSubSize; ++nSubCount)
            {
                if (nMeasure == SC_DPMEASURE_ALL)
                    nMemberMeasure = nSubCount;
                else if (pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL)
                    nMemberMeasure = SC_DPMEASURE_ALL;

                pDataRoot->UpdateDataRow(pRefMember, nMemberMeasure, bHasChild, aSubState);
            }
        }
    }

    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;

        tools::Long nMemberMeasure = nMeasure;
        tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);

        if ( pDataRoot )
        {
            ScDPSubTotalState aSubState;        // initial state

            for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++)   // including hidden "automatic"
            {
                if ( bHasChild && nUserSubCount > 1 )
                {
                    aSubState.nRowSubTotalFunc = nUserPos;
                    aSubState.eRowForce = lcl_GetForceFunc(GetParentLevel(), nUserPos);
                }

                for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
                {
                    if ( nMeasure == SC_DPMEASURE_ALL )
                        nMemberMeasure = nSubCount;
                    else if ( pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL )
                        nMemberMeasure = SC_DPMEASURE_ALL;

                    if (pRefMember->IsVisible())
                        pDataRoot->UpdateRunningTotals(
                            pRefMember, nMemberMeasure, bHasChild, aSubState, rRunning, rTotals, *this);
                }
            }
        }
    }

    if (bHasChild)  // child dimension must be processed last, so the column total is known
    {
        pChildDimension->UpdateRunningTotals( pRefMember, nMeasure, rRunning, rTotals );
    }
}

#if DUMP_PIVOT_TABLE
void ScDPResultMember::DumpState( const ScDPResultMember* pRefMember, ScDocument* pDoc, ScAddress& rPos ) const
{
    dumpRow(u"ScDPResultMember"_ustr, GetName(), nullptr, pDoc, rPos);
    SCROW nStartRow = rPos.Row();

    if (pDataRoot)
        pDataRoot->DumpState( pRefMember, pDoc, rPos );

    if (pChildDimension)
        pChildDimension->DumpState( pRefMember, pDoc, rPos );

    indent(pDoc, nStartRow, rPos);
}

void ScDPResultMember::Dump(int nIndent) const
{
    std::string aIndent(nIndent*2, ' ');
    std::cout << aIndent << "-- result member '" << GetName() << "'" << std::endl;

    std::cout << aIndent << " column totals" << std::endl;
    for (const ScDPAggData* p = &aColTotal; p; p = p->GetExistingChild())
        p->Dump(nIndent+1);

    if (pChildDimension)
        pChildDimension->Dump(nIndent+1);

    if (pDataRoot)
    {
        std::cout << aIndent << " data root" << std::endl;
        pDataRoot->Dump(nIndent+1);
    }
}
#endif

ScDPAggData* ScDPResultMember::GetColTotal( tools::Long nMeasure ) const
{
    return lcl_GetChildTotal( const_cast<ScDPAggData*>(&aColTotal), nMeasure );
}

void ScDPResultMember::FillVisibilityData(ScDPResultVisibilityData& rData) const
{
    if (pChildDimension)
        pChildDimension->FillVisibilityData(rData);
}

ScDPDataMember::ScDPDataMember( const ScDPResultData* pData, const ScDPResultMember* pRes ) :
    pResultData( pData ),
    pResultMember( pRes )
{
    // pResultMember is 0 for root members
}

ScDPDataMember::~ScDPDataMember()
{
}

OUString ScDPDataMember::GetName() const
{
    if (pResultMember)
        return pResultMember->GetName();
    else
        return OUString();
}

bool ScDPDataMember::IsVisible() const
{
    if (pResultMember)
        return pResultMember->IsVisible();
    else
        return false;
}

bool ScDPDataMember::IsNamedItem( SCROW nRow ) const
{
    if (pResultMember)
        return pResultMember->IsNamedItem(nRow);
    else
        return false;
}

bool ScDPDataMember::HasHiddenDetails() const
{
    if (pResultMember)
        return pResultMember->HasHiddenDetails();
    else
        return false;
}

void ScDPDataMember::InitFrom( const ScDPResultDimension* pDim )
{
    if ( !pChildDimension )
        pChildDimension.reset( new ScDPDataDimension(pResultData) );
    pChildDimension->InitFrom(pDim);
}

const tools::Long SC_SUBTOTALPOS_AUTO = -1;    // default
const tools::Long SC_SUBTOTALPOS_SKIP = -2;    // don't use

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

        return SC_SUBTOTALPOS_SKIP;
    }

    tools::Long nRet = SC_SUBTOTALPOS_AUTO;
    if ( rSubState.nColSubTotalFunc >= 0 ) nRet = rSubState.nColSubTotalFunc;
    if ( rSubState.nRowSubTotalFunc >= 0 ) nRet = rSubState.nRowSubTotalFunc;
    return nRet;
}

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
    }

    size_t nCount = aValues.size();
    for (size_t nPos = 0; nPos < nCount; ++nPos)
    {
        pAgg->Update(aValues[nPos], pResultData->GetMeasureFunction(nPos), rSubState);
        pAgg = pAgg->GetChild();
    }
}

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() );
    }

    tools::Long nUserSubCount = pResultMember ? pResultMember->GetSubTotalCount() : 0;

    // 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;

    ScDPSubTotalState aLocalSubState = rSubState;        // keep row state, modify column
    for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++)   // including hidden "automatic"
    {
        if ( pChildDimension && nUserSubCount > 1 )
        {
            const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
            aLocalSubState.nColSubTotalFunc = nUserPos;
            aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
        }

        UpdateValues( aValues, aLocalSubState );
    }

    if (pChildDimension)
        pChildDimension->ProcessData( aChildMembers, aValues, rSubState );      // with unmodified subtotal state
}

bool ScDPDataMember::HasData( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
{
    if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE && rSubState.eRowForce != SUBTOTAL_FUNC_NONE &&
                                                        rSubState.eColForce != rSubState.eRowForce )
        return false;

    //  HasData can be different between measures!

    const ScDPAggData* pAgg = GetConstAggData( nMeasure, rSubState );
    if (!pAgg)
        return false;           //TODO: error?

    return pAgg->HasData();
}

bool ScDPDataMember::HasError( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
{
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=91 H=96 G=93

¤ Dauer der Verarbeitung: 0.23 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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.