/* -*- 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 <xepivot.hxx>
#include <xehelper.hxx>
#include <com/sun/star/sheet/DataPilotFieldSortInfo.hpp>
#include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp>
#include <com/sun/star/sheet/DataPilotFieldLayoutInfo.hpp>
#include <com/sun/star/sheet/DataPilotFieldReference.hpp>
#include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp>
#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>

#include <algorithm>
#include <math.h>
#include <string_view>

#include <osl/diagnose.h>
#include <sot/storage.hxx>
#include <document.hxx>
#include <dpcache.hxx>
#include <dpgroup.hxx>
#include <dpobject.hxx>
#include <dpsave.hxx>
#include <dpdimsave.hxx>
#include <dpshttab.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <xestring.hxx>
#include <xelink.hxx>
#include <dputil.hxx>
#include <generalfunction.hxx>
#include <svl/numformat.hxx>

using ::com::sun::star::sheet::DataPilotFieldOrientation;
using ::com::sun::star::sheet::DataPilotFieldOrientation_ROW;
using ::com::sun::star::sheet::DataPilotFieldOrientation_COLUMN;
using ::com::sun::star::sheet::DataPilotFieldOrientation_PAGE;
using ::com::sun::star::sheet::DataPilotFieldOrientation_DATA;
using ::com::sun::star::sheet::DataPilotFieldSortInfo;
using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo;
using ::com::sun::star::sheet::DataPilotFieldLayoutInfo;
using ::com::sun::star::sheet::DataPilotFieldReference;

// Pivot cache

namespace {

// constants to track occurrence of specific data types
const sal_uInt16 EXC_PCITEM_DATA_STRING     = 0x0001;   /// String, empty, boolean, error.
const sal_uInt16 EXC_PCITEM_DATA_DOUBLE     = 0x0002;   /// Double with fraction.
const sal_uInt16 EXC_PCITEM_DATA_INTEGER    = 0x0004;   /// Integer, double without fraction.
const sal_uInt16 EXC_PCITEM_DATA_DATE       = 0x0008;   /// Date, time, date/time.

/** Maps a bitfield consisting of EXC_PCITEM_DATA_* flags above to SXFIELD data type bitfield. */
const sal_uInt16 spnPCItemFlags[] =
{                               // STR DBL INT DAT
    EXC_SXFIELD_DATA_NONE,
    EXC_SXFIELD_DATA_STR,       //  x
    EXC_SXFIELD_DATA_INT,       //      x
    EXC_SXFIELD_DATA_STR_INT,   //  x   x
    EXC_SXFIELD_DATA_DBL,       //          x
    EXC_SXFIELD_DATA_STR_DBL,   //  x       x
    EXC_SXFIELD_DATA_INT,       //      x   x
    EXC_SXFIELD_DATA_STR_INT,   //  x   x   x
    EXC_SXFIELD_DATA_DATE,      //              x
    EXC_SXFIELD_DATA_DATE_STR,  //  x           x
    EXC_SXFIELD_DATA_DATE_NUM,  //      x       x
    EXC_SXFIELD_DATA_DATE_STR,  //  x   x       x
    EXC_SXFIELD_DATA_DATE_NUM,  //          x   x
    EXC_SXFIELD_DATA_DATE_STR,  //  x       x   x
    EXC_SXFIELD_DATA_DATE_NUM,  //      x   x   x
    EXC_SXFIELD_DATA_DATE_STR   //  x   x   x   x
};

} // namespace

XclExpPCItem::XclExpPCItem( const OUString& rText ) :
    XclExpRecord( (!rText.isEmpty()) ? EXC_ID_SXSTRING : EXC_ID_SXEMPTY, 0 ),
    mnTypeFlag( EXC_PCITEM_DATA_STRING )
{
    if( !rText.isEmpty() )
        SetText( rText );
    else
        SetEmpty();
}

XclExpPCItem::XclExpPCItem( double fValue, const OUString& rText ) :
    XclExpRecord( EXC_ID_SXDOUBLE, 8 )
{
    SetDouble( fValue, rText );
    mnTypeFlag = (fValue - floor( fValue ) == 0.0) ?
        EXC_PCITEM_DATA_INTEGER : EXC_PCITEM_DATA_DOUBLE;
}

XclExpPCItem::XclExpPCItem( const DateTime& rDateTime, const OUString& rText ) :
    XclExpRecord( EXC_ID_SXDATETIME, 8 )
{
    SetDateTime( rDateTime, rText );
    mnTypeFlag = EXC_PCITEM_DATA_DATE;
}

XclExpPCItem::XclExpPCItem( sal_Int16 nValue ) :
    XclExpRecord( EXC_ID_SXINTEGER, 2 ),
    mnTypeFlag( EXC_PCITEM_DATA_INTEGER )
{
    SetInteger( nValue );
}

XclExpPCItem::XclExpPCItem( bool bValue, const OUString& rText ) :
    XclExpRecord( EXC_ID_SXBOOLEAN, 2 ),
    mnTypeFlag( EXC_PCITEM_DATA_STRING )
{
    SetBool( bValue, rText );
}

bool XclExpPCItem::EqualsText( std::u16string_view rText ) const
{
    return rText.empty() ? IsEmpty() : (GetText() && (*GetText() == rText));
}

bool XclExpPCItem::EqualsDouble( double fValue ) const
{
    return GetDouble() && (*GetDouble() == fValue);
}

bool XclExpPCItem::EqualsDateTime( const DateTime& rDateTime ) const
{
    return GetDateTime() && (*GetDateTime() == rDateTime);
}

bool XclExpPCItem::EqualsBool( bool bValue ) const
{
    return GetBool() && (*GetBool() == bValue);
}

void XclExpPCItem::WriteBody( XclExpStream& rStrm )
{
    if( const OUString* pText = GetText() )
    {
        rStrm << XclExpString( *pText );
    }
    else if( const double* pfValue = GetDouble() )
    {
        rStrm << *pfValue;
    }
    else if( const sal_Int16* pnValue = GetInteger() )
    {
        rStrm << *pnValue;
    }
    else if( const DateTime* pDateTime = GetDateTime() )
    {
        sal_uInt16 nYear = static_cast< sal_uInt16 >( pDateTime->GetYear() );
        sal_uInt16 nMonth = pDateTime->GetMonth();
        sal_uInt8 nDay = static_cast< sal_uInt8 >( pDateTime->GetDay() );
        sal_uInt8 nHour = static_cast< sal_uInt8 >( pDateTime->GetHour() );
        sal_uInt8 nMin = static_cast< sal_uInt8 >( pDateTime->GetMin() );
        sal_uInt8 nSec = static_cast< sal_uInt8 >( pDateTime->GetSec() );
        if( nYear < 1900 ) { nYear = 1900; nMonth = 1; nDay = 0; }
        rStrm << nYear << nMonth << nDay << nHour << nMin << nSec;
    }
    else if( const bool* pbValue = GetBool() )
    {
        rStrm << static_cast< sal_uInt16 >( *pbValue ? 1 : 0 );
    }
    else
    {
        // nothing to do for SXEMPTY
        OSL_ENSURE( IsEmpty(), "XclExpPCItem::WriteBody - no data found" );
    }
}

XclExpPCField::XclExpPCField(
        const XclExpRoot& rRoot, sal_uInt16 nFieldIdx,
        const ScDPObject& rDPObj, const ScRange& rRange ) :
    XclExpRecord( EXC_ID_SXFIELD ),
    XclPCField( EXC_PCFIELD_STANDARD, nFieldIdx ),
    XclExpRoot( rRoot ),
    mnTypeFlags( 0 )
{
    // general settings for the standard field, insert all items from source range
    InitStandardField( rRange );

    // add special settings for inplace numeric grouping
    if( const ScDPSaveData* pSaveData = rDPObj.GetSaveData() )
    {
        if( const ScDPDimensionSaveData* pSaveDimData = pSaveData->GetExistingDimensionData() )
        {
            if( const ScDPSaveNumGroupDimension* pNumGroupDim = pSaveDimData->GetNumGroupDim( GetFieldName() ) )
            {
                const ScDPNumGroupInfo& rNumInfo = pNumGroupDim->GetInfo();
                const ScDPNumGroupInfo& rDateInfo = pNumGroupDim->GetDateInfo();
                OSL_ENSURE( !rNumInfo.mbEnable || !rDateInfo.mbEnable,
                    "XclExpPCField::XclExpPCField - numeric and date grouping enabled" );

                if( rNumInfo.mbEnable )
                    InitNumGroupField( rDPObj, rNumInfo );
                else if( rDateInfo.mbEnable )
                    InitDateGroupField( rDPObj, rDateInfo, pNumGroupDim->GetDatePart() );
            }
        }
    }

    // final settings (flags, item numbers)
    Finalize();
}

