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