1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <xepivot.hxx>
21 #include <xehelper.hxx>
22 #include <com/sun/star/sheet/DataPilotFieldSortInfo.hpp>
23 #include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp>
24 #include <com/sun/star/sheet/DataPilotFieldLayoutInfo.hpp>
25 #include <com/sun/star/sheet/DataPilotFieldReference.hpp>
26 #include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp>
27 #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
28 #include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
29 
30 #include <algorithm>
31 #include <math.h>
32 #include <string_view>
33 
34 #include <osl/diagnose.h>
35 #include <sot/storage.hxx>
36 #include <document.hxx>
37 #include <dpcache.hxx>
38 #include <dpgroup.hxx>
39 #include <dpobject.hxx>
40 #include <dpsave.hxx>
41 #include <dpdimsave.hxx>
42 #include <dpshttab.hxx>
43 #include <globstr.hrc>
44 #include <scresid.hxx>
45 #include <xestring.hxx>
46 #include <xelink.hxx>
47 #include <dputil.hxx>
48 #include <generalfunction.hxx>
49 
50 using namespace ::oox;
51 
52 using ::com::sun::star::sheet::DataPilotFieldOrientation;
53 using ::com::sun::star::sheet::DataPilotFieldOrientation_ROW;
54 using ::com::sun::star::sheet::DataPilotFieldOrientation_COLUMN;
55 using ::com::sun::star::sheet::DataPilotFieldOrientation_PAGE;
56 using ::com::sun::star::sheet::DataPilotFieldOrientation_DATA;
57 using ::com::sun::star::sheet::DataPilotFieldSortInfo;
58 using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo;
59 using ::com::sun::star::sheet::DataPilotFieldLayoutInfo;
60 using ::com::sun::star::sheet::DataPilotFieldReference;
61 
62 // Pivot cache
63 
64 namespace {
65 
66 // constants to track occurrence of specific data types
67 const sal_uInt16 EXC_PCITEM_DATA_STRING     = 0x0001;   /// String, empty, boolean, error.
68 const sal_uInt16 EXC_PCITEM_DATA_DOUBLE     = 0x0002;   /// Double with fraction.
69 const sal_uInt16 EXC_PCITEM_DATA_INTEGER    = 0x0004;   /// Integer, double without fraction.
70 const sal_uInt16 EXC_PCITEM_DATA_DATE       = 0x0008;   /// Date, time, date/time.
71 
72 /** Maps a bitfield consisting of EXC_PCITEM_DATA_* flags above to SXFIELD data type bitfield. */
73 const sal_uInt16 spnPCItemFlags[] =
74 {                               // STR DBL INT DAT
75     EXC_SXFIELD_DATA_NONE,
76     EXC_SXFIELD_DATA_STR,       //  x
77     EXC_SXFIELD_DATA_INT,       //      x
78     EXC_SXFIELD_DATA_STR_INT,   //  x   x
79     EXC_SXFIELD_DATA_DBL,       //          x
80     EXC_SXFIELD_DATA_STR_DBL,   //  x       x
81     EXC_SXFIELD_DATA_INT,       //      x   x
82     EXC_SXFIELD_DATA_STR_INT,   //  x   x   x
83     EXC_SXFIELD_DATA_DATE,      //              x
84     EXC_SXFIELD_DATA_DATE_STR,  //  x           x
85     EXC_SXFIELD_DATA_DATE_NUM,  //      x       x
86     EXC_SXFIELD_DATA_DATE_STR,  //  x   x       x
87     EXC_SXFIELD_DATA_DATE_NUM,  //          x   x
88     EXC_SXFIELD_DATA_DATE_STR,  //  x       x   x
89     EXC_SXFIELD_DATA_DATE_NUM,  //      x   x   x
90     EXC_SXFIELD_DATA_DATE_STR   //  x   x   x   x
91 };
92 
93 } // namespace
94 
XclExpPCItem(const OUString & rText)95 XclExpPCItem::XclExpPCItem( const OUString& rText ) :
96     XclExpRecord( (!rText.isEmpty()) ? EXC_ID_SXSTRING : EXC_ID_SXEMPTY, 0 ),
97     mnTypeFlag( EXC_PCITEM_DATA_STRING )
98 {
99     if( !rText.isEmpty() )
100         SetText( rText );
101     else
102         SetEmpty();
103 }
104 
XclExpPCItem(double fValue,const OUString & rText)105 XclExpPCItem::XclExpPCItem( double fValue, const OUString& rText ) :
106     XclExpRecord( EXC_ID_SXDOUBLE, 8 )
107 {
108     SetDouble( fValue, rText );
109     mnTypeFlag = (fValue - floor( fValue ) == 0.0) ?
110         EXC_PCITEM_DATA_INTEGER : EXC_PCITEM_DATA_DOUBLE;
111 }
112 
XclExpPCItem(const DateTime & rDateTime,const OUString & rText)113 XclExpPCItem::XclExpPCItem( const DateTime& rDateTime, const OUString& rText ) :
114     XclExpRecord( EXC_ID_SXDATETIME, 8 )
115 {
116     SetDateTime( rDateTime, rText );
117     mnTypeFlag = EXC_PCITEM_DATA_DATE;
118 }
119 
XclExpPCItem(sal_Int16 nValue)120 XclExpPCItem::XclExpPCItem( sal_Int16 nValue ) :
121     XclExpRecord( EXC_ID_SXINTEGER, 2 ),
122     mnTypeFlag( EXC_PCITEM_DATA_INTEGER )
123 {
124     SetInteger( nValue );
125 }
126 
XclExpPCItem(bool bValue,const OUString & rText)127 XclExpPCItem::XclExpPCItem( bool bValue, const OUString& rText ) :
128     XclExpRecord( EXC_ID_SXBOOLEAN, 2 ),
129     mnTypeFlag( EXC_PCITEM_DATA_STRING )
130 {
131     SetBool( bValue, rText );
132 }
133 
EqualsText(std::u16string_view rText) const134 bool XclExpPCItem::EqualsText( std::u16string_view rText ) const
135 {
136     return rText.empty() ? IsEmpty() : (GetText() && (*GetText() == rText));
137 }
138 
EqualsDouble(double fValue) const139 bool XclExpPCItem::EqualsDouble( double fValue ) const
140 {
141     return GetDouble() && (*GetDouble() == fValue);
142 }
143 
EqualsDateTime(const DateTime & rDateTime) const144 bool XclExpPCItem::EqualsDateTime( const DateTime& rDateTime ) const
145 {
146     return GetDateTime() && (*GetDateTime() == rDateTime);
147 }
148 
EqualsBool(bool bValue) const149 bool XclExpPCItem::EqualsBool( bool bValue ) const
150 {
151     return GetBool() && (*GetBool() == bValue);
152 }
153 
WriteBody(XclExpStream & rStrm)154 void XclExpPCItem::WriteBody( XclExpStream& rStrm )
155 {
156     if( const OUString* pText = GetText() )
157     {
158         rStrm << XclExpString( *pText );
159     }
160     else if( const double* pfValue = GetDouble() )
161     {
162         rStrm << *pfValue;
163     }
164     else if( const sal_Int16* pnValue = GetInteger() )
165     {
166         rStrm << *pnValue;
167     }
168     else if( const DateTime* pDateTime = GetDateTime() )
169     {
170         sal_uInt16 nYear = static_cast< sal_uInt16 >( pDateTime->GetYear() );
171         sal_uInt16 nMonth = pDateTime->GetMonth();
172         sal_uInt8 nDay = static_cast< sal_uInt8 >( pDateTime->GetDay() );
173         sal_uInt8 nHour = static_cast< sal_uInt8 >( pDateTime->GetHour() );
174         sal_uInt8 nMin = static_cast< sal_uInt8 >( pDateTime->GetMin() );
175         sal_uInt8 nSec = static_cast< sal_uInt8 >( pDateTime->GetSec() );
176         if( nYear < 1900 ) { nYear = 1900; nMonth = 1; nDay = 0; }
177         rStrm << nYear << nMonth << nDay << nHour << nMin << nSec;
178     }
179     else if( const bool* pbValue = GetBool() )
180     {
181         rStrm << static_cast< sal_uInt16 >( *pbValue ? 1 : 0 );
182     }
183     else
184     {
185         // nothing to do for SXEMPTY
186         OSL_ENSURE( IsEmpty(), "XclExpPCItem::WriteBody - no data found" );
187     }
188 }
189 
XclExpPCField(const XclExpRoot & rRoot,sal_uInt16 nFieldIdx,const ScDPObject & rDPObj,const ScRange & rRange)190 XclExpPCField::XclExpPCField(
191         const XclExpRoot& rRoot, sal_uInt16 nFieldIdx,
192         const ScDPObject& rDPObj, const ScRange& rRange ) :
193     XclExpRecord( EXC_ID_SXFIELD ),
194     XclPCField( EXC_PCFIELD_STANDARD, nFieldIdx ),
195     XclExpRoot( rRoot ),
196     mnTypeFlags( 0 )
197 {
198     // general settings for the standard field, insert all items from source range
199     InitStandardField( rRange );
200 
201     // add special settings for inplace numeric grouping
202     if( const ScDPSaveData* pSaveData = rDPObj.GetSaveData() )
203     {
204         if( const ScDPDimensionSaveData* pSaveDimData = pSaveData->GetExistingDimensionData() )
205         {
206             if( const ScDPSaveNumGroupDimension* pNumGroupDim = pSaveDimData->GetNumGroupDim( GetFieldName() ) )
207             {
208                 const ScDPNumGroupInfo& rNumInfo = pNumGroupDim->GetInfo();
209                 const ScDPNumGroupInfo& rDateInfo = pNumGroupDim->GetDateInfo();
210                 OSL_ENSURE( !rNumInfo.mbEnable || !rDateInfo.mbEnable,
211                     "XclExpPCField::XclExpPCField - numeric and date grouping enabled" );
212 
213                 if( rNumInfo.mbEnable )
214                     InitNumGroupField( rDPObj, rNumInfo );
215                 else if( rDateInfo.mbEnable )
216                     InitDateGroupField( rDPObj, rDateInfo, pNumGroupDim->GetDatePart() );
217             }
218         }
219     }
220 
221     // final settings (flags, item numbers)
222     Finalize();
223 }
224 
XclExpPCField(const XclExpRoot & rRoot,sal_uInt16 nFieldIdx,const ScDPObject & rDPObj,const ScDPSaveGroupDimension & rGroupDim,const XclExpPCField & rBaseField)225 XclExpPCField::XclExpPCField(
226         const XclExpRoot& rRoot, sal_uInt16 nFieldIdx,
227         const ScDPObject& rDPObj, const ScDPSaveGroupDimension& rGroupDim, const XclExpPCField& rBaseField ) :
228     XclExpRecord( EXC_ID_SXFIELD ),
229     XclPCField( EXC_PCFIELD_STDGROUP, nFieldIdx ),
230     XclExpRoot( rRoot ),
231     mnTypeFlags( 0 )
232 {
233     // add base field info (always using first base field, not predecessor of this field) ***
234     OSL_ENSURE( rBaseField.GetFieldName() == rGroupDim.GetSourceDimName(),
235         "XclExpPCField::FillFromGroup - wrong base cache field" );
236     maFieldInfo.maName = rGroupDim.GetGroupDimName();
237     maFieldInfo.mnGroupBase = rBaseField.GetFieldIndex();
238 
239     // add standard group info or date group info
240     const ScDPNumGroupInfo& rDateInfo = rGroupDim.GetDateInfo();
241     if( rDateInfo.mbEnable && (rGroupDim.GetDatePart() != 0) )
242         InitDateGroupField( rDPObj, rDateInfo, rGroupDim.GetDatePart() );
243     else
244         InitStdGroupField( rBaseField, rGroupDim );
245 
246     // final settings (flags, item numbers)
247     Finalize();
248 }
249 
~XclExpPCField()250 XclExpPCField::~XclExpPCField()
251 {
252 }
253 
SetGroupChildField(const XclExpPCField & rChildField)254 void XclExpPCField::SetGroupChildField( const XclExpPCField& rChildField )
255 {
256     OSL_ENSURE( !::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD ),
257         "XclExpPCField::SetGroupChildIndex - field already has a grouping child field" );
258     ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD );
259     maFieldInfo.mnGroupChild = rChildField.GetFieldIndex();
260 }
261 
GetItemCount() const262 sal_uInt16 XclExpPCField::GetItemCount() const
263 {
264     return static_cast< sal_uInt16 >( GetVisItemList().GetSize() );
265 }
266 
GetItem(sal_uInt16 nItemIdx) const267 const XclExpPCItem* XclExpPCField::GetItem( sal_uInt16 nItemIdx ) const
268 {
269     return GetVisItemList().GetRecord( nItemIdx );
270 }
271 
GetItemIndex(std::u16string_view rItemName) const272 sal_uInt16 XclExpPCField::GetItemIndex( std::u16string_view rItemName ) const
273 {
274     const XclExpPCItemList& rItemList = GetVisItemList();
275     for( size_t nPos = 0, nSize = rItemList.GetSize(); nPos < nSize; ++nPos )
276         if( rItemList.GetRecord( nPos )->ConvertToText() == rItemName )
277             return static_cast< sal_uInt16 >( nPos );
278     return EXC_PC_NOITEM;
279 }
280 
GetIndexSize() const281 std::size_t XclExpPCField::GetIndexSize() const
282 {
283     return Has16BitIndexes() ? 2 : 1;
284 }
285 
WriteIndex(XclExpStream & rStrm,sal_uInt32 nSrcRow) const286 void XclExpPCField::WriteIndex( XclExpStream& rStrm, sal_uInt32 nSrcRow ) const
287 {
288     // only standard fields write item indexes
289     if( nSrcRow < maIndexVec.size() )
290     {
291         sal_uInt16 nIndex = maIndexVec[ nSrcRow ];
292         if( Has16BitIndexes() )
293             rStrm << nIndex;
294         else
295             rStrm << static_cast< sal_uInt8 >( nIndex );
296     }
297 }
298 
Save(XclExpStream & rStrm)299 void XclExpPCField::Save( XclExpStream& rStrm )
300 {
301     OSL_ENSURE( IsSupportedField(), "XclExpPCField::Save - unknown field type" );
302     // SXFIELD
303     XclExpRecord::Save( rStrm );
304     // SXFDBTYPE
305     XclExpUInt16Record( EXC_ID_SXFDBTYPE, EXC_SXFDBTYPE_DEFAULT ).Save( rStrm );
306     // list of grouping items
307     maGroupItemList.Save( rStrm );
308     // SXGROUPINFO
309     WriteSxgroupinfo( rStrm );
310     // SXNUMGROUP and additional grouping items (grouping limit settings)
311     WriteSxnumgroup( rStrm );
312     // list of original items
313     maOrigItemList.Save( rStrm );
314 }
315 
316 // private --------------------------------------------------------------------
317 
GetVisItemList() const318 const XclExpPCField::XclExpPCItemList& XclExpPCField::GetVisItemList() const
319 {
320     OSL_ENSURE( IsStandardField() == maGroupItemList.IsEmpty(),
321         "XclExpPCField::GetVisItemList - unexpected additional items in standard field" );
322     return IsStandardField() ? maOrigItemList : maGroupItemList;
323 }
324 
InitStandardField(const ScRange & rRange)325 void XclExpPCField::InitStandardField( const ScRange& rRange )
326 {
327     OSL_ENSURE( IsStandardField(), "XclExpPCField::InitStandardField - only for standard fields" );
328     OSL_ENSURE( rRange.aStart.Col() == rRange.aEnd.Col(), "XclExpPCField::InitStandardField - cell range with multiple columns" );
329 
330     ScDocument& rDoc = GetDoc();
331     SvNumberFormatter& rFormatter = GetFormatter();
332 
333     // field name is in top cell of the range
334     ScAddress aPos( rRange.aStart );
335     maFieldInfo.maName = rDoc.GetString(aPos.Col(), aPos.Row(), aPos.Tab());
336     // #i76047# maximum field name length in pivot cache is 255
337     if (maFieldInfo.maName.getLength() > EXC_PC_MAXSTRLEN)
338         maFieldInfo.maName = maFieldInfo.maName.copy(0, EXC_PC_MAXSTRLEN);
339 
340     // loop over all cells, create pivot cache items
341     for( aPos.IncRow(); (aPos.Row() <= rRange.aEnd.Row()) && (maOrigItemList.GetSize() < EXC_PC_MAXITEMCOUNT); aPos.IncRow() )
342     {
343         OUString aText = rDoc.GetString(aPos.Col(), aPos.Row(), aPos.Tab());
344         if( rDoc.HasValueData( aPos.Col(), aPos.Row(), aPos.Tab() ) )
345         {
346             double fValue = rDoc.GetValue( aPos );
347             SvNumFormatType nFmtType = rFormatter.GetType( rDoc.GetNumberFormat( rDoc.GetNonThreadedContext(), aPos ) );
348             if( nFmtType == SvNumFormatType::LOGICAL )
349                 InsertOrigBoolItem( fValue != 0, aText );
350             else if( nFmtType & SvNumFormatType::DATETIME )
351                 InsertOrigDateTimeItem( GetDateTimeFromDouble( ::std::max( fValue, 0.0 ) ), aText );
352             else
353                 InsertOrigDoubleItem( fValue, aText );
354         }
355         else
356         {
357             InsertOrigTextItem( aText );
358         }
359     }
360 }
361 
InitStdGroupField(const XclExpPCField & rBaseField,const ScDPSaveGroupDimension & rGroupDim)362 void XclExpPCField::InitStdGroupField( const XclExpPCField& rBaseField, const ScDPSaveGroupDimension& rGroupDim )
363 {
364     OSL_ENSURE( IsGroupField(), "XclExpPCField::InitStdGroupField - only for standard grouping fields" );
365 
366     maFieldInfo.mnBaseItems = rBaseField.GetItemCount();
367     maGroupOrder.resize( maFieldInfo.mnBaseItems, EXC_PC_NOITEM );
368 
369     // loop over all groups of this field
370     for( tools::Long nGroupIdx = 0, nGroupCount = rGroupDim.GetGroupCount(); nGroupIdx < nGroupCount; ++nGroupIdx )
371     {
372         const ScDPSaveGroupItem& rGroupItem = rGroupDim.GetGroupByIndex( nGroupIdx );
373         // the index of the new item containing the grouping name
374         sal_uInt16 nGroupItemIdx = EXC_PC_NOITEM;
375         // loop over all elements of one group
376         for( size_t nElemIdx = 0, nElemCount = rGroupItem.GetElementCount(); nElemIdx < nElemCount; ++nElemIdx )
377         {
378             if (const OUString* pElemName = rGroupItem.GetElementByIndex(nElemIdx))
379             {
380                 // try to find the item that is part of the group in the base field
381                 sal_uInt16 nBaseItemIdx = rBaseField.GetItemIndex( *pElemName );
382                 if( nBaseItemIdx < maFieldInfo.mnBaseItems )
383                 {
384                     // add group name item only if there are any valid base items
385                     if( nGroupItemIdx == EXC_PC_NOITEM )
386                         nGroupItemIdx = InsertGroupItem( new XclExpPCItem( rGroupItem.GetGroupName() ) );
387                     maGroupOrder[ nBaseItemIdx ] = nGroupItemIdx;
388                 }
389             }
390         }
391     }
392 
393     // add items and base item indexes of all ungrouped elements
394     for( sal_uInt16 nBaseItemIdx = 0; nBaseItemIdx < maFieldInfo.mnBaseItems; ++nBaseItemIdx )
395         // items that are not part of a group still have the EXC_PC_NOITEM entry
396         if( maGroupOrder[ nBaseItemIdx ] == EXC_PC_NOITEM )
397             // try to find the base item
398             if( const XclExpPCItem* pBaseItem = rBaseField.GetItem( nBaseItemIdx ) )
399                 // create a clone of the base item, insert its index into item order list
400                 maGroupOrder[ nBaseItemIdx ] = InsertGroupItem( new XclExpPCItem( *pBaseItem ) );
401 }
402 
InitNumGroupField(const ScDPObject & rDPObj,const ScDPNumGroupInfo & rNumInfo)403 void XclExpPCField::InitNumGroupField( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rNumInfo )
404 {
405     OSL_ENSURE( IsStandardField(), "XclExpPCField::InitNumGroupField - only for standard fields" );
406     OSL_ENSURE( rNumInfo.mbEnable, "XclExpPCField::InitNumGroupField - numeric grouping not enabled" );
407 
408     // new field type, date type, limit settings (min/max/step/auto)
409     if( rNumInfo.mbDateValues )
410     {
411         // special case: group by days with step count
412         meFieldType = EXC_PCFIELD_DATEGROUP;
413         maNumGroupInfo.SetScDateType( css::sheet::DataPilotFieldGroupBy::DAYS );
414         SetDateGroupLimit( rNumInfo, true );
415     }
416     else
417     {
418         meFieldType = EXC_PCFIELD_NUMGROUP;
419         maNumGroupInfo.SetNumType();
420         SetNumGroupLimit( rNumInfo );
421     }
422 
423     // generate visible items
424     InsertNumDateGroupItems( rDPObj, rNumInfo );
425 }
426 
InitDateGroupField(const ScDPObject & rDPObj,const ScDPNumGroupInfo & rDateInfo,sal_Int32 nDatePart)427 void XclExpPCField::InitDateGroupField( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nDatePart )
428 {
429     OSL_ENSURE( IsStandardField() || IsStdGroupField(), "XclExpPCField::InitDateGroupField - only for standard fields" );
430     OSL_ENSURE( rDateInfo.mbEnable, "XclExpPCField::InitDateGroupField - date grouping not enabled" );
431 
432     // new field type
433     meFieldType = IsStandardField() ? EXC_PCFIELD_DATEGROUP : EXC_PCFIELD_DATECHILD;
434 
435     // date type, limit settings (min/max/step/auto)
436     maNumGroupInfo.SetScDateType( nDatePart );
437     SetDateGroupLimit( rDateInfo, false );
438 
439     // generate visible items
440     InsertNumDateGroupItems( rDPObj, rDateInfo, nDatePart );
441 }
442 
InsertItemArrayIndex(size_t nListPos)443 void XclExpPCField::InsertItemArrayIndex( size_t nListPos )
444 {
445     OSL_ENSURE( IsStandardField(), "XclExpPCField::InsertItemArrayIndex - only for standard fields" );
446     maIndexVec.push_back( static_cast< sal_uInt16 >( nListPos ) );
447 }
448 
InsertOrigItem(XclExpPCItem * pNewItem)449 void XclExpPCField::InsertOrigItem( XclExpPCItem* pNewItem )
450 {
451     size_t nItemIdx = maOrigItemList.GetSize();
452     maOrigItemList.AppendNewRecord( pNewItem );
453     InsertItemArrayIndex( nItemIdx );
454     mnTypeFlags |= pNewItem->GetTypeFlag();
455 }
456 
InsertOrigTextItem(const OUString & rText)457 void XclExpPCField::InsertOrigTextItem( const OUString& rText )
458 {
459     size_t nPos = 0;
460     bool bFound = false;
461     // #i76047# maximum item text length in pivot cache is 255
462     OUString aShortText = rText.copy( 0, ::std::min(rText.getLength(), EXC_PC_MAXSTRLEN ) );
463     for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos )
464         if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsText( aShortText )) )
465             InsertItemArrayIndex( nPos );
466     if( !bFound )
467         InsertOrigItem( new XclExpPCItem( aShortText ) );
468 }
469 
InsertOrigDoubleItem(double fValue,const OUString & rText)470 void XclExpPCField::InsertOrigDoubleItem( double fValue, const OUString& rText )
471 {
472     size_t nPos = 0;
473     bool bFound = false;
474     for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos )
475         if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsDouble( fValue )) )
476             InsertItemArrayIndex( nPos );
477     if( !bFound )
478         InsertOrigItem( new XclExpPCItem( fValue, rText ) );
479 }
480 
InsertOrigDateTimeItem(const DateTime & rDateTime,const OUString & rText)481 void XclExpPCField::InsertOrigDateTimeItem( const DateTime& rDateTime, const OUString& rText )
482 {
483     size_t nPos = 0;
484     bool bFound = false;
485     for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos )
486         if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsDateTime( rDateTime )) )
487             InsertItemArrayIndex( nPos );
488     if( !bFound )
489         InsertOrigItem( new XclExpPCItem( rDateTime, rText ) );
490 }
491 
InsertOrigBoolItem(bool bValue,const OUString & rText)492 void XclExpPCField::InsertOrigBoolItem( bool bValue, const OUString& rText )
493 {
494     size_t nPos = 0;
495     bool bFound = false;
496     for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos )
497         if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsBool( bValue )) )
498             InsertItemArrayIndex( nPos );
499     if( !bFound )
500         InsertOrigItem( new XclExpPCItem( bValue, rText ) );
501 }
502 
InsertGroupItem(XclExpPCItem * pNewItem)503 sal_uInt16 XclExpPCField::InsertGroupItem( XclExpPCItem* pNewItem )
504 {
505     maGroupItemList.AppendNewRecord( pNewItem );
506     return static_cast< sal_uInt16 >( maGroupItemList.GetSize() - 1 );
507 }
508 
InsertNumDateGroupItems(const ScDPObject & rDPObj,const ScDPNumGroupInfo & rNumInfo,sal_Int32 nDatePart)509 void XclExpPCField::InsertNumDateGroupItems( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rNumInfo, sal_Int32 nDatePart )
510 {
511     OSL_ENSURE( rDPObj.GetSheetDesc(), "XclExpPCField::InsertNumDateGroupItems - cannot generate element list" );
512     const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc();
513     if(!pSrcDesc)
514         return;
515 
516     // get the string collection with original source elements
517     const ScDPSaveData* pSaveData = rDPObj.GetSaveData();
518     const ScDPDimensionSaveData* pDimData = nullptr;
519     if (pSaveData)
520         pDimData = pSaveData->GetExistingDimensionData();
521 
522     const ScDPCache* pCache = pSrcDesc->CreateCache(pDimData);
523     if (!pCache)
524         return;
525 
526     ScSheetDPData aDPData(&GetDoc(), *pSrcDesc, *pCache);
527     tools::Long nDim = GetFieldIndex();
528     // get the string collection with generated grouping elements
529     ScDPNumGroupDimension aTmpDim( rNumInfo );
530     if( nDatePart != 0 )
531         aTmpDim.SetDateDimension();
532     const std::vector<SCROW>& aMemberIds = aTmpDim.GetNumEntries(
533         static_cast<SCCOL>(nDim), pCache);
534     for (SCROW nMemberId : aMemberIds)
535     {
536         const ScDPItemData* pData = aDPData.GetMemberById(nDim, nMemberId);
537         if ( pData )
538         {
539             OUString aStr = pCache->GetFormattedString(nDim, *pData, false);
540             InsertGroupItem(new XclExpPCItem(aStr));
541         }
542     }
543 }
544 
SetNumGroupLimit(const ScDPNumGroupInfo & rNumInfo)545 void XclExpPCField::SetNumGroupLimit( const ScDPNumGroupInfo& rNumInfo )
546 {
547     ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN, rNumInfo.mbAutoStart );
548     ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX, rNumInfo.mbAutoEnd );
549     maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfStart ) );
550     maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfEnd ) );
551     maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfStep ) );
552 }
553 
SetDateGroupLimit(const ScDPNumGroupInfo & rDateInfo,bool bUseStep)554 void XclExpPCField::SetDateGroupLimit( const ScDPNumGroupInfo& rDateInfo, bool bUseStep )
555 {
556     ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN, rDateInfo.mbAutoStart );
557     ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX, rDateInfo.mbAutoEnd );
558     maNumGroupLimits.AppendNewRecord( new XclExpPCItem( GetDateTimeFromDouble( rDateInfo.mfStart ) ) );
559     maNumGroupLimits.AppendNewRecord( new XclExpPCItem( GetDateTimeFromDouble( rDateInfo.mfEnd ) ) );
560     sal_Int16 nStep = bUseStep ? limit_cast< sal_Int16 >( rDateInfo.mfStep, 1, SAL_MAX_INT16 ) : 1;
561     maNumGroupLimits.AppendNewRecord( new XclExpPCItem( nStep ) );
562 }
563 
Finalize()564 void XclExpPCField::Finalize()
565 {
566     // flags
567     ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASITEMS, !GetVisItemList().IsEmpty() );
568     // Excel writes long indexes even for 0x0100 items (indexes from 0x00 to 0xFF)
569     ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_16BIT, maOrigItemList.GetSize() >= 0x0100 );
570     ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_NUMGROUP, IsNumGroupField() || IsDateGroupField() );
571     /*  mnTypeFlags is updated in all Insert***Item() functions. Now the flags
572         for the current combination of item types is added to the flags. */
573     ::set_flag( maFieldInfo.mnFlags, spnPCItemFlags[ mnTypeFlags ] );
574 
575     // item count fields
576     maFieldInfo.mnVisItems = static_cast< sal_uInt16 >( GetVisItemList().GetSize() );
577     maFieldInfo.mnGroupItems = static_cast< sal_uInt16 >( maGroupItemList.GetSize() );
578     // maFieldInfo.mnBaseItems set in InitStdGroupField()
579     maFieldInfo.mnOrigItems = static_cast< sal_uInt16 >( maOrigItemList.GetSize() );
580 }
581 
WriteSxnumgroup(XclExpStream & rStrm)582 void XclExpPCField::WriteSxnumgroup( XclExpStream& rStrm )
583 {
584     if( IsNumGroupField() || IsDateGroupField() )
585     {
586         // SXNUMGROUP record
587         rStrm.StartRecord( EXC_ID_SXNUMGROUP, 2 );
588         rStrm << maNumGroupInfo;
589         rStrm.EndRecord();
590 
591         // limits (min/max/step) for numeric grouping
592         OSL_ENSURE( maNumGroupLimits.GetSize() == 3,
593             "XclExpPCField::WriteSxnumgroup - missing numeric grouping limits" );
594         maNumGroupLimits.Save( rStrm );
595     }
596 }
597 
WriteSxgroupinfo(XclExpStream & rStrm)598 void XclExpPCField::WriteSxgroupinfo( XclExpStream& rStrm )
599 {
600     OSL_ENSURE( IsStdGroupField() != maGroupOrder.empty(),
601         "XclExpPCField::WriteSxgroupinfo - missing grouping info" );
602     if( IsStdGroupField() && !maGroupOrder.empty() )
603     {
604         rStrm.StartRecord( EXC_ID_SXGROUPINFO, 2 * maGroupOrder.size() );
605         for( const auto& rItem : maGroupOrder )
606             rStrm << rItem;
607         rStrm.EndRecord();
608     }
609 }
610 
WriteBody(XclExpStream & rStrm)611 void XclExpPCField::WriteBody( XclExpStream& rStrm )
612 {
613     rStrm << maFieldInfo;
614 }
615 
XclExpPivotCache(const XclExpRoot & rRoot,const ScDPObject & rDPObj,sal_uInt16 nListIdx)616 XclExpPivotCache::XclExpPivotCache( const XclExpRoot& rRoot, const ScDPObject& rDPObj, sal_uInt16 nListIdx ) :
617     XclExpRoot( rRoot ),
618     mnListIdx( nListIdx ),
619     mbValid( false )
620 {
621     // source from sheet only
622     const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc();
623     if(!pSrcDesc)
624         return;
625 
626     /*  maOrigSrcRange: Range received from the DataPilot object.
627         maExpSrcRange: Range written to the DCONREF record.
628         maDocSrcRange: Range used to get source data from Calc document.
629             This range may be shorter than maExpSrcRange to improve export
630             performance (#i22541#). */
631     maOrigSrcRange = maExpSrcRange = maDocSrcRange = pSrcDesc->GetSourceRange();
632     maSrcRangeName = pSrcDesc->GetRangeName();
633 
634     // internal sheet data only
635     SCTAB nScTab = maExpSrcRange.aStart.Tab();
636     if( !((nScTab == maExpSrcRange.aEnd.Tab()) && GetTabInfo().IsExportTab( nScTab )) )
637         return;
638 
639     // ValidateRange() restricts source range to valid Excel limits
640     if( !GetAddressConverter().ValidateRange( maExpSrcRange, true ) )
641         return;
642 
643     // #i22541# skip empty cell areas (performance)
644     SCCOL nDocCol1, nDocCol2;
645     SCROW nDocRow1, nDocRow2;
646     GetDoc().GetDataStart( nScTab, nDocCol1, nDocRow1 );
647     GetDoc().GetPrintArea( nScTab, nDocCol2, nDocRow2, false );
648     SCCOL nSrcCol1 = maExpSrcRange.aStart.Col();
649     SCROW nSrcRow1 = maExpSrcRange.aStart.Row();
650     SCCOL nSrcCol2 = maExpSrcRange.aEnd.Col();
651     SCROW nSrcRow2 = maExpSrcRange.aEnd.Row();
652 
653     // #i22541# do not store index list for too big ranges
654     if( 2 * (nDocRow2 - nDocRow1) < (nSrcRow2 - nSrcRow1) )
655         ::set_flag( maPCInfo.mnFlags, EXC_SXDB_SAVEDATA, false );
656 
657     // adjust row indexes, keep one row of empty area to surely have the empty cache item
658     if( nSrcRow1 < nDocRow1 )
659         nSrcRow1 = nDocRow1 - 1;
660     if( nSrcRow2 > nDocRow2 )
661         nSrcRow2 = nDocRow2 + 1;
662 
663     maDocSrcRange.aStart.SetCol( ::std::max( nDocCol1, nSrcCol1 ) );
664     maDocSrcRange.aStart.SetRow( nSrcRow1 );
665     maDocSrcRange.aEnd.SetCol( ::std::min( nDocCol2, nSrcCol2 ) );
666     maDocSrcRange.aEnd.SetRow( nSrcRow2 );
667 
668     GetDoc().GetName( nScTab, maTabName );
669     maPCInfo.mnSrcRecs = static_cast< sal_uInt32 >( maExpSrcRange.aEnd.Row() - maExpSrcRange.aStart.Row() );
670     maPCInfo.mnStrmId = nListIdx + 1;
671     maPCInfo.mnSrcType = EXC_SXDB_SRC_SHEET;
672 
673     AddFields( rDPObj );
674 
675     mbValid = true;
676 }
677 
HasItemIndexList() const678 bool XclExpPivotCache::HasItemIndexList() const
679 {
680     return ::get_flag( maPCInfo.mnFlags, EXC_SXDB_SAVEDATA );
681 }
682 
GetFieldCount() const683 sal_uInt16 XclExpPivotCache::GetFieldCount() const
684 {
685     return static_cast< sal_uInt16 >( maFieldList.GetSize() );
686 }
687 
GetField(sal_uInt16 nFieldIdx) const688 const XclExpPCField* XclExpPivotCache::GetField( sal_uInt16 nFieldIdx ) const
689 {
690     return maFieldList.GetRecord( nFieldIdx );
691 }
692 
HasAddFields() const693 bool XclExpPivotCache::HasAddFields() const
694 {
695     // pivot cache can be shared, if there are no additional cache fields
696     return maPCInfo.mnStdFields < maPCInfo.mnTotalFields;
697 }
698 
HasEqualDataSource(const ScDPObject & rDPObj) const699 bool XclExpPivotCache::HasEqualDataSource( const ScDPObject& rDPObj ) const
700 {
701     /*  For now, only sheet sources are supported, therefore it is enough to
702         compare the ScSheetSourceDesc. Later, there should be done more complicated
703         comparisons regarding the source type of rDPObj and this cache. */
704     if( const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc() )
705         return pSrcDesc->GetSourceRange() == maOrigSrcRange;
706     return false;
707 }
708 
Save(XclExpStream & rStrm)709 void XclExpPivotCache::Save( XclExpStream& rStrm )
710 {
711     OSL_ENSURE( mbValid, "XclExpPivotCache::Save - invalid pivot cache" );
712     // SXIDSTM
713     XclExpUInt16Record( EXC_ID_SXIDSTM, maPCInfo.mnStrmId ).Save( rStrm );
714     // SXVS
715     XclExpUInt16Record( EXC_ID_SXVS, EXC_SXVS_SHEET ).Save( rStrm );
716 
717     if (!maSrcRangeName.isEmpty())
718         // DCONNAME
719         WriteDConName(rStrm);
720     else
721         // DCONREF
722         WriteDconref(rStrm);
723 
724     // create the pivot cache storage stream
725     WriteCacheStream();
726 }
727 
SaveXml(XclExpXmlStream &)728 void XclExpPivotCache::SaveXml( XclExpXmlStream& /*rStrm*/ )
729 {
730 }
731 
AddFields(const ScDPObject & rDPObj)732 void XclExpPivotCache::AddFields( const ScDPObject& rDPObj )
733 {
734     AddStdFields( rDPObj );
735     maPCInfo.mnStdFields = GetFieldCount();
736     AddGroupFields( rDPObj );
737     maPCInfo.mnTotalFields = GetFieldCount();
738 };
739 
AddStdFields(const ScDPObject & rDPObj)740 void XclExpPivotCache::AddStdFields( const ScDPObject& rDPObj )
741 {
742     // if item index list is not written, used shortened source range (maDocSrcRange) for performance
743     const ScRange& rRange = HasItemIndexList() ? maExpSrcRange : maDocSrcRange;
744     // create a standard pivot cache field for each source column
745     for( SCCOL nScCol = rRange.aStart.Col(), nEndScCol = rRange.aEnd.Col(); nScCol <= nEndScCol; ++nScCol )
746     {
747         ScRange aColRange( rRange );
748         aColRange.aStart.SetCol( nScCol );
749         aColRange.aEnd.SetCol( nScCol );
750         maFieldList.AppendNewRecord( new XclExpPCField(
751             GetRoot(), GetFieldCount(), rDPObj, aColRange ) );
752     }
753 }
754 
AddGroupFields(const ScDPObject & rDPObj)755 void XclExpPivotCache::AddGroupFields( const ScDPObject& rDPObj )
756 {
757     const ScDPSaveData* pSaveData = rDPObj.GetSaveData();
758     if(!pSaveData)
759         return;
760     const ScDPDimensionSaveData* pSaveDimData = pSaveData->GetExistingDimensionData();
761     if( !pSaveDimData )
762         return;
763 
764     // loop over all existing standard fields to find their group fields
765     for( sal_uInt16 nFieldIdx = 0; nFieldIdx < maPCInfo.mnStdFields; ++nFieldIdx )
766     {
767         if( XclExpPCField* pCurrStdField = maFieldList.GetRecord( nFieldIdx ) )
768         {
769             const ScDPSaveGroupDimension* pGroupDim = pSaveDimData->GetGroupDimForBase( pCurrStdField->GetFieldName() );
770             XclExpPCField* pLastGroupField = pCurrStdField;
771             while( pGroupDim )
772             {
773                 // insert the new grouping field
774                 XclExpPCFieldRef xNewGroupField = new XclExpPCField(
775                     GetRoot(), GetFieldCount(), rDPObj, *pGroupDim, *pCurrStdField );
776                 maFieldList.AppendRecord( xNewGroupField );
777 
778                 // register new grouping field at current grouping field, building a chain
779                 pLastGroupField->SetGroupChildField( *xNewGroupField );
780 
781                 // next grouping dimension
782                 pGroupDim = pSaveDimData->GetGroupDimForBase( pGroupDim->GetGroupDimName() );
783                 pLastGroupField = xNewGroupField.get();
784             }
785         }
786     }
787 }
788 
WriteDconref(XclExpStream & rStrm) const789 void XclExpPivotCache::WriteDconref( XclExpStream& rStrm ) const
790 {
791     XclExpString aRef( XclExpUrlHelper::EncodeUrl( GetRoot(), EMPTY_OUSTRING, &maTabName ) );
792     rStrm.StartRecord( EXC_ID_DCONREF, 7 + aRef.GetSize() );
793     rStrm   << static_cast< sal_uInt16 >( maExpSrcRange.aStart.Row() )
794             << static_cast< sal_uInt16 >( maExpSrcRange.aEnd.Row() )
795             << static_cast< sal_uInt8 >( maExpSrcRange.aStart.Col() )
796             << static_cast< sal_uInt8 >( maExpSrcRange.aEnd.Col() )
797             << aRef
798             << sal_uInt8( 0 );
799     rStrm.EndRecord();
800 }
801 
WriteDConName(XclExpStream & rStrm) const802 void XclExpPivotCache::WriteDConName( XclExpStream& rStrm ) const
803 {
804     XclExpString aName(maSrcRangeName);
805     rStrm.StartRecord(EXC_ID_DCONNAME, aName.GetSize() + 2);
806     rStrm << aName << sal_uInt16(0);
807     rStrm.EndRecord();
808 }
809 
WriteCacheStream()810 void XclExpPivotCache::WriteCacheStream()
811 {
812     tools::SvRef<SotStorage> xSvStrg = OpenStorage( EXC_STORAGE_PTCACHE );
813     tools::SvRef<SotStorageStream> xSvStrm = OpenStream( xSvStrg, ScfTools::GetHexStr( maPCInfo.mnStrmId ) );
814     if( !xSvStrm.is() )
815         return;
816 
817     XclExpStream aStrm( *xSvStrm, GetRoot() );
818     // SXDB
819     WriteSxdb( aStrm );
820     // SXDBEX
821     WriteSxdbex( aStrm );
822     // field list (SXFIELD and items)
823     maFieldList.Save( aStrm );
824     // index table (list of SXINDEXLIST)
825     WriteSxindexlistList( aStrm );
826     // EOF
827     XclExpEmptyRecord( EXC_ID_EOF ).Save( aStrm );
828 }
829 
WriteSxdb(XclExpStream & rStrm) const830 void XclExpPivotCache::WriteSxdb( XclExpStream& rStrm ) const
831 {
832     rStrm.StartRecord( EXC_ID_SXDB, 21 );
833     rStrm << maPCInfo;
834     rStrm.EndRecord();
835 }
836 
WriteSxdbex(XclExpStream & rStrm)837 void XclExpPivotCache::WriteSxdbex( XclExpStream& rStrm )
838 {
839     rStrm.StartRecord( EXC_ID_SXDBEX, 12 );
840     rStrm   << EXC_SXDBEX_CREATION_DATE
841             << sal_uInt32( 0 );             // number of SXFORMULA records
842     rStrm.EndRecord();
843 }
844 
WriteSxindexlistList(XclExpStream & rStrm) const845 void XclExpPivotCache::WriteSxindexlistList( XclExpStream& rStrm ) const
846 {
847     if( !HasItemIndexList() )
848         return;
849 
850     std::size_t nRecSize = 0;
851     size_t nPos, nSize = maFieldList.GetSize();
852     for( nPos = 0; nPos < nSize; ++nPos )
853         nRecSize += maFieldList.GetRecord( nPos )->GetIndexSize();
854 
855     for( sal_uInt32 nSrcRow = 0; nSrcRow < maPCInfo.mnSrcRecs; ++nSrcRow )
856     {
857         rStrm.StartRecord( EXC_ID_SXINDEXLIST, nRecSize );
858         for( nPos = 0; nPos < nSize; ++nPos )
859             maFieldList.GetRecord( nPos )->WriteIndex( rStrm, nSrcRow );
860         rStrm.EndRecord();
861     }
862 }
863 
864 // Pivot table
865 
866 namespace {
867 
868 /** Returns a display string for a data field containing the field name and aggregation function. */
lclGetDataFieldCaption(std::u16string_view rFieldName,ScGeneralFunction eFunc)869 OUString lclGetDataFieldCaption( std::u16string_view rFieldName, ScGeneralFunction eFunc )
870 {
871     OUString aCaption;
872 
873     const char* pResIdx = nullptr;
874     switch( eFunc )
875     {
876         case ScGeneralFunction::SUM:       pResIdx = STR_FUN_TEXT_SUM;     break;
877         case ScGeneralFunction::COUNT:     pResIdx = STR_FUN_TEXT_COUNT;   break;
878         case ScGeneralFunction::AVERAGE:   pResIdx = STR_FUN_TEXT_AVG;     break;
879         case ScGeneralFunction::MAX:       pResIdx = STR_FUN_TEXT_MAX;     break;
880         case ScGeneralFunction::MIN:       pResIdx = STR_FUN_TEXT_MIN;     break;
881         case ScGeneralFunction::PRODUCT:   pResIdx = STR_FUN_TEXT_PRODUCT; break;
882         case ScGeneralFunction::COUNTNUMS: pResIdx = STR_FUN_TEXT_COUNT;   break;
883         case ScGeneralFunction::STDEV:     pResIdx = STR_FUN_TEXT_STDDEV;  break;
884         case ScGeneralFunction::STDEVP:    pResIdx = STR_FUN_TEXT_STDDEV;  break;
885         case ScGeneralFunction::VAR:       pResIdx = STR_FUN_TEXT_VAR;     break;
886         case ScGeneralFunction::VARP:      pResIdx = STR_FUN_TEXT_VAR;     break;
887         default:;
888     }
889     if (pResIdx)
890         aCaption = ScResId(pResIdx) + " - ";
891     aCaption += rFieldName;
892     return aCaption;
893 }
894 
895 } // namespace
896 
XclExpPTItem(const XclExpPCField & rCacheField,sal_uInt16 nCacheIdx)897 XclExpPTItem::XclExpPTItem( const XclExpPCField& rCacheField, sal_uInt16 nCacheIdx ) :
898     XclExpRecord( EXC_ID_SXVI, 8 ),
899     mpCacheItem( rCacheField.GetItem( nCacheIdx ) )
900 {
901     maItemInfo.mnType = EXC_SXVI_TYPE_DATA;
902     maItemInfo.mnCacheIdx = nCacheIdx;
903     maItemInfo.maVisName.mbUseCache = mpCacheItem != nullptr;
904 }
905 
XclExpPTItem(sal_uInt16 nItemType,sal_uInt16 nCacheIdx)906 XclExpPTItem::XclExpPTItem( sal_uInt16 nItemType, sal_uInt16 nCacheIdx ) :
907     XclExpRecord( EXC_ID_SXVI, 8 ),
908     mpCacheItem( nullptr )
909 {
910     maItemInfo.mnType = nItemType;
911     maItemInfo.mnCacheIdx = nCacheIdx;
912     maItemInfo.maVisName.mbUseCache = true;
913 }
914 
GetItemName() const915 OUString XclExpPTItem::GetItemName() const
916 {
917     return mpCacheItem ? mpCacheItem->ConvertToText() : OUString();
918 }
919 
SetPropertiesFromMember(const ScDPSaveMember & rSaveMem)920 void XclExpPTItem::SetPropertiesFromMember( const ScDPSaveMember& rSaveMem )
921 {
922     // #i115659# GetIsVisible() is not valid if HasIsVisible() returns false, default is 'visible' then
923     ::set_flag( maItemInfo.mnFlags, EXC_SXVI_HIDDEN, rSaveMem.HasIsVisible() && !rSaveMem.GetIsVisible() );
924     // #i115659# GetShowDetails() is not valid if HasShowDetails() returns false, default is 'show detail' then
925     ::set_flag( maItemInfo.mnFlags, EXC_SXVI_HIDEDETAIL, rSaveMem.HasShowDetails() && !rSaveMem.GetShowDetails() );
926 
927     // visible name
928     const std::optional<OUString> & pVisName = rSaveMem.GetLayoutName();
929     if (pVisName && *pVisName != GetItemName())
930         maItemInfo.SetVisName(*pVisName);
931 }
932 
WriteBody(XclExpStream & rStrm)933 void XclExpPTItem::WriteBody( XclExpStream& rStrm )
934 {
935     rStrm << maItemInfo;
936 }
937 
XclExpPTField(const XclExpPivotTable & rPTable,sal_uInt16 nCacheIdx)938 XclExpPTField::XclExpPTField( const XclExpPivotTable& rPTable, sal_uInt16 nCacheIdx ) :
939     mrPTable( rPTable ),
940     mpCacheField( rPTable.GetCacheField( nCacheIdx ) )
941 {
942     maFieldInfo.mnCacheIdx = nCacheIdx;
943 
944     // create field items
945     if( mpCacheField )
946         for( sal_uInt16 nItemIdx = 0, nItemCount = mpCacheField->GetItemCount(); nItemIdx < nItemCount; ++nItemIdx )
947             maItemList.AppendNewRecord( new XclExpPTItem( *mpCacheField, nItemIdx ) );
948     maFieldInfo.mnItemCount = static_cast< sal_uInt16 >( maItemList.GetSize() );
949 }
950 
951 // data access ----------------------------------------------------------------
952 
GetFieldName() const953 OUString XclExpPTField::GetFieldName() const
954 {
955     return mpCacheField ? mpCacheField->GetFieldName() : OUString();
956 }
957 
GetLastDataInfoIndex() const958 sal_uInt16 XclExpPTField::GetLastDataInfoIndex() const
959 {
960     OSL_ENSURE( !maDataInfoVec.empty(), "XclExpPTField::GetLastDataInfoIndex - no data info found" );
961     // will return 0xFFFF for empty vector -> ok
962     return static_cast< sal_uInt16 >( maDataInfoVec.size() - 1 );
963 }
964 
GetItemIndex(std::u16string_view rName,sal_uInt16 nDefaultIdx) const965 sal_uInt16 XclExpPTField::GetItemIndex( std::u16string_view rName, sal_uInt16 nDefaultIdx ) const
966 {
967     for( size_t nPos = 0, nSize = maItemList.GetSize(); nPos < nSize; ++nPos )
968         if( maItemList.GetRecord( nPos )->GetItemName() == rName )
969             return static_cast< sal_uInt16 >( nPos );
970     return nDefaultIdx;
971 }
972 
973 // fill data --------------------------------------------------------------
974 
975 /**
976  * Calc's subtotal names are escaped with backslashes ('\'), while Excel's
977  * are not escaped at all.
978  */
lcl_convertCalcSubtotalName(const OUString & rName)979 static OUString lcl_convertCalcSubtotalName(const OUString& rName)
980 {
981     OUStringBuffer aBuf;
982     const sal_Unicode* p = rName.getStr();
983     sal_Int32 n = rName.getLength();
984     bool bEscaped = false;
985     for (sal_Int32 i = 0; i < n; ++i)
986     {
987         const sal_Unicode c = p[i];
988         if (!bEscaped && c == '\\')
989         {
990             bEscaped = true;
991             continue;
992         }
993 
994         aBuf.append(c);
995         bEscaped = false;
996     }
997     return aBuf.makeStringAndClear();
998 }
999 
SetPropertiesFromDim(const ScDPSaveDimension & rSaveDim)1000 void XclExpPTField::SetPropertiesFromDim( const ScDPSaveDimension& rSaveDim )
1001 {
1002     // orientation
1003     DataPilotFieldOrientation eOrient = rSaveDim.GetOrientation();
1004     OSL_ENSURE( eOrient != DataPilotFieldOrientation_DATA, "XclExpPTField::SetPropertiesFromDim - called for data field" );
1005     maFieldInfo.AddApiOrient( eOrient );
1006 
1007     // show empty items (#i115659# GetShowEmpty() is not valid if HasShowEmpty() returns false, default is false then)
1008     ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SHOWALL, rSaveDim.HasShowEmpty() && rSaveDim.GetShowEmpty() );
1009 
1010     // visible name
1011     const std::optional<OUString> & pLayoutName = rSaveDim.GetLayoutName();
1012     if (pLayoutName && *pLayoutName != GetFieldName())
1013         maFieldInfo.SetVisName(*pLayoutName);
1014 
1015     const std::optional<OUString> & pSubtotalName = rSaveDim.GetSubtotalName();
1016     if (pSubtotalName)
1017     {
1018         OUString aSubName = lcl_convertCalcSubtotalName(*pSubtotalName);
1019         maFieldExtInfo.mpFieldTotalName = aSubName;
1020     }
1021 
1022     // subtotals
1023     XclPTSubtotalVec aSubtotals;
1024     aSubtotals.reserve( static_cast< size_t >( rSaveDim.GetSubTotalsCount() ) );
1025     for( tools::Long nSubtIdx = 0, nSubtCount = rSaveDim.GetSubTotalsCount(); nSubtIdx < nSubtCount; ++nSubtIdx )
1026         aSubtotals.push_back( rSaveDim.GetSubTotalFunc( nSubtIdx ) );
1027     maFieldInfo.SetSubtotals( aSubtotals );
1028 
1029     // sorting
1030     if( const DataPilotFieldSortInfo* pSortInfo = rSaveDim.GetSortInfo() )
1031     {
1032         maFieldExtInfo.SetApiSortMode( pSortInfo->Mode );
1033         if( pSortInfo->Mode == css::sheet::DataPilotFieldSortMode::DATA )
1034             maFieldExtInfo.mnSortField = mrPTable.GetDataFieldIndex( pSortInfo->Field, EXC_SXVDEX_SORT_OWN );
1035         ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SORT_ASC, pSortInfo->IsAscending );
1036     }
1037 
1038     // auto show
1039     if( const DataPilotFieldAutoShowInfo* pShowInfo = rSaveDim.GetAutoShowInfo() )
1040     {
1041         ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_AUTOSHOW, pShowInfo->IsEnabled );
1042         maFieldExtInfo.SetApiAutoShowMode( pShowInfo->ShowItemsMode );
1043         maFieldExtInfo.SetApiAutoShowCount( pShowInfo->ItemCount );
1044         maFieldExtInfo.mnShowField = mrPTable.GetDataFieldIndex( pShowInfo->DataField, EXC_SXVDEX_SHOW_NONE );
1045     }
1046 
1047     // layout
1048     if( const DataPilotFieldLayoutInfo* pLayoutInfo = rSaveDim.GetLayoutInfo() )
1049     {
1050         maFieldExtInfo.SetApiLayoutMode( pLayoutInfo->LayoutMode );
1051         ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_LAYOUT_BLANK, pLayoutInfo->AddEmptyLines );
1052     }
1053 
1054     // special page field properties
1055     if( eOrient == DataPilotFieldOrientation_PAGE )
1056     {
1057         maPageInfo.mnField = GetFieldIndex();
1058         maPageInfo.mnSelItem = EXC_SXPI_ALLITEMS;
1059     }
1060 
1061     // item properties
1062     const ScDPSaveDimension::MemberList &rMembers = rSaveDim.GetMembers();
1063     for (const auto& pMember : rMembers)
1064         if( XclExpPTItem* pItem = GetItemAcc( pMember->GetName() ) )
1065             pItem->SetPropertiesFromMember( *pMember );
1066 }
1067 
SetDataPropertiesFromDim(const ScDPSaveDimension & rSaveDim)1068 void XclExpPTField::SetDataPropertiesFromDim( const ScDPSaveDimension& rSaveDim )
1069 {
1070     maDataInfoVec.emplace_back( );
1071     XclPTDataFieldInfo& rDataInfo = maDataInfoVec.back();
1072     rDataInfo.mnField = GetFieldIndex();
1073 
1074     // orientation
1075     maFieldInfo.AddApiOrient( DataPilotFieldOrientation_DATA );
1076 
1077     // aggregation function
1078     ScGeneralFunction eFunc = rSaveDim.GetFunction();
1079     rDataInfo.SetApiAggFunc( eFunc );
1080 
1081     // visible name
1082     const std::optional<OUString> & pVisName = rSaveDim.GetLayoutName();
1083     if (pVisName)
1084         rDataInfo.SetVisName(*pVisName);
1085     else
1086         rDataInfo.SetVisName( lclGetDataFieldCaption( GetFieldName(), eFunc ) );
1087 
1088     // result field reference
1089     if( const DataPilotFieldReference* pFieldRef = rSaveDim.GetReferenceValue() )
1090     {
1091         rDataInfo.SetApiRefType( pFieldRef->ReferenceType );
1092         rDataInfo.SetApiRefItemType( pFieldRef->ReferenceItemType );
1093         if( const XclExpPTField* pRefField = mrPTable.GetField( pFieldRef->ReferenceField ) )
1094         {
1095             rDataInfo.mnRefField = pRefField->GetFieldIndex();
1096             if( pFieldRef->ReferenceItemType == css::sheet::DataPilotFieldReferenceItemType::NAMED )
1097                 rDataInfo.mnRefItem = pRefField->GetItemIndex( pFieldRef->ReferenceItemName, 0 );
1098         }
1099     }
1100 }
1101 
AppendSubtotalItems()1102 void XclExpPTField::AppendSubtotalItems()
1103 {
1104     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_DEFAULT )   AppendSubtotalItem( EXC_SXVI_TYPE_DEFAULT );
1105     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_SUM )       AppendSubtotalItem( EXC_SXVI_TYPE_SUM );
1106     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_COUNT )     AppendSubtotalItem( EXC_SXVI_TYPE_COUNT );
1107     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_AVERAGE )   AppendSubtotalItem( EXC_SXVI_TYPE_AVERAGE );
1108     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_MAX )       AppendSubtotalItem( EXC_SXVI_TYPE_MAX );
1109     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_MIN )       AppendSubtotalItem( EXC_SXVI_TYPE_MIN );
1110     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_PROD )      AppendSubtotalItem( EXC_SXVI_TYPE_PROD );
1111     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_COUNTNUM )  AppendSubtotalItem( EXC_SXVI_TYPE_COUNTNUM );
1112     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_STDDEV )    AppendSubtotalItem( EXC_SXVI_TYPE_STDDEV );
1113     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_STDDEVP )   AppendSubtotalItem( EXC_SXVI_TYPE_STDDEVP );
1114     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_VAR )       AppendSubtotalItem( EXC_SXVI_TYPE_VAR );
1115     if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_VARP )      AppendSubtotalItem( EXC_SXVI_TYPE_VARP );
1116 }
1117 
1118 // records --------------------------------------------------------------------
1119 
WriteSxpiEntry(XclExpStream & rStrm) const1120 void XclExpPTField::WriteSxpiEntry( XclExpStream& rStrm ) const
1121 {
1122     rStrm << maPageInfo;
1123 }
1124 
WriteSxdi(XclExpStream & rStrm,sal_uInt16 nDataInfoIdx) const1125 void XclExpPTField::WriteSxdi( XclExpStream& rStrm, sal_uInt16 nDataInfoIdx ) const
1126 {
1127     OSL_ENSURE( nDataInfoIdx < maDataInfoVec.size(), "XclExpPTField::WriteSxdi - data field not found" );
1128     if( nDataInfoIdx < maDataInfoVec.size() )
1129     {
1130         rStrm.StartRecord( EXC_ID_SXDI, 12 );
1131         rStrm << maDataInfoVec[ nDataInfoIdx ];
1132         rStrm.EndRecord();
1133     }
1134 }
1135 
Save(XclExpStream & rStrm)1136 void XclExpPTField::Save( XclExpStream& rStrm )
1137 {
1138     // SXVD
1139     WriteSxvd( rStrm );
1140     // list of SXVI records
1141     maItemList.Save( rStrm );
1142     // SXVDEX
1143     WriteSxvdex( rStrm );
1144 }
1145 
1146 // private --------------------------------------------------------------------
1147 
GetItemAcc(std::u16string_view rName)1148 XclExpPTItem* XclExpPTField::GetItemAcc( std::u16string_view rName )
1149 {
1150     XclExpPTItem* pItem = nullptr;
1151     for( size_t nPos = 0, nSize = maItemList.GetSize(); !pItem && (nPos < nSize); ++nPos )
1152         if( maItemList.GetRecord( nPos )->GetItemName() == rName )
1153             pItem = maItemList.GetRecord( nPos );
1154     return pItem;
1155 }
1156 
AppendSubtotalItem(sal_uInt16 nItemType)1157 void XclExpPTField::AppendSubtotalItem( sal_uInt16 nItemType )
1158 {
1159     maItemList.AppendNewRecord( new XclExpPTItem( nItemType, EXC_SXVI_DEFAULT_CACHE ) );
1160     ++maFieldInfo.mnItemCount;
1161 }
1162 
WriteSxvd(XclExpStream & rStrm) const1163 void XclExpPTField::WriteSxvd( XclExpStream& rStrm ) const
1164 {
1165     rStrm.StartRecord( EXC_ID_SXVD, 10 );
1166     rStrm << maFieldInfo;
1167     rStrm.EndRecord();
1168 }
1169 
WriteSxvdex(XclExpStream & rStrm) const1170 void XclExpPTField::WriteSxvdex( XclExpStream& rStrm ) const
1171 {
1172     rStrm.StartRecord( EXC_ID_SXVDEX, 20 );
1173     rStrm << maFieldExtInfo;
1174     rStrm.EndRecord();
1175 }
1176 
XclExpPivotTable(const XclExpRoot & rRoot,const ScDPObject & rDPObj,const XclExpPivotCache & rPCache)1177 XclExpPivotTable::XclExpPivotTable( const XclExpRoot& rRoot, const ScDPObject& rDPObj, const XclExpPivotCache& rPCache ) :
1178     XclExpRoot( rRoot ),
1179     mrPCache( rPCache ),
1180     maDataOrientField( *this, EXC_SXIVD_DATA ),
1181     mnOutScTab( 0 ),
1182     mbValid( false ),
1183     mbFilterBtn( false )
1184 {
1185     const ScRange& rOutScRange = rDPObj.GetOutRange();
1186     if( !GetAddressConverter().ConvertRange( maPTInfo.maOutXclRange, rOutScRange, true ) )
1187         return;
1188 
1189     // DataPilot properties -----------------------------------------------
1190 
1191     // pivot table properties from DP object
1192     mnOutScTab = rOutScRange.aStart.Tab();
1193     maPTInfo.maTableName = rDPObj.GetName();
1194     maPTInfo.mnCacheIdx = mrPCache.GetCacheIndex();
1195 
1196     maPTViewEx9Info.Init( rDPObj );
1197 
1198     const ScDPSaveData* pSaveData = rDPObj.GetSaveData();
1199     if( !pSaveData )
1200         return;
1201 
1202     // additional properties from ScDPSaveData
1203     SetPropertiesFromDP( *pSaveData );
1204 
1205     // loop over all dimensions ---------------------------------------
1206 
1207     /*  1)  Default-construct all pivot table fields for all pivot cache fields. */
1208     for( sal_uInt16 nFieldIdx = 0, nFieldCount = mrPCache.GetFieldCount(); nFieldIdx < nFieldCount; ++nFieldIdx )
1209         maFieldList.AppendNewRecord( new XclExpPTField( *this, nFieldIdx ) );
1210 
1211     const ScDPSaveData::DimsType& rDimList = pSaveData->GetDimensions();
1212 
1213     /*  2)  First process all data dimensions, they are needed for extended
1214             settings of row/column/page fields (sorting/auto show). */
1215     for (auto const& iter : rDimList)
1216     {
1217         if (iter->GetOrientation() == DataPilotFieldOrientation_DATA)
1218             SetDataFieldPropertiesFromDim(*iter);
1219     }
1220 
1221     /*  3)  Row/column/page/hidden fields. */
1222     for (auto const& iter : rDimList)
1223     {
1224         if (iter->GetOrientation() != DataPilotFieldOrientation_DATA)
1225             SetFieldPropertiesFromDim(*iter);
1226     }
1227 
1228     // Finalize -------------------------------------------------------
1229 
1230     Finalize();
1231     mbValid = true;
1232 }
1233 
GetCacheField(sal_uInt16 nCacheIdx) const1234 const XclExpPCField* XclExpPivotTable::GetCacheField( sal_uInt16 nCacheIdx ) const
1235 {
1236     return mrPCache.GetField( nCacheIdx );
1237 }
1238 
GetField(sal_uInt16 nFieldIdx) const1239 const XclExpPTField* XclExpPivotTable::GetField( sal_uInt16 nFieldIdx ) const
1240 {
1241     return (nFieldIdx == EXC_SXIVD_DATA) ? &maDataOrientField : maFieldList.GetRecord( nFieldIdx );
1242 }
1243 
GetField(std::u16string_view rName) const1244 const XclExpPTField* XclExpPivotTable::GetField( std::u16string_view rName ) const
1245 {
1246     return const_cast< XclExpPivotTable* >( this )->GetFieldAcc( rName );
1247 }
1248 
GetDataFieldIndex(const OUString & rName,sal_uInt16 nDefaultIdx) const1249 sal_uInt16 XclExpPivotTable::GetDataFieldIndex( const OUString& rName, sal_uInt16 nDefaultIdx ) const
1250 {
1251     auto aIt = std::find_if(maDataFields.begin(), maDataFields.end(),
1252         [this, &rName](const XclPTDataFieldPos& rDataField) {
1253             const XclExpPTField* pField = GetField( rDataField.first );
1254             return pField && pField->GetFieldName() == rName;
1255         });
1256     if (aIt != maDataFields.end())
1257         return static_cast< sal_uInt16 >( std::distance(maDataFields.begin(), aIt) );
1258     return nDefaultIdx;
1259 }
1260 
Save(XclExpStream & rStrm)1261 void XclExpPivotTable::Save( XclExpStream& rStrm )
1262 {
1263     if( !mbValid )
1264         return;
1265 
1266     // SXVIEW
1267     WriteSxview( rStrm );
1268     // pivot table fields (SXVD, SXVDEX, and item records)
1269     maFieldList.Save( rStrm );
1270     // SXIVD records for row and column fields
1271     WriteSxivd( rStrm, maRowFields );
1272     WriteSxivd( rStrm, maColFields );
1273     // SXPI
1274     WriteSxpi( rStrm );
1275     // list of SXDI records containing data field info
1276     WriteSxdiList( rStrm );
1277     // SXLI records
1278     WriteSxli( rStrm, maPTInfo.mnDataRows, maPTInfo.mnRowFields );
1279     WriteSxli( rStrm, maPTInfo.mnDataCols, maPTInfo.mnColFields );
1280     // SXEX
1281     WriteSxex( rStrm );
1282     // QSISXTAG
1283     WriteQsiSxTag( rStrm );
1284     // SXVIEWEX9
1285     WriteSxViewEx9( rStrm );
1286 }
1287 
GetFieldAcc(std::u16string_view rName)1288 XclExpPTField* XclExpPivotTable::GetFieldAcc( std::u16string_view rName )
1289 {
1290     XclExpPTField* pField = nullptr;
1291     for( size_t nPos = 0, nSize = maFieldList.GetSize(); !pField && (nPos < nSize); ++nPos )
1292         if( maFieldList.GetRecord( nPos )->GetFieldName() == rName )
1293             pField = maFieldList.GetRecord( nPos );
1294     return pField;
1295 }
1296 
GetFieldAcc(const ScDPSaveDimension & rSaveDim)1297 XclExpPTField* XclExpPivotTable::GetFieldAcc( const ScDPSaveDimension& rSaveDim )
1298 {
1299     // data field orientation field?
1300     if( rSaveDim.IsDataLayout() )
1301         return &maDataOrientField;
1302 
1303     // a real dimension
1304     OUString aFieldName = ScDPUtil::getSourceDimensionName(rSaveDim.GetName());
1305     return aFieldName.isEmpty() ? nullptr : GetFieldAcc(aFieldName);
1306 }
1307 
1308 // fill data --------------------------------------------------------------
1309 
SetPropertiesFromDP(const ScDPSaveData & rSaveData)1310 void XclExpPivotTable::SetPropertiesFromDP( const ScDPSaveData& rSaveData )
1311 {
1312     ::set_flag( maPTInfo.mnFlags, EXC_SXVIEW_ROWGRAND, rSaveData.GetRowGrand() );
1313     ::set_flag( maPTInfo.mnFlags, EXC_SXVIEW_COLGRAND, rSaveData.GetColumnGrand() );
1314     ::set_flag( maPTExtInfo.mnFlags, EXC_SXEX_DRILLDOWN, rSaveData.GetDrillDown() );
1315     mbFilterBtn = rSaveData.GetFilterButton();
1316     const ScDPSaveDimension* pDim = rSaveData.GetExistingDataLayoutDimension();
1317 
1318     if (pDim && pDim->GetLayoutName())
1319         maPTInfo.maDataName = *pDim->GetLayoutName();
1320     else
1321         maPTInfo.maDataName = ScResId(STR_PIVOT_DATA);
1322 }
1323 
SetFieldPropertiesFromDim(const ScDPSaveDimension & rSaveDim)1324 void XclExpPivotTable::SetFieldPropertiesFromDim( const ScDPSaveDimension& rSaveDim )
1325 {
1326     XclExpPTField* pField = GetFieldAcc( rSaveDim );
1327     if(!pField)
1328         return;
1329 
1330     // field properties
1331     pField->SetPropertiesFromDim( rSaveDim );
1332 
1333     // update the corresponding field position list
1334     DataPilotFieldOrientation eOrient = rSaveDim.GetOrientation();
1335     sal_uInt16 nFieldIdx = pField->GetFieldIndex();
1336     bool bDataLayout = nFieldIdx == EXC_SXIVD_DATA;
1337     bool bMultiData = maDataFields.size() > 1;
1338 
1339     if( bDataLayout && !bMultiData )
1340         return;
1341 
1342     switch( eOrient )
1343     {
1344         case DataPilotFieldOrientation_ROW:
1345             maRowFields.push_back( nFieldIdx );
1346             if( bDataLayout )
1347                 maPTInfo.mnDataAxis = EXC_SXVD_AXIS_ROW;
1348         break;
1349         case DataPilotFieldOrientation_COLUMN:
1350             maColFields.push_back( nFieldIdx );
1351             if( bDataLayout )
1352                 maPTInfo.mnDataAxis = EXC_SXVD_AXIS_COL;
1353         break;
1354         case DataPilotFieldOrientation_PAGE:
1355             maPageFields.push_back( nFieldIdx );
1356             OSL_ENSURE( !bDataLayout, "XclExpPivotTable::SetFieldPropertiesFromDim - wrong orientation for data fields" );
1357         break;
1358         case DataPilotFieldOrientation_DATA:
1359             OSL_FAIL( "XclExpPivotTable::SetFieldPropertiesFromDim - called for data field" );
1360         break;
1361         default:;
1362     }
1363 }
1364 
SetDataFieldPropertiesFromDim(const ScDPSaveDimension & rSaveDim)1365 void XclExpPivotTable::SetDataFieldPropertiesFromDim( const ScDPSaveDimension& rSaveDim )
1366 {
1367     if( XclExpPTField* pField = GetFieldAcc( rSaveDim ) )
1368     {
1369         // field properties
1370         pField->SetDataPropertiesFromDim( rSaveDim );
1371         // update the data field position list
1372         maDataFields.emplace_back( pField->GetFieldIndex(), pField->GetLastDataInfoIndex() );
1373     }
1374 }
1375 
Finalize()1376 void XclExpPivotTable::Finalize()
1377 {
1378     // field numbers
1379     maPTInfo.mnFields = static_cast< sal_uInt16 >( maFieldList.GetSize() );
1380     maPTInfo.mnRowFields = static_cast< sal_uInt16 >( maRowFields.size() );
1381     maPTInfo.mnColFields = static_cast< sal_uInt16 >( maColFields.size() );
1382     maPTInfo.mnPageFields = static_cast< sal_uInt16 >( maPageFields.size() );
1383     maPTInfo.mnDataFields = static_cast< sal_uInt16 >( maDataFields.size() );
1384 
1385     maPTExtInfo.mnPagePerRow = maPTInfo.mnPageFields;
1386     maPTExtInfo.mnPagePerCol = (maPTInfo.mnPageFields > 0) ? 1 : 0;
1387 
1388     // subtotal items
1389     for( size_t nPos = 0, nSize = maFieldList.GetSize(); nPos < nSize; ++nPos )
1390         maFieldList.GetRecord( nPos )->AppendSubtotalItems();
1391 
1392     // find data field orientation field
1393     maPTInfo.mnDataPos = EXC_SXVIEW_DATALAST;
1394     const ScfUInt16Vec* pFieldVec = nullptr;
1395     switch( maPTInfo.mnDataAxis )
1396     {
1397         case EXC_SXVD_AXIS_ROW: pFieldVec = &maRowFields;   break;
1398         case EXC_SXVD_AXIS_COL: pFieldVec = &maColFields;   break;
1399     }
1400 
1401     if( pFieldVec && !pFieldVec->empty() && (pFieldVec->back() != EXC_SXIVD_DATA) )
1402     {
1403         ScfUInt16Vec::const_iterator aIt = ::std::find( pFieldVec->begin(), pFieldVec->end(), EXC_SXIVD_DATA );
1404         if( aIt != pFieldVec->end() )
1405             maPTInfo.mnDataPos = static_cast< sal_uInt16 >( std::distance(pFieldVec->begin(), aIt) );
1406     }
1407 
1408     // single data field is always row oriented
1409     if( maPTInfo.mnDataAxis == EXC_SXVD_AXIS_NONE )
1410         maPTInfo.mnDataAxis = EXC_SXVD_AXIS_ROW;
1411 
1412     // update output range (initialized in ctor)
1413     sal_uInt16& rnXclCol1 = maPTInfo.maOutXclRange.maFirst.mnCol;
1414     sal_uInt32& rnXclRow1 = maPTInfo.maOutXclRange.maFirst.mnRow;
1415     sal_uInt16& rnXclCol2 = maPTInfo.maOutXclRange.maLast.mnCol;
1416     sal_uInt32& rnXclRow2 = maPTInfo.maOutXclRange.maLast.mnRow;
1417     // exclude page fields from output range
1418     rnXclRow1 = rnXclRow1 + maPTInfo.mnPageFields;
1419     // exclude filter button from output range
1420     if( mbFilterBtn )
1421         ++rnXclRow1;
1422     // exclude empty row between (filter button and/or page fields) and table
1423     if( mbFilterBtn || maPTInfo.mnPageFields )
1424         ++rnXclRow1;
1425 
1426     // data area
1427     sal_uInt16& rnDataXclCol = maPTInfo.maDataXclPos.mnCol;
1428     sal_uInt32& rnDataXclRow = maPTInfo.maDataXclPos.mnRow;
1429     rnDataXclCol = rnXclCol1 + maPTInfo.mnRowFields;
1430     rnDataXclRow = rnXclRow1 + maPTInfo.mnColFields + 1;
1431     if( maDataFields.empty() )
1432         ++rnDataXclRow;
1433 
1434     bool bExtraHeaderRow = (0 == maPTViewEx9Info.mnGridLayout && maPTInfo.mnColFields == 0);
1435     if (bExtraHeaderRow)
1436         // Insert an extra row only when there is no column field.
1437         ++rnDataXclRow;
1438 
1439     rnXclCol2 = ::std::max( rnXclCol2, rnDataXclCol );
1440     rnXclRow2 = ::std::max( rnXclRow2, rnDataXclRow );
1441     maPTInfo.mnDataCols = rnXclCol2 - rnDataXclCol + 1;
1442     maPTInfo.mnDataRows = rnXclRow2 - rnDataXclRow + 1;
1443 
1444     // first heading
1445     maPTInfo.mnFirstHeadRow = rnXclRow1 + 1;
1446     if (bExtraHeaderRow)
1447         maPTInfo.mnFirstHeadRow += 1;
1448 }
1449 
1450 // records ----------------------------------------------------------------
1451 
WriteSxview(XclExpStream & rStrm) const1452 void XclExpPivotTable::WriteSxview( XclExpStream& rStrm ) const
1453 {
1454     rStrm.StartRecord( EXC_ID_SXVIEW, 46 + maPTInfo.maTableName.getLength() + maPTInfo.maDataName.getLength() );
1455     rStrm << maPTInfo;
1456     rStrm.EndRecord();
1457 }
1458 
WriteSxivd(XclExpStream & rStrm,const ScfUInt16Vec & rFields)1459 void XclExpPivotTable::WriteSxivd( XclExpStream& rStrm, const ScfUInt16Vec& rFields )
1460 {
1461     if( !rFields.empty() )
1462     {
1463         rStrm.StartRecord( EXC_ID_SXIVD, rFields.size() * 2 );
1464         for( const auto& rField : rFields )
1465             rStrm << rField;
1466         rStrm.EndRecord();
1467     }
1468 }
1469 
WriteSxpi(XclExpStream & rStrm) const1470 void XclExpPivotTable::WriteSxpi( XclExpStream& rStrm ) const
1471 {
1472     if( !maPageFields.empty() )
1473     {
1474         rStrm.StartRecord( EXC_ID_SXPI, maPageFields.size() * 6 );
1475         rStrm.SetSliceSize( 6 );
1476         for( const auto& rPageField : maPageFields )
1477         {
1478             XclExpPTFieldRef xField = maFieldList.GetRecord( rPageField );
1479             if( xField )
1480                 xField->WriteSxpiEntry( rStrm );
1481         }
1482         rStrm.EndRecord();
1483     }
1484 }
1485 
WriteSxdiList(XclExpStream & rStrm) const1486 void XclExpPivotTable::WriteSxdiList( XclExpStream& rStrm ) const
1487 {
1488     for( const auto& [rFieldIdx, rDataInfoIdx] : maDataFields )
1489     {
1490         XclExpPTFieldRef xField = maFieldList.GetRecord( rFieldIdx );
1491         if( xField )
1492             xField->WriteSxdi( rStrm, rDataInfoIdx );
1493     }
1494 }
1495 
WriteSxli(XclExpStream & rStrm,sal_uInt16 nLineCount,sal_uInt16 nIndexCount)1496 void XclExpPivotTable::WriteSxli( XclExpStream& rStrm, sal_uInt16 nLineCount, sal_uInt16 nIndexCount )
1497 {
1498     if( nLineCount <= 0 )
1499         return;
1500 
1501     std::size_t nLineSize = 8 + 2 * nIndexCount;
1502     rStrm.StartRecord( EXC_ID_SXLI, nLineSize * nLineCount );
1503 
1504     /*  Excel expects the records to be filled completely, do not
1505         set a segment size... */
1506 //        rStrm.SetSliceSize( nLineSize );
1507 
1508     for( sal_uInt16 nLine = 0; nLine < nLineCount; ++nLine )
1509     {
1510         // Excel XP needs a partly initialized SXLI record
1511         rStrm   << sal_uInt16( 0 )      // number of equal index entries
1512                 << EXC_SXVI_TYPE_DATA
1513                 << nIndexCount
1514                 << EXC_SXLI_DEFAULTFLAGS;
1515         rStrm.WriteZeroBytes( 2 * nIndexCount );
1516     }
1517     rStrm.EndRecord();
1518 }
1519 
WriteSxex(XclExpStream & rStrm) const1520 void XclExpPivotTable::WriteSxex( XclExpStream& rStrm ) const
1521 {
1522     rStrm.StartRecord( EXC_ID_SXEX, 24 );
1523     rStrm << maPTExtInfo;
1524     rStrm.EndRecord();
1525 }
1526 
WriteQsiSxTag(XclExpStream & rStrm) const1527 void XclExpPivotTable::WriteQsiSxTag( XclExpStream& rStrm ) const
1528 {
1529     rStrm.StartRecord( 0x0802, 32 );
1530 
1531     sal_uInt16 const nRecordType = 0x0802;
1532     sal_uInt16 const nDummyFlags = 0x0000;
1533     sal_uInt16 const nTableType  = 1; // 0 = query table : 1 = pivot table
1534 
1535     rStrm << nRecordType << nDummyFlags << nTableType;
1536 
1537     // General flags
1538     sal_uInt16 const nFlags = 0x0001;
1539 #if 0
1540     // for doc purpose
1541     sal_uInt16 nFlags = 0x0000;
1542     bool bEnableRefresh = true;
1543     bool bPCacheInvalid = false;
1544     bool bOlapPTReport  = false;
1545 
1546     if (bEnableRefresh) nFlags |= 0x0001;
1547     if (bPCacheInvalid) nFlags |= 0x0002;
1548     if (bOlapPTReport)  nFlags |= 0x0004;
1549 #endif
1550     rStrm << nFlags;
1551 
1552     // Feature-specific options.  The value differs depending on the table
1553     // type, but we assume the table type is always pivot table.
1554     sal_uInt32 const nOptions = 0x00000000;
1555 #if 0
1556     // documentation for which bit is for what
1557     bool bNoStencil = false;
1558     bool bHideTotal = false;
1559     bool bEmptyRows = false;
1560     bool bEmptyCols = false;
1561     if (bNoStencil) nOptions |= 0x00000001;
1562     if (bHideTotal) nOptions |= 0x00000002;
1563     if (bEmptyRows) nOptions |= 0x00000008;
1564     if (bEmptyCols) nOptions |= 0x00000010;
1565 #endif
1566     rStrm << nOptions;
1567 
1568     sal_uInt8 eXclVer = 0; // Excel2000
1569     sal_uInt8 const nOffsetBytes = 16;
1570     rStrm << eXclVer  // version table last refreshed
1571           << eXclVer  // minimum version to refresh
1572           << nOffsetBytes
1573           << eXclVer; // first version created
1574 
1575     rStrm << XclExpString(maPTInfo.maTableName);
1576     rStrm << static_cast<sal_uInt16>(0x0001); // no idea what this is for.
1577 
1578     rStrm.EndRecord();
1579 }
1580 
WriteSxViewEx9(XclExpStream & rStrm) const1581 void XclExpPivotTable::WriteSxViewEx9( XclExpStream& rStrm ) const
1582 {
1583     // Until we sync the autoformat ids only export if using grid header layout
1584     // That could only have been set via xls import so far.
1585     if ( 0 == maPTViewEx9Info.mnGridLayout )
1586     {
1587         rStrm.StartRecord( EXC_ID_SXVIEWEX9, 17 );
1588         rStrm << maPTViewEx9Info;
1589         rStrm.EndRecord();
1590     }
1591 }
1592 
1593 namespace {
1594 
1595 const SCTAB EXC_PTMGR_PIVOTCACHES = SCTAB_MAX;
1596 
1597 /** Record wrapper class to write the pivot caches or pivot tables. */
1598 class XclExpPivotRecWrapper : public XclExpRecordBase
1599 {
1600 public:
1601     explicit            XclExpPivotRecWrapper( XclExpPivotTableManager& rPTMgr, SCTAB nScTab );
1602     virtual void        Save( XclExpStream& rStrm ) override;
1603 private:
1604     XclExpPivotTableManager& mrPTMgr;
1605     SCTAB               mnScTab;
1606 };
1607 
XclExpPivotRecWrapper(XclExpPivotTableManager & rPTMgr,SCTAB nScTab)1608 XclExpPivotRecWrapper::XclExpPivotRecWrapper( XclExpPivotTableManager& rPTMgr, SCTAB nScTab ) :
1609     mrPTMgr( rPTMgr ),
1610     mnScTab( nScTab )
1611 {
1612 }
1613 
Save(XclExpStream & rStrm)1614 void XclExpPivotRecWrapper::Save( XclExpStream& rStrm )
1615 {
1616     if( mnScTab == EXC_PTMGR_PIVOTCACHES )
1617         mrPTMgr.WritePivotCaches( rStrm );
1618     else
1619         mrPTMgr.WritePivotTables( rStrm, mnScTab );
1620 }
1621 
1622 } // namespace
1623 
XclExpPivotTableManager(const XclExpRoot & rRoot)1624 XclExpPivotTableManager::XclExpPivotTableManager( const XclExpRoot& rRoot ) :
1625     XclExpRoot( rRoot )
1626 {
1627 }
1628 
CreatePivotTables()1629 void XclExpPivotTableManager::CreatePivotTables()
1630 {
1631     if( ScDPCollection* pDPColl = GetDoc().GetDPCollection() )
1632         for( size_t nDPObj = 0, nCount = pDPColl->GetCount(); nDPObj < nCount; ++nDPObj )
1633         {
1634             ScDPObject& rDPObj = (*pDPColl)[ nDPObj ];
1635             if( const XclExpPivotCache* pPCache = CreatePivotCache( rDPObj ) )
1636                 maPTableList.AppendNewRecord( new XclExpPivotTable( GetRoot(), rDPObj, *pPCache ) );
1637         }
1638 }
1639 
CreatePivotCachesRecord()1640 XclExpRecordRef XclExpPivotTableManager::CreatePivotCachesRecord()
1641 {
1642     return new XclExpPivotRecWrapper( *this, EXC_PTMGR_PIVOTCACHES );
1643 }
1644 
CreatePivotTablesRecord(SCTAB nScTab)1645 XclExpRecordRef XclExpPivotTableManager::CreatePivotTablesRecord( SCTAB nScTab )
1646 {
1647     return new XclExpPivotRecWrapper( *this, nScTab );
1648 }
1649 
WritePivotCaches(XclExpStream & rStrm)1650 void XclExpPivotTableManager::WritePivotCaches( XclExpStream& rStrm )
1651 {
1652     maPCacheList.Save( rStrm );
1653 }
1654 
WritePivotTables(XclExpStream & rStrm,SCTAB nScTab)1655 void XclExpPivotTableManager::WritePivotTables( XclExpStream& rStrm, SCTAB nScTab )
1656 {
1657     for( size_t nPos = 0, nSize = maPTableList.GetSize(); nPos < nSize; ++nPos )
1658     {
1659         XclExpPivotTableRef xPTable = maPTableList.GetRecord( nPos );
1660         if( xPTable->GetScTab() == nScTab )
1661             xPTable->Save( rStrm );
1662     }
1663 }
1664 
CreatePivotCache(const ScDPObject & rDPObj)1665 const XclExpPivotCache* XclExpPivotTableManager::CreatePivotCache( const ScDPObject& rDPObj )
1666 {
1667     // try to find a pivot cache with the same data source
1668     /*  #i25110# In Excel, the pivot cache contains additional fields
1669         (i.e. grouping info, calculated fields). If the passed DataPilot object
1670         or the found cache contains this data, do not share the cache with
1671         multiple pivot tables. */
1672     if( const ScDPSaveData* pSaveData = rDPObj.GetSaveData() )
1673     {
1674         const ScDPDimensionSaveData* pDimSaveData = pSaveData->GetExistingDimensionData();
1675         // no dimension save data at all or save data does not contain grouping info
1676         if( !pDimSaveData || !pDimSaveData->HasGroupDimensions() )
1677         {
1678             // check all existing pivot caches
1679             for( size_t nPos = 0, nSize = maPCacheList.GetSize(); nPos < nSize; ++nPos )
1680             {
1681                 XclExpPivotCache* pPCache = maPCacheList.GetRecord( nPos );
1682                 // pivot cache does not have grouping info and source data is equal
1683                 if( !pPCache->HasAddFields() && pPCache->HasEqualDataSource( rDPObj ) )
1684                     return pPCache;
1685             }
1686         }
1687     }
1688 
1689     // create a new pivot cache
1690     sal_uInt16 nNewCacheIdx = static_cast< sal_uInt16 >( maPCacheList.GetSize() );
1691     XclExpPivotCacheRef xNewPCache = new XclExpPivotCache( GetRoot(), rDPObj, nNewCacheIdx );
1692     if( xNewPCache->IsValid() )
1693     {
1694         maPCacheList.AppendRecord( xNewPCache.get() );
1695         return xNewPCache.get();
1696     }
1697 
1698     return nullptr;
1699 }
1700 
1701 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1702