XclExpPCField::XclExpPCField(
        const XclExpRoot& rRoot, sal_uInt16 nFieldIdx,
        const ScDPObject& rDPObj, const ScDPSaveGroupDimension& rGroupDim, const XclExpPCField& rBaseField ) :
    XclExpRecord( EXC_ID_SXFIELD ),
    XclPCField( EXC_PCFIELD_STDGROUP, nFieldIdx ),
    XclExpRoot( rRoot ),
    mnTypeFlags( 0 )
{
    // add base field info (always using first base field, not predecessor of this field) ***
    OSL_ENSURE( rBaseField.GetFieldName() == rGroupDim.GetSourceDimName(),
        "XclExpPCField::FillFromGroup - wrong base cache field" );
    maFieldInfo.maName = rGroupDim.GetGroupDimName();
    maFieldInfo.mnGroupBase = rBaseField.GetFieldIndex();

    // add standard group info or date group info
    const ScDPNumGroupInfo& rDateInfo = rGroupDim.GetDateInfo();
    if( rDateInfo.mbEnable && (rGroupDim.GetDatePart() != 0) )
        InitDateGroupField( rDPObj, rDateInfo, rGroupDim.GetDatePart() );
    else
        InitStdGroupField( rBaseField, rGroupDim );

    // final settings (flags, item numbers)
    Finalize();
}

XclExpPCField::~XclExpPCField()
{
}

void XclExpPCField::SetGroupChildField( const XclExpPCField& rChildField )
{
    OSL_ENSURE( !::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD ),
        "XclExpPCField::SetGroupChildIndex - field already has a grouping child field" );
    ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD );
    maFieldInfo.mnGroupChild = rChildField.GetFieldIndex();
}

sal_uInt16 XclExpPCField::GetItemCount() const
{
    return static_cast< sal_uInt16 >( GetVisItemList().GetSize() );
}

const XclExpPCItem* XclExpPCField::GetItem( sal_uInt16 nItemIdx ) const
{
    return GetVisItemList().GetRecord( nItemIdx );
}

sal_uInt16 XclExpPCField::GetItemIndex( std::u16string_view rItemName ) const
{
    const XclExpPCItemList& rItemList = GetVisItemList();
    for( size_t nPos = 0, nSize = rItemList.GetSize(); nPos < nSize; ++nPos )
        if( rItemList.GetRecord( nPos )->ConvertToText() == rItemName )
            return static_cast< sal_uInt16 >( nPos );
    return EXC_PC_NOITEM;
}

std::size_t XclExpPCField::GetIndexSize() const
{
    return Has16BitIndexes() ? 2 : 1;
}

void XclExpPCField::WriteIndex( XclExpStream& rStrm, sal_uInt32 nSrcRow ) const
{
    // only standard fields write item indexes
    if( nSrcRow < maIndexVec.size() )
    {
        sal_uInt16 nIndex = maIndexVec[ nSrcRow ];
        if( Has16BitIndexes() )
            rStrm << nIndex;
        else
            rStrm << static_cast< sal_uInt8 >( nIndex );
    }
}

void XclExpPCField::Save( XclExpStream& rStrm )
{
    OSL_ENSURE( IsSupportedField(), "XclExpPCField::Save - unknown field type" );
    // SXFIELD
    XclExpRecord::Save( rStrm );
    // SXFDBTYPE
    XclExpUInt16Record( EXC_ID_SXFDBTYPE, EXC_SXFDBTYPE_DEFAULT ).Save( rStrm );
    // list of grouping items
    maGroupItemList.Save( rStrm );
    // SXGROUPINFO
    WriteSxgroupinfo( rStrm );
    // SXNUMGROUP and additional grouping items (grouping limit settings)
    WriteSxnumgroup( rStrm );
    // list of original items
    maOrigItemList.Save( rStrm );
}

// private --------------------------------------------------------------------

const XclExpPCField::XclExpPCItemList& XclExpPCField::GetVisItemList() const
{
    OSL_ENSURE( IsStandardField() == maGroupItemList.IsEmpty(),
        "XclExpPCField::GetVisItemList - unexpected additional items in standard field" );
    return IsStandardField() ? maOrigItemList : maGroupItemList;
}

void XclExpPCField::InitStandardField( const ScRange& rRange )
{
    OSL_ENSURE( IsStandardField(), "XclExpPCField::InitStandardField - only for standard fields" );
    OSL_ENSURE( rRange.aStart.Col() == rRange.aEnd.Col(), "XclExpPCField::InitStandardField - cell range with multiple columns" );

    ScDocument& rDoc = GetDoc();
    SvNumberFormatter& rFormatter = GetFormatter();

    // field name is in top cell of the range
    ScAddress aPos( rRange.aStart );
    maFieldInfo.maName = rDoc.GetString(aPos.Col(), aPos.Row(), aPos.Tab());
    // #i76047# maximum field name length in pivot cache is 255
    if (maFieldInfo.maName.getLength() > EXC_PC_MAXSTRLEN)
        maFieldInfo.maName = maFieldInfo.maName.copy(0, EXC_PC_MAXSTRLEN);

    // loop over all cells, create pivot cache items
    for( aPos.IncRow(); (aPos.Row() <= rRange.aEnd.Row()) && (maOrigItemList.GetSize() < EXC_PC_MAXITEMCOUNT); aPos.IncRow() )
    {
        OUString aText = rDoc.GetString(aPos.Col(), aPos.Row(), aPos.Tab());
        if( rDoc.HasValueData( aPos.Col(), aPos.Row(), aPos.Tab() ) )
        {
            double fValue = rDoc.GetValue( aPos );
            SvNumFormatType nFmtType = rFormatter.GetType( rDoc.GetNumberFormat( rDoc.GetNonThreadedContext(), aPos ) );
            if( nFmtType == SvNumFormatType::LOGICAL )
                InsertOrigBoolItem( fValue != 0, aText );
            else if( nFmtType & SvNumFormatType::DATETIME )
                InsertOrigDateTimeItem( GetDateTimeFromDouble( ::std::max( fValue, 0.0 ) ), aText );
            else
                InsertOrigDoubleItem( fValue, aText );
        }
        else
        {
            InsertOrigTextItem( aText );
        }
    }
}

void XclExpPCField::InitStdGroupField( const XclExpPCField& rBaseField, const ScDPSaveGroupDimension& rGroupDim )
{
    OSL_ENSURE( IsGroupField(), "XclExpPCField::InitStdGroupField - only for standard grouping fields" );

    maFieldInfo.mnBaseItems = rBaseField.GetItemCount();
    maGroupOrder.resize( maFieldInfo.mnBaseItems, EXC_PC_NOITEM );

    // loop over all groups of this field
    for( tools::Long nGroupIdx = 0, nGroupCount = rGroupDim.GetGroupCount(); nGroupIdx < nGroupCount; ++nGroupIdx )
    {
        const ScDPSaveGroupItem& rGroupItem = rGroupDim.GetGroupByIndex( nGroupIdx );
        // the index of the new item containing the grouping name
        sal_uInt16 nGroupItemIdx = EXC_PC_NOITEM;
        // loop over all elements of one group
        for( size_t nElemIdx = 0, nElemCount = rGroupItem.GetElementCount(); nElemIdx < nElemCount; ++nElemIdx )
        {
            if (const OUString* pElemName = rGroupItem.GetElementByIndex(nElemIdx))
            {
                // try to find the item that is part of the group in the base field
                sal_uInt16 nBaseItemIdx = rBaseField.GetItemIndex( *pElemName );
                if( nBaseItemIdx < maFieldInfo.mnBaseItems )
                {
                    // add group name item only if there are any valid base items
                    if( nGroupItemIdx == EXC_PC_NOITEM )
                        nGroupItemIdx = InsertGroupItem( new XclExpPCItem( rGroupItem.GetGroupName() ) );
                    maGroupOrder[ nBaseItemIdx ] = nGroupItemIdx;
                }
            }
        }
    }

    // add items and base item indexes of all ungrouped elements
    for( sal_uInt16 nBaseItemIdx = 0; nBaseItemIdx < maFieldInfo.mnBaseItems; ++nBaseItemIdx )
        // items that are not part of a group still have the EXC_PC_NOITEM entry
        if( maGroupOrder[ nBaseItemIdx ] == EXC_PC_NOITEM )
            // try to find the base item
            if( const XclExpPCItem* pBaseItem = rBaseField.GetItem( nBaseItemIdx ) )
                // create a clone of the base item, insert its index into item order list
                maGroupOrder[ nBaseItemIdx ] = InsertGroupItem( new XclExpPCItem( *pBaseItem ) );
}

void XclExpPCField::InitNumGroupField( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rNumInfo )
{
    OSL_ENSURE( IsStandardField(), "XclExpPCField::InitNumGroupField - only for standard fields" );
    OSL_ENSURE( rNumInfo.mbEnable, "XclExpPCField::InitNumGroupField - numeric grouping not enabled" );

    // new field type, date type, limit settings (min/max/step/auto)
    if( rNumInfo.mbDateValues )
    {
        // special case: group by days with step count
        meFieldType = EXC_PCFIELD_DATEGROUP;
        maNumGroupInfo.SetScDateType( css::sheet::DataPilotFieldGroupBy::DAYS );
        SetDateGroupLimit( rNumInfo, true );
    }
    else
    {
        meFieldType = EXC_PCFIELD_NUMGROUP;
        maNumGroupInfo.SetNumType();
        SetNumGroupLimit( rNumInfo );
    }

    // generate visible items
    InsertNumDateGroupItems( rDPObj, rNumInfo );
}

void XclExpPCField::InitDateGroupField( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nDatePart )
{
    OSL_ENSURE( IsStandardField() || IsStdGroupField(), "XclExpPCField::InitDateGroupField - only for standard fields" );
    OSL_ENSURE( rDateInfo.mbEnable, "XclExpPCField::InitDateGroupField - date grouping not enabled" );

    // new field type
    meFieldType = IsStandardField() ? EXC_PCFIELD_DATEGROUP : EXC_PCFIELD_DATECHILD;

    // date type, limit settings (min/max/step/auto)
    maNumGroupInfo.SetScDateType( nDatePart );
    SetDateGroupLimit( rDateInfo, false );

    // generate visible items
    InsertNumDateGroupItems( rDPObj, rDateInfo, nDatePart );
}

void XclExpPCField::InsertItemArrayIndex( size_t nListPos )
{
    OSL_ENSURE( IsStandardField(), "XclExpPCField::InsertItemArrayIndex - only for standard fields" );
    maIndexVec.push_back( static_cast< sal_uInt16 >( nListPos ) );
}

void XclExpPCField::InsertOrigItem( XclExpPCItem* pNewItem )
{
    size_t nItemIdx = maOrigItemList.GetSize();
    maOrigItemList.AppendNewRecord( pNewItem );
    InsertItemArrayIndex( nItemIdx );
    mnTypeFlags |= pNewItem->GetTypeFlag();
}

void XclExpPCField::InsertOrigTextItem( const OUString& aText )
{
    size_t nPos = 0;
    bool bFound = false;
    // #i76047# maximum item text length in pivot cache is 255
    OUString aShortText = aText.copy( 0, ::std::min(aText.getLength(), EXC_PC_MAXSTRLEN ) );
    for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos )
        if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsText( aShortText )) )
            InsertItemArrayIndex( nPos );
    if( !bFound )
        InsertOrigItem( new XclExpPCItem( aShortText ) );
}

void XclExpPCField::InsertOrigDoubleItem( double fValue, const OUString& rText )
{
    size_t nPos = 0;
    bool bFound = false;
    for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos )
        if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsDouble( fValue )) )
            InsertItemArrayIndex( nPos );
    if( !bFound )
        InsertOrigItem( new XclExpPCItem( fValue, rText ) );
}

void XclExpPCField::InsertOrigDateTimeItem( const DateTime& rDateTime, const OUString& rText )
{
    size_t nPos = 0;
    bool bFound = false;
    for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos )
        if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsDateTime( rDateTime )) )
            InsertItemArrayIndex( nPos );
    if( !bFound )
        InsertOrigItem( new XclExpPCItem( rDateTime, rText ) );
}

void XclExpPCField::InsertOrigBoolItem( bool bValue, const OUString& rText )
{
    size_t nPos = 0;
    bool bFound = false;
    for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos )
        if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsBool( bValue )) )
            InsertItemArrayIndex( nPos );
    if( !bFound )
        InsertOrigItem( new XclExpPCItem( bValue, rText ) );
}

sal_uInt16 XclExpPCField::InsertGroupItem( XclExpPCItem* pNewItem )
{
    maGroupItemList.AppendNewRecord( pNewItem );
    return static_cast< sal_uInt16 >( maGroupItemList.GetSize() - 1 );
}

void XclExpPCField::InsertNumDateGroupItems( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rNumInfo, sal_Int32 nDatePart )
{
    OSL_ENSURE( rDPObj.GetSheetDesc(), "XclExpPCField::InsertNumDateGroupItems - cannot generate element list" );
    const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc();
    if(!pSrcDesc)
        return;

    // get the string collection with original source elements
    const ScDPSaveData* pSaveData = rDPObj.GetSaveData();
    const ScDPDimensionSaveData* pDimData = nullptr;
    if (pSaveData)
        pDimData = pSaveData->GetExistingDimensionData();

    const ScDPCache* pCache = pSrcDesc->CreateCache(pDimData);
    if (!pCache)
        return;

    ScSheetDPData aDPData(&GetDoc(), *pSrcDesc, *pCache);
    tools::Long nDim = GetFieldIndex();
    // get the string collection with generated grouping elements
    ScDPNumGroupDimension aTmpDim( rNumInfo );
    if( nDatePart != 0 )
        aTmpDim.SetDateDimension();
    const std::vector<SCROW>& aMemberIds = aTmpDim.GetNumEntries(
        static_cast<SCCOL>(nDim), pCache);
    for (SCROW nMemberId : aMemberIds)
    {
        const ScDPItemData* pData = aDPData.GetMemberById(nDim, nMemberId);
        if ( pData )
        {
            OUString aStr = pCache->GetFormattedString(nDim, *pData, false);
            InsertGroupItem(new XclExpPCItem(aStr));
        }
    }
}

void XclExpPCField::SetNumGroupLimit( const ScDPNumGroupInfo& rNumInfo )
{
    ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN, rNumInfo.mbAutoStart );
    ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX, rNumInfo.mbAutoEnd );
    maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfStart ) );
    maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfEnd ) );
    maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfStep ) );
}

void XclExpPCField::SetDateGroupLimit( const ScDPNumGroupInfo& rDateInfo, bool bUseStep )
{
    ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN, rDateInfo.mbAutoStart );
    ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX, rDateInfo.mbAutoEnd );
    maNumGroupLimits.AppendNewRecord( new XclExpPCItem( GetDateTimeFromDouble( rDateInfo.mfStart ) ) );
    maNumGroupLimits.AppendNewRecord( new XclExpPCItem( GetDateTimeFromDouble( rDateInfo.mfEnd ) ) );
    sal_Int16 nStep = bUseStep ? limit_cast< sal_Int16 >( rDateInfo.mfStep, 1, SAL_MAX_INT16 ) : 1;
    maNumGroupLimits.AppendNewRecord( new XclExpPCItem( nStep ) );
}

void XclExpPCField::Finalize()
{
    // flags
    ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASITEMS, !GetVisItemList().IsEmpty() );
    // Excel writes long indexes even for 0x0100 items (indexes from 0x00 to 0xFF)
    ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_16BIT, maOrigItemList.GetSize() >= 0x0100 );
    ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_NUMGROUP, IsNumGroupField() || IsDateGroupField() );
    /*  mnTypeFlags is updated in all Insert***Item() functions. Now the flags
        for the current combination of item types is added to the flags. */
    ::set_flag( maFieldInfo.mnFlags, spnPCItemFlags[ mnTypeFlags ] );

    // item count fields
    maFieldInfo.mnVisItems = static_cast< sal_uInt16 >( GetVisItemList().GetSize() );
    maFieldInfo.mnGroupItems = static_cast< sal_uInt16 >( maGroupItemList.GetSize() );
    // maFieldInfo.mnBaseItems set in InitStdGroupField()
    maFieldInfo.mnOrigItems = static_cast< sal_uInt16 >( maOrigItemList.GetSize() );
}

void XclExpPCField::WriteSxnumgroup( XclExpStream& rStrm )
{
    if( IsNumGroupField() || IsDateGroupField() )
    {
        // SXNUMGROUP record
        rStrm.StartRecord( EXC_ID_SXNUMGROUP, 2 );
        rStrm << maNumGroupInfo;
        rStrm.EndRecord();

        // limits (min/max/step) for numeric grouping
        OSL_ENSURE( maNumGroupLimits.GetSize() == 3,
            "XclExpPCField::WriteSxnumgroup - missing numeric grouping limits" );
        maNumGroupLimits.Save( rStrm );
    }
}

void XclExpPCField::WriteSxgroupinfo( XclExpStream& rStrm )
{
    OSL_ENSURE( IsStdGroupField() != maGroupOrder.empty(),
        "XclExpPCField::WriteSxgroupinfo - missing grouping info" );
    if( IsStdGroupField() && !maGroupOrder.empty() )
    {
        rStrm.StartRecord( EXC_ID_SXGROUPINFO, 2 * maGroupOrder.size() );
        for( const auto& rItem : maGroupOrder )
            rStrm << rItem;
        rStrm.EndRecord();
    }
}

void XclExpPCField::WriteBody( XclExpStream& rStrm )
{
    rStrm << maFieldInfo;
}

XclExpPivotCache::XclExpPivotCache( const XclExpRoot& rRoot, const ScDPObject& rDPObj, sal_uInt16 nListIdx ) :
    XclExpRoot( rRoot ),
    mnListIdx( nListIdx ),
    mbValid( false )
{
    // source from sheet only
    const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc();
    if(!pSrcDesc)
        return;

    /*  maOrigSrcRange: Range received from the DataPilot object.
        maExpSrcRange: Range written to the DCONREF record.
        maDocSrcRange: Range used to get source data from Calc document.
            This range may be shorter than maExpSrcRange to improve export
            performance (#i22541#). */
    maOrigSrcRange = maExpSrcRange = maDocSrcRange = pSrcDesc->GetSourceRange();
    maSrcRangeName = pSrcDesc->GetRangeName();

    // internal sheet data only
    SCTAB nScTab = maExpSrcRange.aStart.Tab();
    if( !((nScTab == maExpSrcRange.aEnd.Tab()) && GetTabInfo().IsExportTab( nScTab )) )
        return;

    // ValidateRange() restricts source range to valid Excel limits
    if( !GetAddressConverter().ValidateRange( maExpSrcRange, true ) )
        return;

    // #i22541# skip empty cell areas (performance)
    SCCOL nDocCol1, nDocCol2;
    SCROW nDocRow1, nDocRow2;
    GetDoc().GetDataStart( nScTab, nDocCol1, nDocRow1 );
    GetDoc().GetPrintArea( nScTab, nDocCol2, nDocRow2, false );
    SCCOL nSrcCol1 = maExpSrcRange.aStart.Col();
    SCROW nSrcRow1 = maExpSrcRange.aStart.Row();
    SCCOL nSrcCol2 = maExpSrcRange.aEnd.Col();
    SCROW nSrcRow2 = maExpSrcRange.aEnd.Row();

    // #i22541# do not store index list for too big ranges
    if( 2 * (nDocRow2 - nDocRow1) < (nSrcRow2 - nSrcRow1) )
        ::set_flag( maPCInfo.mnFlags, EXC_SXDB_SAVEDATA, false );

    // adjust row indexes, keep one row of empty area to surely have the empty cache item
    if( nSrcRow1 < nDocRow1 )
        nSrcRow1 = nDocRow1 - 1;
    if( nSrcRow2 > nDocRow2 )
        nSrcRow2 = nDocRow2 + 1;

    maDocSrcRange.aStart.SetCol( ::std::max( nDocCol1, nSrcCol1 ) );
    maDocSrcRange.aStart.SetRow( nSrcRow1 );
    maDocSrcRange.aEnd.SetCol( ::std::min( nDocCol2, nSrcCol2 ) );
    maDocSrcRange.aEnd.SetRow( nSrcRow2 );

    GetDoc().GetName( nScTab, maTabName );
    maPCInfo.mnSrcRecs = static_cast< sal_uInt32 >( maExpSrcRange.aEnd.Row() - maExpSrcRange.aStart.Row() );
    maPCInfo.mnStrmId = nListIdx + 1;
    maPCInfo.mnSrcType = EXC_SXDB_SRC_SHEET;

    AddFields( rDPObj );

    mbValid = true;
}

bool XclExpPivotCache::HasItemIndexList() const
{
    return ::get_flag( maPCInfo.mnFlags, EXC_SXDB_SAVEDATA );
}

sal_uInt16 XclExpPivotCache::GetFieldCount() const
{
    return static_cast< sal_uInt16 >( maFieldList.GetSize() );
}

const XclExpPCField* XclExpPivotCache::GetField( sal_uInt16 nFieldIdx ) const
{
    return maFieldList.GetRecord( nFieldIdx );
}

bool XclExpPivotCache::HasAddFields() const
{
    // pivot cache can be shared, if there are no additional cache fields
    return maPCInfo.mnStdFields < maPCInfo.mnTotalFields;
}

bool XclExpPivotCache::HasEqualDataSource( const ScDPObject& rDPObj ) const
{
    /*  For now, only sheet sources are supported, therefore it is enough to
        compare the ScSheetSourceDesc. Later, there should be done more complicated
        comparisons regarding the source type of rDPObj and this cache. */
    if( const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc() )
        return pSrcDesc->GetSourceRange() == maOrigSrcRange;
    return false;
}

void XclExpPivotCache::Save( XclExpStream& rStrm )
{
    OSL_ENSURE( mbValid, "XclExpPivotCache::Save - invalid pivot cache" );
    // SXIDSTM
    XclExpUInt16Record( EXC_ID_SXIDSTM, maPCInfo.mnStrmId ).Save( rStrm );
    // SXVS
    XclExpUInt16Record( EXC_ID_SXVS, EXC_SXVS_SHEET ).Save( rStrm );

    if (!maSrcRangeName.isEmpty())
        // DCONNAME
        WriteDConName(rStrm);
    else
        // DCONREF
        WriteDconref(rStrm);

    // create the pivot cache storage stream
    WriteCacheStream();
}

void XclExpPivotCache::SaveXml( XclExpXmlStream& /*rStrm*/ )
{
}

void XclExpPivotCache::AddFields( const ScDPObject& rDPObj )
{
    AddStdFields( rDPObj );
    maPCInfo.mnStdFields = GetFieldCount();
    AddGroupFields( rDPObj );
    maPCInfo.mnTotalFields = GetFieldCount();
};

void XclExpPivotCache::AddStdFields( const ScDPObject& rDPObj )
{
    // if item index list is not written, used shortened source range (maDocSrcRange) for performance
    const ScRange& rRange = HasItemIndexList() ? maExpSrcRange : maDocSrcRange;
    // create a standard pivot cache field for each source column
    for( SCCOL nScCol = rRange.aStart.Col(), nEndScCol = rRange.aEnd.Col(); nScCol <= nEndScCol; ++nScCol )
    {
        ScRange aColRange( rRange );
        aColRange.aStart.SetCol( nScCol );
        aColRange.aEnd.SetCol( nScCol );
        maFieldList.AppendNewRecord( new XclExpPCField(
            GetRoot(), GetFieldCount(), rDPObj, aColRange ) );
    }
}

void XclExpPivotCache::AddGroupFields( const ScDPObject& rDPObj )
{
    const ScDPSaveData* pSaveData = rDPObj.GetSaveData();
    if(!pSaveData)
        return;
    const ScDPDimensionSaveData* pSaveDimData = pSaveData->GetExistingDimensionData();
    if( !pSaveDimData )
        return;

    // loop over all existing standard fields to find their group fields
    for( sal_uInt16 nFieldIdx = 0; nFieldIdx < maPCInfo.mnStdFields; ++nFieldIdx )
    {
        if( XclExpPCField* pCurrStdField = maFieldList.GetRecord( nFieldIdx ) )
        {
            const ScDPSaveGroupDimension* pGroupDim = pSaveDimData->GetGroupDimForBase( pCurrStdField->GetFieldName() );
            XclExpPCField* pLastGroupField = pCurrStdField;
            while( pGroupDim )
            {
                // insert the new grouping field
                XclExpPCFieldRef xNewGroupField = new XclExpPCField(
                    GetRoot(), GetFieldCount(), rDPObj, *pGroupDim, *pCurrStdField );
                maFieldList.AppendRecord( xNewGroupField );

                // register new grouping field at current grouping field, building a chain
                pLastGroupField->SetGroupChildField( *xNewGroupField );

                // next grouping dimension
                pGroupDim = pSaveDimData->GetGroupDimForBase( pGroupDim->GetGroupDimName() );
                pLastGroupField = xNewGroupField.get();
            }
        }
    }
}

void XclExpPivotCache::WriteDconref( XclExpStream& rStrm ) const
{
    XclExpString aRef( XclExpUrlHelper::EncodeUrl( GetRoot(), u"", &maTabName ) );
    rStrm.StartRecord( EXC_ID_DCONREF, 7 + aRef.GetSize() );
    rStrm   << static_cast< sal_uInt16 >( maExpSrcRange.aStart.Row() )
            << static_cast< sal_uInt16 >( maExpSrcRange.aEnd.Row() )
            << static_cast< sal_uInt8 >( maExpSrcRange.aStart.Col() )
            << static_cast< sal_uInt8 >( maExpSrcRange.aEnd.Col() )
            << aRef
            << sal_uInt8( 0 );
    rStrm.EndRecord();
}

void XclExpPivotCache::WriteDConName( XclExpStream& rStrm ) const
{
    XclExpString aName(maSrcRangeName);
    rStrm.StartRecord(EXC_ID_DCONNAME, aName.GetSize() + 2);
    rStrm << aName << sal_uInt16(0);
    rStrm.EndRecord();
}

void XclExpPivotCache::WriteCacheStream()
{
    rtl::Reference<SotStorage> xSvStrg = OpenStorage(EXC_STORAGE_PTCACHE);
    rtl::Reference<SotStorageStream> xSvStrm = OpenStream( xSvStrg, ScfTools::GetHexStr( maPCInfo.mnStrmId ) );
    if( !xSvStrm.is() )
        return;

    XclExpStream aStrm( *xSvStrm, GetRoot() );
    // SXDB
    WriteSxdb( aStrm );
    // SXDBEX
    WriteSxdbex( aStrm );
    // field list (SXFIELD and items)
    maFieldList.Save( aStrm );
    // index table (list of SXINDEXLIST)
    WriteSxindexlistList( aStrm );
    // EOF
    XclExpEmptyRecord( EXC_ID_EOF ).Save( aStrm );
}

void XclExpPivotCache::WriteSxdb( XclExpStream& rStrm ) const
{
    rStrm.StartRecord( EXC_ID_SXDB, 21 );
    rStrm << maPCInfo;
    rStrm.EndRecord();
}

void XclExpPivotCache::WriteSxdbex( XclExpStream& rStrm )
{
    rStrm.StartRecord( EXC_ID_SXDBEX, 12 );
    rStrm   << EXC_SXDBEX_CREATION_DATE
            << sal_uInt32( 0 );             // number of SXFORMULA records
    rStrm.EndRecord();
}

void XclExpPivotCache::WriteSxindexlistList( XclExpStream& rStrm ) const
{
    if( !HasItemIndexList() )
        return;

    std::size_t nRecSize = 0;
    size_t nPos, nSize = maFieldList.GetSize();
    for( nPos = 0; nPos < nSize; ++nPos )
        nRecSize += maFieldList.GetRecord( nPos )->GetIndexSize();

    for( sal_uInt32 nSrcRow = 0; nSrcRow < maPCInfo.mnSrcRecs; ++nSrcRow )
    {
        rStrm.StartRecord( EXC_ID_SXINDEXLIST, nRecSize );
        for( nPos = 0; nPos < nSize; ++nPos )
            maFieldList.GetRecord( nPos )->WriteIndex( rStrm, nSrcRow );
        rStrm.EndRecord();
    }
}

// Pivot table

namespace {

/** Returns a display string for a data field containing the field name and aggregation function. */
OUString lclGetDataFieldCaption( std::u16string_view rFieldName, ScGeneralFunction eFunc )
{
    OUString aCaption;

    TranslateId pResIdx;
    switch( eFunc )
    {
        case ScGeneralFunction::SUM:       pResIdx = STR_FUN_TEXT_SUM;     break;
        case ScGeneralFunction::COUNT:     pResIdx = STR_FUN_TEXT_COUNT;   break;
        case ScGeneralFunction::AVERAGE:   pResIdx = STR_FUN_TEXT_AVG;     break;
        case ScGeneralFunction::MAX:       pResIdx = STR_FUN_TEXT_MAX;     break;
        case ScGeneralFunction::MIN:       pResIdx = STR_FUN_TEXT_MIN;     break;
        case ScGeneralFunction::PRODUCT:   pResIdx = STR_FUN_TEXT_PRODUCT; break;
        case ScGeneralFunction::COUNTNUMS: pResIdx = STR_FUN_TEXT_COUNT;   break;
        case ScGeneralFunction::STDEV:     pResIdx = STR_FUN_TEXT_STDDEV;  break;
        case ScGeneralFunction::STDEVP:    pResIdx = STR_FUN_TEXT_STDDEV;  break;
        case ScGeneralFunction::VAR:       pResIdx = STR_FUN_TEXT_VAR;     break;
        case ScGeneralFunction::VARP:      pResIdx = STR_FUN_TEXT_VAR;     break;
        default:;
    }
    if (pResIdx)
        aCaption = ScResId(pResIdx) + " - ";
    aCaption += rFieldName;
    return aCaption;
}

} // namespace

XclExpPTItem::XclExpPTItem( const XclExpPCField& rCacheField, sal_uInt16 nCacheIdx ) :
    XclExpRecord( EXC_ID_SXVI, 8 ),
    mpCacheItem( rCacheField.GetItem( nCacheIdx ) )
{
    maItemInfo.mnType = EXC_SXVI_TYPE_DATA;
    maItemInfo.mnCacheIdx = nCacheIdx;
    maItemInfo.maVisName.mbUseCache = mpCacheItem != nullptr;
}

XclExpPTItem::XclExpPTItem( sal_uInt16 nItemType, sal_uInt16 nCacheIdx ) :
    XclExpRecord( EXC_ID_SXVI, 8 ),
    mpCacheItem( nullptr )
{
    maItemInfo.mnType = nItemType;
    maItemInfo.mnCacheIdx = nCacheIdx;
    maItemInfo.maVisName.mbUseCache = true;
}

OUString XclExpPTItem::GetItemName() const
{
    return mpCacheItem ? mpCacheItem->ConvertToText() : OUString();
}

void XclExpPTItem::SetPropertiesFromMember( const ScDPSaveMember& rSaveMem )
{
    // #i115659# GetIsVisible() is not valid if HasIsVisible() returns false, default is 'visible' then
    ::set_flag( maItemInfo.mnFlags, EXC_SXVI_HIDDEN, rSaveMem.HasIsVisible() && !rSaveMem.GetIsVisible() );
    // #i115659# GetShowDetails() is not valid if HasShowDetails() returns false, default is 'show detail' then
    ::set_flag( maItemInfo.mnFlags, EXC_SXVI_HIDEDETAIL, rSaveMem.HasShowDetails() && !rSaveMem.GetShowDetails() );

    // visible name
    const std::optional<OUString> & pVisName = rSaveMem.GetLayoutName();
    if (pVisName && *pVisName != GetItemName())
        maItemInfo.SetVisName(*pVisName);
}

void XclExpPTItem::WriteBody( XclExpStream& rStrm )
{
    rStrm << maItemInfo;
}

XclExpPTField::XclExpPTField( const XclExpPivotTable& rPTable, sal_uInt16 nCacheIdx ) :
    mrPTable( rPTable ),
    mpCacheField( rPTable.GetCacheField( nCacheIdx ) )
{
    maFieldInfo.mnCacheIdx = nCacheIdx;

    // create field items
    if( mpCacheField )
        for( sal_uInt16 nItemIdx = 0, nItemCount = mpCacheField->GetItemCount(); nItemIdx < nItemCount; ++nItemIdx )
            maItemList.AppendNewRecord( new XclExpPTItem( *mpCacheField, nItemIdx ) );
    maFieldInfo.mnItemCount = static_cast< sal_uInt16 >( maItemList.GetSize() );
}

// data access ----------------------------------------------------------------

OUString XclExpPTField::GetFieldName() const
{
    return mpCacheField ? mpCacheField->GetFieldName() : OUString();
}

sal_uInt16 XclExpPTField::GetLastDataInfoIndex() const
{
    OSL_ENSURE( !maDataInfoVec.empty(), "XclExpPTField::GetLastDataInfoIndex - no data info found" );
    // will return 0xFFFF for empty vector -> ok
    return static_cast< sal_uInt16 >( maDataInfoVec.size() - 1 );
}

sal_uInt16 XclExpPTField::GetItemIndex( std::u16string_view rName, sal_uInt16 nDefaultIdx ) const
{
    for( size_t nPos = 0, nSize = maItemList.GetSize(); nPos < nSize; ++nPos )
        if( maItemList.GetRecord( nPos )->GetItemName() == rName )
            return static_cast< sal_uInt16 >( nPos );
    return nDefaultIdx;
}

// fill data --------------------------------------------------------------

/**
 * Calc's subtotal names are escaped with backslashes ('\'), while Excel's
 * are not escaped at all.
 */
static OUString lcl_convertCalcSubtotalName(const OUString& rName)
{
    OUStringBuffer aBuf;
    const sal_Unicode* p = rName.getStr();
    sal_Int32 n = rName.getLength();
    bool bEscaped = false;
    for (sal_Int32 i = 0; i < n; ++i)
    {
        const sal_Unicode c = p[i];
        if (!bEscaped && c == '\\')
        {
            bEscaped = true;
            continue;
        }

        aBuf.append(c);
        bEscaped = false;
    }
    return aBuf.makeStringAndClear();
}

void XclExpPTField::SetPropertiesFromDim( const ScDPSaveDimension& rSaveDim )
{
    // orientation
    DataPilotFieldOrientation eOrient = rSaveDim.GetOrientation();
    OSL_ENSURE( eOrient != DataPilotFieldOrientation_DATA, "XclExpPTField::SetPropertiesFromDim - called for data field" );
    maFieldInfo.AddApiOrient( eOrient );

    // show empty items (#i115659# GetShowEmpty() is not valid if HasShowEmpty() returns false, default is false then)
    ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SHOWALL, rSaveDim.HasShowEmpty() && rSaveDim.GetShowEmpty() );

    // visible name
    const std::optional<OUString> & pLayoutName = rSaveDim.GetLayoutName();
    if (pLayoutName && *pLayoutName != GetFieldName())
        maFieldInfo.SetVisName(*pLayoutName);

    const std::optional<OUString> & pSubtotalName = rSaveDim.GetSubtotalName();
    if (pSubtotalName)
    {
        OUString aSubName = lcl_convertCalcSubtotalName(*pSubtotalName);
        maFieldExtInfo.mpFieldTotalName = aSubName;
    }

    // subtotals
    XclPTSubtotalVec aSubtotals;
    aSubtotals.reserve( static_cast< size_t >( rSaveDim.GetSubTotalsCount() ) );
    for( tools::Long nSubtIdx = 0, nSubtCount = rSaveDim.GetSubTotalsCount(); nSubtIdx < nSubtCount; ++nSubtIdx )
        aSubtotals.push_back( rSaveDim.GetSubTotalFunc( nSubtIdx ) );
    maFieldInfo.SetSubtotals( aSubtotals );

    // sorting
    if( const DataPilotFieldSortInfo* pSortInfo = rSaveDim.GetSortInfo() )
    {
        maFieldExtInfo.SetApiSortMode( pSortInfo->Mode );
        if( pSortInfo->Mode == css::sheet::DataPilotFieldSortMode::DATA )
            maFieldExtInfo.mnSortField = mrPTable.GetDataFieldIndex( pSortInfo->Field, EXC_SXVDEX_SORT_OWN );
        ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SORT_ASC, pSortInfo->IsAscending );
    }

    // auto show
    if( const DataPilotFieldAutoShowInfo* pShowInfo = rSaveDim.GetAutoShowInfo() )
    {
        ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_AUTOSHOW, pShowInfo->IsEnabled );
        maFieldExtInfo.SetApiAutoShowMode( pShowInfo->ShowItemsMode );
        maFieldExtInfo.SetApiAutoShowCount( pShowInfo->ItemCount );
        maFieldExtInfo.mnShowField = mrPTable.GetDataFieldIndex( pShowInfo->DataField, EXC_SXVDEX_SHOW_NONE );
    }

    // layout
    if( const DataPilotFieldLayoutInfo* pLayoutInfo = rSaveDim.GetLayoutInfo() )
    {
        maFieldExtInfo.SetApiLayoutMode( pLayoutInfo->LayoutMode );
        ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_LAYOUT_BLANK, pLayoutInfo->AddEmptyLines );
    }

    // special page field properties
    if( eOrient == DataPilotFieldOrientation_PAGE )
    {
        maPageInfo.mnField = GetFieldIndex();
        maPageInfo.mnSelItem = EXC_SXPI_ALLITEMS;
    }

    // item properties
    const ScDPSaveDimension::MemberList &rMembers = rSaveDim.GetMembers();
    for (const auto& pMember : rMembers)
        if( XclExpPTItem* pItem = GetItemAcc( pMember->GetName() ) )
            pItem->SetPropertiesFromMember( *pMember );
}

void XclExpPTField::SetDataPropertiesFromDim( const ScDPSaveDimension& rSaveDim )
{
    maDataInfoVec.emplace_back( );
    XclPTDataFieldInfo& rDataInfo = maDataInfoVec.back();
    rDataInfo.mnField = GetFieldIndex();

    // orientation
    maFieldInfo.AddApiOrient( DataPilotFieldOrientation_DATA );

    // aggregation function
    ScGeneralFunction eFunc = rSaveDim.GetFunction();
    rDataInfo.SetApiAggFunc( eFunc );

    // visible name
    const std::optional<OUString> & pVisName = rSaveDim.GetLayoutName();
    if (pVisName)
        rDataInfo.SetVisName(*pVisName);
    else
        rDataInfo.SetVisName( lclGetDataFieldCaption( GetFieldName(), eFunc ) );

    // result field reference
    if( const DataPilotFieldReference* pFieldRef = rSaveDim.GetReferenceValue() )
    {
        rDataInfo.SetApiRefType( pFieldRef->ReferenceType );
        rDataInfo.SetApiRefItemType( pFieldRef->ReferenceItemType );
        if( const XclExpPTField* pRefField = mrPTable.GetField( pFieldRef->ReferenceField ) )
        {
            rDataInfo.mnRefField = pRefField->GetFieldIndex();
            if( pFieldRef->ReferenceItemType == css::sheet::DataPilotFieldReferenceItemType::NAMED )
                rDataInfo.mnRefItem = pRefField->GetItemIndex( pFieldRef->ReferenceItemName, 0 );
        }
    }
}

void XclExpPTField::AppendSubtotalItems()
{
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_DEFAULT )   AppendSubtotalItem( EXC_SXVI_TYPE_DEFAULT );
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_SUM )       AppendSubtotalItem( EXC_SXVI_TYPE_SUM );
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_COUNT )     AppendSubtotalItem( EXC_SXVI_TYPE_COUNT );
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_AVERAGE )   AppendSubtotalItem( EXC_SXVI_TYPE_AVERAGE );
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_MAX )       AppendSubtotalItem( EXC_SXVI_TYPE_MAX );
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_MIN )       AppendSubtotalItem( EXC_SXVI_TYPE_MIN );
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_PROD )      AppendSubtotalItem( EXC_SXVI_TYPE_PROD );
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_COUNTNUM )  AppendSubtotalItem( EXC_SXVI_TYPE_COUNTNUM );
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_STDDEV )    AppendSubtotalItem( EXC_SXVI_TYPE_STDDEV );
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_STDDEVP )   AppendSubtotalItem( EXC_SXVI_TYPE_STDDEVP );
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_VAR )       AppendSubtotalItem( EXC_SXVI_TYPE_VAR );
    if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_VARP )      AppendSubtotalItem( EXC_SXVI_TYPE_VARP );
}

// records --------------------------------------------------------------------

void XclExpPTField::WriteSxpiEntry( XclExpStream& rStrm ) const
{
    rStrm << maPageInfo;
}

void XclExpPTField::WriteSxdi( XclExpStream& rStrm, sal_uInt16 nDataInfoIdx ) const
{
    OSL_ENSURE( nDataInfoIdx < maDataInfoVec.size(), "XclExpPTField::WriteSxdi - data field not found" );
    if( nDataInfoIdx < maDataInfoVec.size() )
    {
        rStrm.StartRecord( EXC_ID_SXDI, 12 );
        rStrm << maDataInfoVec[ nDataInfoIdx ];
        rStrm.EndRecord();
    }
}

void XclExpPTField::Save( XclExpStream& rStrm )
{
    // SXVD
    WriteSxvd( rStrm );
    // list of SXVI records
    maItemList.Save( rStrm );
    // SXVDEX
    WriteSxvdex( rStrm );
}

// private --------------------------------------------------------------------

XclExpPTItem* XclExpPTField::GetItemAcc( std::u16string_view rName )
{
    XclExpPTItem* pItem = nullptr;
    for( size_t nPos = 0, nSize = maItemList.GetSize(); !pItem && (nPos < nSize); ++nPos )
        if( maItemList.GetRecord( nPos )->GetItemName() == rName )
            pItem = maItemList.GetRecord( nPos );
    return pItem;
}

void XclExpPTField::AppendSubtotalItem( sal_uInt16 nItemType )
{
    maItemList.AppendNewRecord( new XclExpPTItem( nItemType, EXC_SXVI_DEFAULT_CACHE ) );
    ++maFieldInfo.mnItemCount;
}

void XclExpPTField::WriteSxvd( XclExpStream& rStrm ) const
{
    rStrm.StartRecord( EXC_ID_SXVD, 10 );
    rStrm << maFieldInfo;
    rStrm.EndRecord();
}

void XclExpPTField::WriteSxvdex( XclExpStream& rStrm ) const
{
    rStrm.StartRecord( EXC_ID_SXVDEX, 20 );
    rStrm << maFieldExtInfo;
    rStrm.EndRecord();
}

XclExpPivotTable::XclExpPivotTable( const XclExpRoot& rRoot, const ScDPObject& rDPObj, const XclExpPivotCache& rPCache ) :
    XclExpRoot( rRoot ),
    mrPCache( rPCache ),
    maDataOrientField( *this, EXC_SXIVD_DATA ),
    mnOutScTab( 0 ),
    mbValid( false ),
    mbFilterBtn( false )
{
    const ScRange& rOutScRange = rDPObj.GetOutRange();
    if( !GetAddressConverter().ConvertRange( maPTInfo.maOutXclRange, rOutScRange, true ) )
        return;

    // DataPilot properties -----------------------------------------------

    // pivot table properties from DP object
    mnOutScTab = rOutScRange.aStart.Tab();
    maPTInfo.maTableName = rDPObj.GetName();
    maPTInfo.mnCacheIdx = mrPCache.GetCacheIndex();

    maPTViewEx9Info.Init( rDPObj );

    const ScDPSaveData* pSaveData = rDPObj.GetSaveData();
    if( !pSaveData )
        return;

    // additional properties from ScDPSaveData
    SetPropertiesFromDP( *pSaveData );

    // loop over all dimensions ---------------------------------------

    /*  1)  Default-construct all pivot table fields for all pivot cache fields. */
    for( sal_uInt16 nFieldIdx = 0, nFieldCount = mrPCache.GetFieldCount(); nFieldIdx < nFieldCount; ++nFieldIdx )
        maFieldList.AppendNewRecord( new XclExpPTField( *this, nFieldIdx ) );

    const ScDPSaveData::DimsType& rDimList = pSaveData->GetDimensions();

    /*  2)  First process all data dimensions, they are needed for extended
            settings of row/column/page fields (sorting/auto show). */
    for (auto const& iter : rDimList)
    {
        if (iter->GetOrientation() == DataPilotFieldOrientation_DATA)
            SetDataFieldPropertiesFromDim(*iter);
    }

    /*  3)  Row/column/page/hidden fields. */
    for (auto const& iter : rDimList)
    {
        if (iter->GetOrientation() != DataPilotFieldOrientation_DATA)
            SetFieldPropertiesFromDim(*iter);
    }

    // Finalize -------------------------------------------------------

    Finalize();
    mbValid = true;
}

const XclExpPCField* XclExpPivotTable::GetCacheField( sal_uInt16 nCacheIdx ) const
{
    return mrPCache.GetField( nCacheIdx );
}

const XclExpPTField* XclExpPivotTable::GetField( sal_uInt16 nFieldIdx ) const
{
    return (nFieldIdx == EXC_SXIVD_DATA) ? &maDataOrientField : maFieldList.GetRecord( nFieldIdx );
}

const XclExpPTField* XclExpPivotTable::GetField( std::u16string_view rName ) const
{
    return const_cast< XclExpPivotTable* >( this )->GetFieldAcc( rName );
}

sal_uInt16 XclExpPivotTable::GetDataFieldIndex( const OUString& rName, sal_uInt16 nDefaultIdx ) const
{
    auto aIt = std::find_if(maDataFields.begin(), maDataFields.end(),
        [this, &rName](const XclPTDataFieldPos& rDataField) {
            const XclExpPTField* pField = GetField( rDataField.first );
            return pField && pField->GetFieldName() == rName;
        });
    if (aIt != maDataFields.end())
        return static_cast< sal_uInt16 >( std::distance(maDataFields.begin(), aIt) );
    return nDefaultIdx;
}

void XclExpPivotTable::Save( XclExpStream& rStrm )
{
    if( !mbValid )
        return;

    // SXVIEW
    WriteSxview( rStrm );
    // pivot table fields (SXVD, SXVDEX, and item records)
    maFieldList.Save( rStrm );
    // SXIVD records for row and column fields
    WriteSxivd( rStrm, maRowFields );
    WriteSxivd( rStrm, maColFields );
    // SXPI
    WriteSxpi( rStrm );
    // list of SXDI records containing data field info
    WriteSxdiList( rStrm );
    // SXLI records
    WriteSxli( rStrm, maPTInfo.mnDataRows, maPTInfo.mnRowFields );
    WriteSxli( rStrm, maPTInfo.mnDataCols, maPTInfo.mnColFields );
    // SXEX
    WriteSxex( rStrm );
    // QSISXTAG
    WriteQsiSxTag( rStrm );
    // SXVIEWEX9
    WriteSxViewEx9( rStrm );
}

XclExpPTField* XclExpPivotTable::GetFieldAcc( std::u16string_view rName )
{
    XclExpPTField* pField = nullptr;
    for( size_t nPos = 0, nSize = maFieldList.GetSize(); !pField && (nPos < nSize); ++nPos )
        if( maFieldList.GetRecord( nPos )->GetFieldName() == rName )
            pField = maFieldList.GetRecord( nPos );
    return pField;
}

XclExpPTField* XclExpPivotTable::GetFieldAcc( const ScDPSaveDimension& rSaveDim )
{
    // data field orientation field?
    if( rSaveDim.IsDataLayout() )
        return &maDataOrientField;

    // a real dimension
    OUString aFieldName = ScDPUtil::getSourceDimensionName(rSaveDim.GetName());
    return aFieldName.isEmpty() ? nullptr : GetFieldAcc(aFieldName);
}

// fill data --------------------------------------------------------------

void XclExpPivotTable::SetPropertiesFromDP( const ScDPSaveData& rSaveData )
{
    ::set_flag( maPTInfo.mnFlags, EXC_SXVIEW_ROWGRAND, rSaveData.GetRowGrand() );
    ::set_flag( maPTInfo.mnFlags, EXC_SXVIEW_COLGRAND, rSaveData.GetColumnGrand() );
    ::set_flag( maPTExtInfo.mnFlags, EXC_SXEX_DRILLDOWN, rSaveData.GetDrillDown() );
    mbFilterBtn = rSaveData.GetFilterButton();
    const ScDPSaveDimension* pDim = rSaveData.GetExistingDataLayoutDimension();

    if (pDim && pDim->GetLayoutName())
        maPTInfo.maDataName = *pDim->GetLayoutName();
    else
        maPTInfo.maDataName = ScResId(STR_PIVOT_DATA);
}

void XclExpPivotTable::SetFieldPropertiesFromDim( const ScDPSaveDimension& rSaveDim )
{
    XclExpPTField* pField = GetFieldAcc( rSaveDim );
    if(!pField)
        return;

    // field properties
    pField->SetPropertiesFromDim( rSaveDim );

    // update the corresponding field position list
    DataPilotFieldOrientation eOrient = rSaveDim.GetOrientation();
    sal_uInt16 nFieldIdx = pField->GetFieldIndex();
    bool bDataLayout = nFieldIdx == EXC_SXIVD_DATA;
    bool bMultiData = maDataFields.size() > 1;

    if( bDataLayout && !bMultiData )
        return;

    switch( eOrient )
    {
        case DataPilotFieldOrientation_ROW:
            maRowFields.push_back( nFieldIdx );
            if( bDataLayout )
                maPTInfo.mnDataAxis = EXC_SXVD_AXIS_ROW;
        break;
        case DataPilotFieldOrientation_COLUMN:
            maColFields.push_back( nFieldIdx );
            if( bDataLayout )
                maPTInfo.mnDataAxis = EXC_SXVD_AXIS_COL;
        break;
        case DataPilotFieldOrientation_PAGE:
            maPageFields.push_back( nFieldIdx );
            OSL_ENSURE( !bDataLayout, "XclExpPivotTable::SetFieldPropertiesFromDim - wrong orientation for data fields" );
        break;
        case DataPilotFieldOrientation_DATA:
            OSL_FAIL( "XclExpPivotTable::SetFieldPropertiesFromDim - called for data field" );
        break;
        default:;
    }
}

void XclExpPivotTable::SetDataFieldPropertiesFromDim( const ScDPSaveDimension& rSaveDim )
{
    if( XclExpPTField* pField = GetFieldAcc( rSaveDim ) )
    {
        // field properties
        pField->SetDataPropertiesFromDim( rSaveDim );
        // update the data field position list
        maDataFields.emplace_back( pField->GetFieldIndex(), pField->GetLastDataInfoIndex() );
    }
}

void XclExpPivotTable::Finalize()
{
    // field numbers
    maPTInfo.mnFields = static_cast< sal_uInt16 >( maFieldList.GetSize() );
    maPTInfo.mnRowFields = static_cast< sal_uInt16 >( maRowFields.size() );
    maPTInfo.mnColFields = static_cast< sal_uInt16 >( maColFields.size() );
    maPTInfo.mnPageFields = static_cast< sal_uInt16 >( maPageFields.size() );
    maPTInfo.mnDataFields = static_cast< sal_uInt16 >( maDataFields.size() );

    maPTExtInfo.mnPagePerRow = maPTInfo.mnPageFields;
    maPTExtInfo.mnPagePerCol = (maPTInfo.mnPageFields > 0) ? 1 : 0;

    // subtotal items
    for( size_t nPos = 0, nSize = maFieldList.GetSize(); nPos < nSize; ++nPos )
        maFieldList.GetRecord( nPos )->AppendSubtotalItems();

    // find data field orientation field
    maPTInfo.mnDataPos = EXC_SXVIEW_DATALAST;
    const ScfUInt16Vec* pFieldVec = nullptr;
    switch( maPTInfo.mnDataAxis )
    {
        case EXC_SXVD_AXIS_ROW: pFieldVec = &maRowFields;   break;
        case EXC_SXVD_AXIS_COL: pFieldVec = &maColFields;   break;
    }

    if( pFieldVec && !pFieldVec->empty() && (pFieldVec->back() != EXC_SXIVD_DATA) )
    {
        ScfUInt16Vec::const_iterator aIt = ::std::find( pFieldVec->begin(), pFieldVec->end(), EXC_SXIVD_DATA );
        if( aIt != pFieldVec->end() )
            maPTInfo.mnDataPos = static_cast< sal_uInt16 >( std::distance(pFieldVec->begin(), aIt) );
    }

    // single data field is always row oriented
    if( maPTInfo.mnDataAxis == EXC_SXVD_AXIS_NONE )
        maPTInfo.mnDataAxis = EXC_SXVD_AXIS_ROW;

    // update output range (initialized in ctor)
    sal_uInt16& rnXclCol1 = maPTInfo.maOutXclRange.maFirst.mnCol;
    sal_uInt32& rnXclRow1 = maPTInfo.maOutXclRange.maFirst.mnRow;
    sal_uInt16& rnXclCol2 = maPTInfo.maOutXclRange.maLast.mnCol;
    sal_uInt32& rnXclRow2 = maPTInfo.maOutXclRange.maLast.mnRow;
    // exclude page fields from output range
    rnXclRow1 = rnXclRow1 + maPTInfo.mnPageFields;
    // exclude filter button from output range
    if( mbFilterBtn )
        ++rnXclRow1;
    // exclude empty row between (filter button and/or page fields) and table
    if( mbFilterBtn || maPTInfo.mnPageFields )
        ++rnXclRow1;

    // data area
    sal_uInt16& rnDataXclCol = maPTInfo.maDataXclPos.mnCol;
    sal_uInt32& rnDataXclRow = maPTInfo.maDataXclPos.mnRow;
    rnDataXclCol = rnXclCol1 + maPTInfo.mnRowFields;
    rnDataXclRow = rnXclRow1 + maPTInfo.mnColFields + 1;
    if( maDataFields.empty() )
        ++rnDataXclRow;

    bool bExtraHeaderRow = (0 == maPTViewEx9Info.mnGridLayout && maPTInfo.mnColFields == 0);
    if (bExtraHeaderRow)
        // Insert an extra row only when there is no column field.
        ++rnDataXclRow;

    rnXclCol2 = ::std::max( rnXclCol2, rnDataXclCol );
    rnXclRow2 = ::std::max( rnXclRow2, rnDataXclRow );
    maPTInfo.mnDataCols = rnXclCol2 - rnDataXclCol + 1;
    maPTInfo.mnDataRows = rnXclRow2 - rnDataXclRow + 1;

    // first heading
    maPTInfo.mnFirstHeadRow = rnXclRow1 + 1;
    if (bExtraHeaderRow)
        maPTInfo.mnFirstHeadRow += 1;
}

// records ----------------------------------------------------------------

void XclExpPivotTable::WriteSxview( XclExpStream& rStrm ) const
{
    rStrm.StartRecord( EXC_ID_SXVIEW, 46 + maPTInfo.maTableName.getLength() + maPTInfo.maDataName.getLength() );
    rStrm << maPTInfo;
    rStrm.EndRecord();
}

void XclExpPivotTable::WriteSxivd( XclExpStream& rStrm, const ScfUInt16Vec& rFields )
{
    if( !rFields.empty() )
    {
        rStrm.StartRecord( EXC_ID_SXIVD, rFields.size() * 2 );
        for( const auto& rField : rFields )
            rStrm << rField;
        rStrm.EndRecord();
    }
}

void XclExpPivotTable::WriteSxpi( XclExpStream& rStrm ) const
{
    if( !maPageFields.empty() )
    {
        rStrm.StartRecord( EXC_ID_SXPI, maPageFields.size() * 6 );
        rStrm.SetSliceSize( 6 );
        for( const auto& rPageField : maPageFields )
        {
            XclExpPTFieldRef xField = maFieldList.GetRecord( rPageField );
            if( xField )
                xField->WriteSxpiEntry( rStrm );
        }
        rStrm.EndRecord();
    }
}

void XclExpPivotTable::WriteSxdiList( XclExpStream& rStrm ) const
{
    for( const auto& [rFieldIdx, rDataInfoIdx] : maDataFields )
    {
        XclExpPTFieldRef xField = maFieldList.GetRecord( rFieldIdx );
        if( xField )
            xField->WriteSxdi( rStrm, rDataInfoIdx );
    }
}

void XclExpPivotTable::WriteSxli( XclExpStream& rStrm, sal_uInt16 nLineCount, sal_uInt16 nIndexCount )
{
    if( nLineCount <= 0 )
        return;

    std::size_t nLineSize = 8 + 2 * nIndexCount;
    rStrm.StartRecord( EXC_ID_SXLI, nLineSize * nLineCount );

    /*  Excel expects the records to be filled completely, do not
        set a segment size... */
//        rStrm.SetSliceSize( nLineSize );

    for( sal_uInt16 nLine = 0; nLine < nLineCount; ++nLine )
    {
        // Excel XP needs a partly initialized SXLI record
        rStrm   << sal_uInt16( 0 )      // number of equal index entries
                << EXC_SXVI_TYPE_DATA
                << nIndexCount
                << EXC_SXLI_DEFAULTFLAGS;
        rStrm.WriteZeroBytes( 2 * nIndexCount );
    }
    rStrm.EndRecord();
}

void XclExpPivotTable::WriteSxex( XclExpStream& rStrm ) const
{
    rStrm.StartRecord( EXC_ID_SXEX, 24 );
    rStrm << maPTExtInfo;
    rStrm.EndRecord();
}

void XclExpPivotTable::WriteQsiSxTag( XclExpStream& rStrm ) const
{
    rStrm.StartRecord( 0x0802, 32 );

    sal_uInt16 const nRecordType = 0x0802;
    sal_uInt16 const nDummyFlags = 0x0000;
    sal_uInt16 const nTableType  = 1; // 0 = query table : 1 = pivot table

    rStrm << nRecordType << nDummyFlags << nTableType;

    // General flags
    sal_uInt16 const nFlags = 0x0001;
#if 0
    // for doc purpose
    sal_uInt16 nFlags = 0x0000;
    bool bEnableRefresh = true;
    bool bPCacheInvalid = false;
    bool bOlapPTReport  = false;

    if (bEnableRefresh) nFlags |= 0x0001;
    if (bPCacheInvalid) nFlags |= 0x0002;
    if (bOlapPTReport)  nFlags |= 0x0004;
#endif
    rStrm << nFlags;

    // Feature-specific options.  The value differs depending on the table
    // type, but we assume the table type is always pivot table.
    sal_uInt32 const nOptions = 0x00000000;
#if 0
    // documentation for which bit is for what
    bool bNoStencil = false;
    bool bHideTotal = false;
    bool bEmptyRows = false;
    bool bEmptyCols = false;
    if (bNoStencil) nOptions |= 0x00000001;
    if (bHideTotal) nOptions |= 0x00000002;
    if (bEmptyRows) nOptions |= 0x00000008;
    if (bEmptyCols) nOptions |= 0x00000010;
#endif
    rStrm << nOptions;

    sal_uInt8 eXclVer = 0; // Excel2000
    sal_uInt8 const nOffsetBytes = 16;
    rStrm << eXclVer  // version table last refreshed
          << eXclVer  // minimum version to refresh
          << nOffsetBytes
          << eXclVer; // first version created

    rStrm << XclExpString(maPTInfo.maTableName);
    rStrm << static_cast<sal_uInt16>(0x0001); // no idea what this is for.

    rStrm.EndRecord();
}

void XclExpPivotTable::WriteSxViewEx9( XclExpStream& rStrm ) const
{
    // Until we sync the autoformat ids only export if using grid header layout
    // That could only have been set via xls import so far.
    if ( 0 == maPTViewEx9Info.mnGridLayout )
    {
        rStrm.StartRecord( EXC_ID_SXVIEWEX9, 17 );
        rStrm << maPTViewEx9Info;
        rStrm.EndRecord();
    }
}

namespace {

const SCTAB EXC_PTMGR_PIVOTCACHES = SCTAB_MAX;

/** Record wrapper class to write the pivot caches or pivot tables. */
class XclExpPivotRecWrapper : public XclExpRecordBase
{
public:
    explicit            XclExpPivotRecWrapper( XclExpPivotTableManager& rPTMgr, SCTAB nScTab );
    virtual void        Save( XclExpStream& rStrm ) override;
private:
    XclExpPivotTableManager& mrPTMgr;
    SCTAB               mnScTab;
};

XclExpPivotRecWrapper::XclExpPivotRecWrapper( XclExpPivotTableManager& rPTMgr, SCTAB nScTab ) :
    mrPTMgr( rPTMgr ),
    mnScTab( nScTab )
{
}

void XclExpPivotRecWrapper::Save( XclExpStream& rStrm )
{
    if( mnScTab == EXC_PTMGR_PIVOTCACHES )
        mrPTMgr.WritePivotCaches( rStrm );
    else
        mrPTMgr.WritePivotTables( rStrm, mnScTab );
}

} // namespace

XclExpPivotTableManager::XclExpPivotTableManager( const XclExpRoot& rRoot ) :
    XclExpRoot( rRoot )
{
}

void XclExpPivotTableManager::CreatePivotTables()
{
    if( ScDPCollection* pDPColl = GetDoc().GetDPCollection() )
        for( size_t nDPObj = 0, nCount = pDPColl->GetCount(); nDPObj < nCount; ++nDPObj )
        {
            ScDPObject& rDPObj = (*pDPColl)[ nDPObj ];
            if( const XclExpPivotCache* pPCache = CreatePivotCache( rDPObj ) )
                maPTableList.AppendNewRecord( new XclExpPivotTable( GetRoot(), rDPObj, *pPCache ) );
        }
}

XclExpRecordRef XclExpPivotTableManager::CreatePivotCachesRecord()
{
    return new XclExpPivotRecWrapper( *this, EXC_PTMGR_PIVOTCACHES );
}

XclExpRecordRef XclExpPivotTableManager::CreatePivotTablesRecord( SCTAB nScTab )
{
    return new XclExpPivotRecWrapper( *this, nScTab );
}

void XclExpPivotTableManager::WritePivotCaches( XclExpStream& rStrm )
{
    maPCacheList.Save( rStrm );
}

void XclExpPivotTableManager::WritePivotTables( XclExpStream& rStrm, SCTAB nScTab )
{
    for( size_t nPos = 0, nSize = maPTableList.GetSize(); nPos < nSize; ++nPos )
    {
        XclExpPivotTableRef xPTable = maPTableList.GetRecord( nPos );
        if( xPTable->GetScTab() == nScTab )
            xPTable->Save( rStrm );
    }
}

const XclExpPivotCache* XclExpPivotTableManager::CreatePivotCache( const ScDPObject& rDPObj )
{
    // try to find a pivot cache with the same data source
    /*  #i25110# In Excel, the pivot cache contains additional fields
        (i.e. grouping info, calculated fields). If the passed DataPilot object
        or the found cache contains this data, do not share the cache with
        multiple pivot tables. */
    if( const ScDPSaveData* pSaveData = rDPObj.GetSaveData() )
    {
        const ScDPDimensionSaveData* pDimSaveData = pSaveData->GetExistingDimensionData();
        // no dimension save data at all or save data does not contain grouping info
        if( !pDimSaveData || !pDimSaveData->HasGroupDimensions() )
        {
            // check all existing pivot caches
            for( size_t nPos = 0, nSize = maPCacheList.GetSize(); nPos < nSize; ++nPos )
            {
                XclExpPivotCache* pPCache = maPCacheList.GetRecord( nPos );
                // pivot cache does not have grouping info and source data is equal
                if( !pPCache->HasAddFields() && pPCache->HasEqualDataSource( rDPObj ) )
                    return pPCache;
            }
        }
    }

    // create a new pivot cache
    sal_uInt16 nNewCacheIdx = static_cast< sal_uInt16 >( maPCacheList.GetSize() );
    XclExpPivotCacheRef xNewPCache = new XclExpPivotCache( GetRoot(), rDPObj, nNewCacheIdx );
    if( xNewPCache->IsValid() )
    {
        maPCacheList.AppendRecord( xNewPCache.get() );
        return xNewPCache.get();
    }

    return nullptr;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
