1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
10 #include <xepivotxml.hxx>
11 #include <dpcache.hxx>
12 #include <pivot/PivotTableFormats.hxx>
13 #include <dpdimsave.hxx>
14 #include <dpitemdata.hxx>
15 #include <dpobject.hxx>
18 #include <document.hxx>
19 #include <generalfunction.hxx>
20 #include <unonames.hxx>
21 #include <xestyle.hxx>
24 #include <o3tl/temporary.hxx>
25 #include <o3tl/safeint.hxx>
26 #include <oox/export/utils.hxx>
27 #include <oox/token/namespaces.hxx>
28 #include <sal/log.hxx>
29 #include <sax/tools/converter.hxx>
30 #include <sax/fastattribs.hxx>
31 #include <sax/fshelper.hxx>
32 #include <svl/numformat.hxx>
34 #include <com/sun/star/beans/XPropertySet.hpp>
35 #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
36 #include <com/sun/star/sheet/DataPilotFieldOrientation.hpp>
37 #include <com/sun/star/sheet/DataPilotFieldLayoutMode.hpp>
38 #include <com/sun/star/sheet/DataPilotOutputRangeType.hpp>
39 #include <com/sun/star/sheet/XDimensionsSupplier.hpp>
44 using namespace com::sun::star
;
48 void savePivotCacheRecordsXml( XclExpXmlStream
& rStrm
, const ScDPCache
& rCache
)
50 SCROW nCount
= rCache
.GetDataSize();
51 size_t nFieldCount
= rCache
.GetFieldCount();
53 sax_fastparser::FSHelperPtr
& pRecStrm
= rStrm
.GetCurrentStream();
54 pRecStrm
->startElement(XML_pivotCacheRecords
,
55 XML_xmlns
, rStrm
.getNamespaceURL(OOX_NS(xls
)).toUtf8(),
56 FSNS(XML_xmlns
, XML_r
), rStrm
.getNamespaceURL(OOX_NS(officeRel
)).toUtf8(),
57 XML_count
, OString::number(static_cast<tools::Long
>(nCount
)));
59 for (SCROW i
= 0; i
< nCount
; ++i
)
61 pRecStrm
->startElement(XML_r
);
62 for (size_t nField
= 0; nField
< nFieldCount
; ++nField
)
64 const ScDPCache::IndexArrayType
* pArray
= rCache
.GetFieldIndexArray(nField
);
66 assert(o3tl::make_unsigned(i
) < pArray
->size());
68 // We are using XML_x reference (like: <x v="0"/>), instead of values here (eg: <s v="No Discount"/>).
69 // That's why in SavePivotCacheXml method, we need to list all items.
70 pRecStrm
->singleElement(XML_x
, XML_v
, OString::number((*pArray
)[i
]));
72 pRecStrm
->endElement(XML_r
);
75 pRecStrm
->endElement(XML_pivotCacheRecords
);
78 const char* toOOXMLAxisType( sheet::DataPilotFieldOrientation eOrient
)
82 case sheet::DataPilotFieldOrientation_COLUMN
:
84 case sheet::DataPilotFieldOrientation_ROW
:
86 case sheet::DataPilotFieldOrientation_PAGE
:
88 case sheet::DataPilotFieldOrientation_DATA
:
90 case sheet::DataPilotFieldOrientation_HIDDEN
:
98 const char* toOOXMLSubtotalType(ScGeneralFunction eFunc
)
102 case ScGeneralFunction::SUM
:
104 case ScGeneralFunction::COUNT
:
106 case ScGeneralFunction::AVERAGE
:
108 case ScGeneralFunction::MAX
:
110 case ScGeneralFunction::MIN
:
112 case ScGeneralFunction::PRODUCT
:
114 case ScGeneralFunction::COUNTNUMS
:
116 case ScGeneralFunction::STDEV
:
118 case ScGeneralFunction::STDEVP
:
120 case ScGeneralFunction::VAR
:
122 case ScGeneralFunction::VARP
:
132 XclExpXmlPivotCaches::XclExpXmlPivotCaches( const XclExpRoot
& rRoot
) :
135 void XclExpXmlPivotCaches::SaveXml( XclExpXmlStream
& rStrm
)
137 sax_fastparser::FSHelperPtr
& pWorkbookStrm
= rStrm
.GetCurrentStream();
138 pWorkbookStrm
->startElement(XML_pivotCaches
);
140 for (size_t i
= 0, n
= maCaches
.size(); i
< n
; ++i
)
142 const Entry
& rEntry
= maCaches
[i
];
144 sal_Int32 nCacheId
= i
+ 1;
146 sax_fastparser::FSHelperPtr pPCStrm
= rStrm
.CreateOutputStream(
147 XclXmlUtils::GetStreamName("xl/pivotCache/", "pivotCacheDefinition", nCacheId
),
148 XclXmlUtils::GetStreamName(nullptr, "pivotCache/pivotCacheDefinition", nCacheId
),
149 rStrm
.GetCurrentStream()->getOutputStream(),
150 CREATE_XL_CONTENT_TYPE("pivotCacheDefinition"),
151 CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheDefinition"),
154 pWorkbookStrm
->singleElement(XML_pivotCache
,
155 XML_cacheId
, OString::number(nCacheId
),
156 FSNS(XML_r
, XML_id
), aRelId
.toUtf8());
158 rStrm
.PushStream(pPCStrm
);
159 SavePivotCacheXml(rStrm
, rEntry
, nCacheId
);
163 pWorkbookStrm
->endElement(XML_pivotCaches
);
166 void XclExpXmlPivotCaches::SetCaches( std::vector
<Entry
>&& rCaches
)
168 maCaches
= std::move(rCaches
);
171 bool XclExpXmlPivotCaches::HasCaches() const
173 return !maCaches
.empty();
176 const XclExpXmlPivotCaches::Entry
* XclExpXmlPivotCaches::GetCache( sal_Int32 nCacheId
) const
179 // cache ID is 1-based.
182 size_t nPos
= nCacheId
- 1;
183 if (nPos
>= maCaches
.size())
186 return &maCaches
[nPos
];
191 * Create combined date and time string according the requirements of Excel.
192 * A single point in time can be represented by concatenating a complete date expression,
193 * the letter T as a delimiter, and a valid time expression. For example, "2007-04-05T14:30".
195 * fSerialDateTime - a number representing the number of days since 1900-Jan-0 (integer portion of the number),
196 * plus a fractional portion of a 24 hour day (fractional portion of the number).
198 OUString
GetExcelFormattedDate( double fSerialDateTime
, const SvNumberFormatter
& rFormatter
)
200 // tdf#125055: properly round the value to seconds when truncating nanoseconds below
201 constexpr double fHalfSecond
= 1 / 86400.0 * 0.5;
202 css::util::DateTime aUDateTime
203 = (DateTime(rFormatter
.GetNullDate()) + fSerialDateTime
+ fHalfSecond
).GetUNODateTime();
204 // We need to reset nanoseconds, to avoid string like: "1982-02-18T16:04:47.999999849"
205 aUDateTime
.NanoSeconds
= 0;
207 ::sax::Converter::convertDateTime(sBuf
, aUDateTime
, nullptr, true);
208 return sBuf
.makeStringAndClear();
211 // Excel seems to expect different order of group item values; we need to rearrange elements
212 // to output "<date1" first, then elements, then ">date2" last.
213 // Since ScDPItemData::DateFirst is -1, ScDPItemData::DateLast is 10000, and other date group
214 // items would fit between those in order (like 0 = Jan, 1 = Feb, etc.), we can simply sort
215 // the items by value.
216 std::vector
<OUString
> SortGroupItems(const ScDPCache
& rCache
, tools::Long nDim
)
221 const ScDPItemData
* pData
;
223 std::vector
<ItemData
> aDataToSort
;
225 rCache
.GetGroupDimMemberIds(nDim
, aGIIds
);
226 for (sal_Int32 id
: aGIIds
)
228 const ScDPItemData
* pGIData
= rCache
.GetItemDataById(nDim
, id
);
229 if (pGIData
->GetType() == ScDPItemData::GroupValue
)
231 auto aGroupVal
= pGIData
->GetGroupValue();
232 aDataToSort
.push_back({ aGroupVal
.mnValue
, pGIData
});
235 std::sort(aDataToSort
.begin(), aDataToSort
.end(),
236 [](const ItemData
& a
, const ItemData
& b
) { return a
.nVal
< b
.nVal
; });
238 std::vector
<OUString
> aSortedResult
;
239 for (const auto& el
: aDataToSort
)
241 aSortedResult
.push_back(rCache
.GetFormattedString(nDim
, *el
.pData
, false));
243 return aSortedResult
;
247 void XclExpXmlPivotCaches::SavePivotCacheXml( XclExpXmlStream
& rStrm
, const Entry
& rEntry
, sal_Int32 nCounter
)
249 assert(rEntry
.mpCache
);
250 const ScDPCache
& rCache
= *rEntry
.mpCache
;
252 sax_fastparser::FSHelperPtr
& pDefStrm
= rStrm
.GetCurrentStream();
255 sax_fastparser::FSHelperPtr pRecStrm
= rStrm
.CreateOutputStream(
256 XclXmlUtils::GetStreamName("xl/pivotCache/", "pivotCacheRecords", nCounter
),
257 XclXmlUtils::GetStreamName(nullptr, "pivotCacheRecords", nCounter
),
258 pDefStrm
->getOutputStream(),
259 CREATE_XL_CONTENT_TYPE("pivotCacheRecords"),
260 CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheRecords"),
263 rStrm
.PushStream(pRecStrm
);
264 savePivotCacheRecordsXml(rStrm
, rCache
);
267 pDefStrm
->startElement(XML_pivotCacheDefinition
,
268 XML_xmlns
, rStrm
.getNamespaceURL(OOX_NS(xls
)).toUtf8(),
269 FSNS(XML_xmlns
, XML_r
), rStrm
.getNamespaceURL(OOX_NS(officeRel
)).toUtf8(),
270 FSNS(XML_r
, XML_id
), aRelId
.toUtf8(),
271 XML_recordCount
, OString::number(rEntry
.mpCache
->GetDataSize()),
272 XML_createdVersion
, "3"); // MS Excel 2007, tdf#112936: setting version number makes MSO to handle the pivot table differently
274 pDefStrm
->startElement(XML_cacheSource
, XML_type
, "worksheet");
277 GetDoc().GetName(rEntry
.maSrcRange
.aStart
.Tab(), aSheetName
);
278 pDefStrm
->singleElement(XML_worksheetSource
,
279 XML_ref
, XclXmlUtils::ToOString(rStrm
.GetRoot().GetDoc(), rEntry
.maSrcRange
),
280 XML_sheet
, aSheetName
.toUtf8());
282 pDefStrm
->endElement(XML_cacheSource
);
284 size_t nCount
= rCache
.GetFieldCount();
285 const size_t nGroupFieldCount
= rCache
.GetGroupFieldCount();
286 pDefStrm
->startElement(XML_cacheFields
,
287 XML_count
, OString::number(static_cast<tools::Long
>(nCount
+ nGroupFieldCount
)));
289 auto WriteFieldGroup
= [this, &rCache
, pDefStrm
](size_t i
, size_t base
) {
290 const sal_Int32 nDatePart
= rCache
.GetGroupType(i
);
296 case sheet::DataPilotFieldGroupBy::SECONDS
:
297 sGroupBy
= "seconds"_ostr
;
299 case sheet::DataPilotFieldGroupBy::MINUTES
:
300 sGroupBy
= "minutes"_ostr
;
302 case sheet::DataPilotFieldGroupBy::HOURS
:
303 sGroupBy
= "hours"_ostr
;
305 case sheet::DataPilotFieldGroupBy::DAYS
:
306 sGroupBy
= "days"_ostr
;
308 case sheet::DataPilotFieldGroupBy::MONTHS
:
309 sGroupBy
= "months"_ostr
;
311 case sheet::DataPilotFieldGroupBy::QUARTERS
:
312 sGroupBy
= "quarters"_ostr
;
314 case sheet::DataPilotFieldGroupBy::YEARS
:
315 sGroupBy
= "years"_ostr
;
319 // fieldGroup element
320 pDefStrm
->startElement(XML_fieldGroup
, XML_base
, OString::number(base
));
322 SvNumberFormatter
& rFormatter
= GetFormatter();
325 const ScDPNumGroupInfo
* pGI
= rCache
.GetNumGroupInfo(i
);
326 auto pGroupAttList
= sax_fastparser::FastSerializerHelper::createAttrList();
327 pGroupAttList
->add(XML_groupBy
, sGroupBy
);
328 // Possible TODO: find out when to write autoStart attribute for years grouping
329 pGroupAttList
->add(XML_startDate
, GetExcelFormattedDate(pGI
->mfStart
, rFormatter
).toUtf8());
330 pGroupAttList
->add(XML_endDate
, GetExcelFormattedDate(pGI
->mfEnd
, rFormatter
).toUtf8());
332 pGroupAttList
->add(XML_groupInterval
, OString::number(pGI
->mfStep
));
333 pDefStrm
->singleElement(XML_rangePr
, pGroupAttList
);
335 // groupItems element
336 auto aElemVec
= SortGroupItems(rCache
, i
);
337 pDefStrm
->startElement(XML_groupItems
, XML_count
, OString::number(aElemVec
.size()));
338 for (const auto& sElem
: aElemVec
)
340 pDefStrm
->singleElement(XML_s
, XML_v
, sElem
.toUtf8());
342 pDefStrm
->endElement(XML_groupItems
);
343 pDefStrm
->endElement(XML_fieldGroup
);
346 for (size_t i
= 0; i
< nCount
; ++i
)
348 OUString aName
= rCache
.GetDimensionName(i
);
350 pDefStrm
->startElement(XML_cacheField
,
351 XML_name
, aName
.toUtf8(),
352 XML_numFmtId
, OString::number(0));
354 const ScDPCache::ScDPItemDataVec
& rFieldItems
= rCache
.GetDimMemberValues(i
);
356 std::set
<ScDPItemData::Type
> aDPTypes
;
357 double fMin
= std::numeric_limits
<double>::infinity(), fMax
= -std::numeric_limits
<double>::infinity();
358 bool isValueInteger
= true;
359 bool isContainsDate
= rCache
.IsDateDimension(i
);
360 bool isLongText
= false;
361 for (const auto& rFieldItem
: rFieldItems
)
363 ScDPItemData::Type eType
= rFieldItem
.GetType();
364 // tdf#123939 : error and string are same for cache; if both are present, keep only one
365 if (eType
== ScDPItemData::Error
)
366 eType
= ScDPItemData::String
;
367 aDPTypes
.insert(eType
);
368 if (eType
== ScDPItemData::Value
)
370 double fVal
= rFieldItem
.GetValue();
371 fMin
= std::min(fMin
, fVal
);
372 fMax
= std::max(fMax
, fVal
);
374 // Check if all values are integers
375 if (isValueInteger
&& (modf(fVal
, &o3tl::temporary(double())) != 0.0))
377 isValueInteger
= false;
380 else if (eType
== ScDPItemData::String
&& !isLongText
)
382 isLongText
= rFieldItem
.GetString().getLength() > 255;
386 auto pAttList
= sax_fastparser::FastSerializerHelper::createAttrList();
387 // TODO In same cases, disable listing of items, as it is done in MS Excel.
388 // Exporting savePivotCacheRecordsXml method needs to be updated accordingly
389 //bool bListItems = true;
391 std::set
<ScDPItemData::Type
> aDPTypesWithoutBlank
= aDPTypes
;
392 aDPTypesWithoutBlank
.erase(ScDPItemData::Empty
);
394 const bool isContainsString
= aDPTypesWithoutBlank
.count(ScDPItemData::String
) > 0;
395 const bool isContainsBlank
= aDPTypes
.count(ScDPItemData::Empty
) > 0;
396 const bool isContainsNumber
397 = !isContainsDate
&& aDPTypesWithoutBlank
.count(ScDPItemData::Value
) > 0;
398 bool isContainsNonDate
= !(isContainsDate
&& aDPTypesWithoutBlank
.size() <= 1);
400 // XML_containsSemiMixedTypes possible values:
401 // 1 - (Default) at least one text value, or can also contain a mix of other data types and blank values,
402 // or blank values only
403 // 0 - the field does not have a mix of text and other values
404 if (!(isContainsString
|| (aDPTypes
.size() > 1) || (isContainsBlank
&& aDPTypesWithoutBlank
.empty())))
405 pAttList
->add(XML_containsSemiMixedTypes
, ToPsz10(false));
407 if (!isContainsNonDate
)
408 pAttList
->add(XML_containsNonDate
, ToPsz10(false));
411 pAttList
->add(XML_containsDate
, ToPsz10(true));
413 // default for containsString field is true, so we are writing only when is false
414 if (!isContainsString
)
415 pAttList
->add(XML_containsString
, ToPsz10(false));
418 pAttList
->add(XML_containsBlank
, ToPsz10(true));
420 // XML_containsMixedType possible values:
421 // 1 - field contains more than one data type
422 // 0 - (Default) only one data type. The field can still contain blank values (that's why we are using aDPTypesWithoutBlank)
423 if (aDPTypesWithoutBlank
.size() > 1)
424 pAttList
->add(XML_containsMixedTypes
, ToPsz10(true));
426 // If field contain mixed types (Date and Numbers), MS Excel is saving only "minDate" and "maxDate" and not "minValue" and "maxValue"
427 // Example how Excel is saving mixed Date and Numbers:
428 // <sharedItems containsSemiMixedTypes="0" containsDate="1" containsString="0" containsMixedTypes="1" minDate="1900-01-03T22:26:04" maxDate="1900-01-07T14:02:04" />
429 // Example how Excel is saving Dates only:
430 // <sharedItems containsSemiMixedTypes="0" containsNonDate="0" containsDate="1" containsString="0" minDate="1903-08-24T07:40:48" maxDate="2024-05-23T07:12:00"/>
431 if (isContainsNumber
)
432 pAttList
->add(XML_containsNumber
, ToPsz10(true));
434 if (isValueInteger
&& isContainsNumber
)
435 pAttList
->add(XML_containsInteger
, ToPsz10(true));
438 // Number type fields could be mixed with blank types, and it shouldn't be treated as listed items.
440 // <cacheField name="employeeID" numFmtId="0">
441 // <sharedItems containsString="0" containsBlank="1" containsNumber="1" containsInteger="1" minValue="35" maxValue="89"/>
443 if (isContainsNumber
)
445 pAttList
->add(XML_minValue
, OString::number(fMin
));
446 pAttList
->add(XML_maxValue
, OString::number(fMax
));
451 pAttList
->add(XML_minDate
, GetExcelFormattedDate(fMin
, GetFormatter()).toUtf8());
452 pAttList
->add(XML_maxDate
, GetExcelFormattedDate(fMax
, GetFormatter()).toUtf8());
455 //if (bListItems) // see TODO above
457 pAttList
->add(XML_count
, OString::number(static_cast<tools::Long
>(rFieldItems
.size())));
462 pAttList
->add(XML_longText
, ToPsz10(true));
465 pDefStrm
->startElement(XML_sharedItems
, pAttList
);
467 //if (bListItems) // see TODO above
469 for (const ScDPItemData
& rItem
: rFieldItems
)
471 switch (rItem
.GetType())
473 case ScDPItemData::String
:
474 pDefStrm
->singleElement(XML_s
, XML_v
, rItem
.GetString().toUtf8());
476 case ScDPItemData::Value
:
479 pDefStrm
->singleElement(XML_d
,
480 XML_v
, GetExcelFormattedDate(rItem
.GetValue(), GetFormatter()).toUtf8());
483 pDefStrm
->singleElement(XML_n
,
484 XML_v
, OString::number(rItem
.GetValue()));
486 case ScDPItemData::Empty
:
487 pDefStrm
->singleElement(XML_m
);
489 case ScDPItemData::Error
:
490 pDefStrm
->singleElement(XML_e
,
491 XML_v
, rItem
.GetString().toUtf8());
493 case ScDPItemData::GroupValue
: // Should not happen here!
494 case ScDPItemData::RangeStart
:
495 // TODO : What do we do with these types?
496 pDefStrm
->singleElement(XML_m
);
504 pDefStrm
->endElement(XML_sharedItems
);
506 WriteFieldGroup(i
, i
);
508 pDefStrm
->endElement(XML_cacheField
);
511 ScDPObject
* pDPObject
512 = rCache
.GetAllReferences().empty() ? nullptr : *rCache
.GetAllReferences().begin();
514 for (size_t i
= nCount
; pDPObject
&& i
< nCount
+ nGroupFieldCount
; ++i
)
516 const OUString aName
= pDPObject
->GetDimName(i
, o3tl::temporary(bool()));
517 // tdf#126748: DPObject might not reference all group fields, when there are several
518 // DPObjects referencing this cache. Trying to get a dimension data for a field not used
519 // in a given DPObject will give nullptr, and dereferencing it then will crash. To avoid
520 // the crash, until there's a correct method to find the names of group fields in cache,
521 // just skip the fields, creating bad cache data, which is of course a temporary hack.
522 // TODO: reimplement the whole block to get the names from another source, not from first
527 ScDPSaveData
* pSaveData
= pDPObject
->GetSaveData();
530 const ScDPSaveGroupDimension
* pDim
= pSaveData
->GetDimensionData()->GetNamedGroupDim(aName
);
533 const SCCOL nBase
= rCache
.GetDimensionIndex(pDim
->GetSourceDimName());
536 pDefStrm
->startElement(XML_cacheField
, XML_name
, aName
.toUtf8(), XML_numFmtId
,
537 OString::number(0), XML_databaseField
, ToPsz10(false));
538 WriteFieldGroup(i
, nBase
);
539 pDefStrm
->endElement(XML_cacheField
);
542 pDefStrm
->endElement(XML_cacheFields
);
544 pDefStrm
->endElement(XML_pivotCacheDefinition
);
547 XclExpXmlPivotTableManager::XclExpXmlPivotTableManager( const XclExpRoot
& rRoot
) :
548 XclExpRoot(rRoot
), maCaches(rRoot
) {}
550 void XclExpXmlPivotTableManager::Initialize()
552 ScDocument
& rDoc
= GetDoc();
553 if (!rDoc
.HasPivotTable())
554 // No pivot table to export.
557 ScDPCollection
* pDPColl
= rDoc
.GetDPCollection();
561 // Update caches from DPObject
562 for (size_t i
= 0; i
< pDPColl
->GetCount(); ++i
)
564 ScDPObject
& rDPObj
= (*pDPColl
)[i
];
565 rDPObj
.SyncAllDimensionMembers();
566 (void)rDPObj
.GetOutputRangeByType(sheet::DataPilotOutputRangeType::TABLE
);
569 // Go through the caches first.
571 std::vector
<XclExpXmlPivotCaches::Entry
> aCaches
;
572 const ScDPCollection::SheetCaches
& rSheetCaches
= pDPColl
->GetSheetCaches();
573 const std::vector
<ScRange
>& rRanges
= rSheetCaches
.getAllRanges();
574 for (const auto & rRange
: rRanges
)
576 const ScDPCache
* pCache
= rSheetCaches
.getExistingCache(rRange
);
580 // Get all pivot objects that reference this cache, and set up an
581 // object to cache ID mapping.
582 const ScDPCache::ScDPObjectSet
& rRefs
= pCache
->GetAllReferences();
583 for (const auto& rRef
: rRefs
)
584 maCacheIdMap
.emplace(rRef
, aCaches
.size()+1);
586 XclExpXmlPivotCaches::Entry aEntry
;
587 aEntry
.mpCache
= pCache
;
588 aEntry
.maSrcRange
= rRange
;
589 aCaches
.push_back(aEntry
); // Cache ID equals position + 1.
592 // TODO : Handle name and database caches as well.
594 for (size_t i
= 0, n
= pDPColl
->GetCount(); i
< n
; ++i
)
596 const ScDPObject
& rDPObj
= (*pDPColl
)[i
];
598 // Get the cache ID for this pivot table.
599 CacheIdMapType::iterator itCache
= maCacheIdMap
.find(&rDPObj
);
600 if (itCache
== maCacheIdMap
.end())
601 // No cache ID found. Something is wrong here...
604 sal_Int32 nCacheId
= itCache
->second
;
605 SCTAB nTab
= rDPObj
.GetOutRange().aStart
.Tab();
607 TablesType::iterator it
= m_Tables
.find(nTab
);
608 if (it
== m_Tables
.end())
610 // Insert a new instance for this sheet index.
611 std::pair
<TablesType::iterator
, bool> r
=
612 m_Tables
.insert(std::make_pair(nTab
, std::make_unique
<XclExpXmlPivotTables
>(GetRoot(), maCaches
)));
616 XclExpXmlPivotTables
*const p
= it
->second
.get();
617 p
->AppendTable(&rDPObj
, nCacheId
, i
+1);
620 maCaches
.SetCaches(std::move(aCaches
));
623 XclExpXmlPivotCaches
& XclExpXmlPivotTableManager::GetCaches()
628 XclExpXmlPivotTables
* XclExpXmlPivotTableManager::GetTablesBySheet( SCTAB nTab
)
630 TablesType::iterator
const it
= m_Tables
.find(nTab
);
631 return it
== m_Tables
.end() ? nullptr : it
->second
.get();
634 XclExpXmlPivotTables::Entry::Entry( const ScDPObject
* pTable
, sal_Int32 nCacheId
, sal_Int32 nPivotId
) :
635 mpTable(pTable
), mnCacheId(nCacheId
), mnPivotId(nPivotId
) {}
637 XclExpXmlPivotTables::XclExpXmlPivotTables( const XclExpRoot
& rRoot
, const XclExpXmlPivotCaches
& rCaches
) :
638 XclExpRoot(rRoot
), mrCaches(rCaches
) {}
640 void XclExpXmlPivotTables::SaveXml( XclExpXmlStream
& rStrm
)
642 sax_fastparser::FSHelperPtr
& pWSStrm
= rStrm
.GetCurrentStream(); // worksheet stream
644 for (const auto& rTable
: maTables
)
646 const ScDPObject
& rObj
= *rTable
.mpTable
;
647 sal_Int32 nCacheId
= rTable
.mnCacheId
;
648 sal_Int32 nPivotId
= rTable
.mnPivotId
;
650 sax_fastparser::FSHelperPtr pPivotStrm
= rStrm
.CreateOutputStream(
651 XclXmlUtils::GetStreamName("xl/pivotTables/", "pivotTable", nPivotId
),
652 XclXmlUtils::GetStreamName(nullptr, "../pivotTables/pivotTable", nPivotId
),
653 pWSStrm
->getOutputStream(),
654 CREATE_XL_CONTENT_TYPE("pivotTable"),
655 CREATE_OFFICEDOC_RELATION_TYPE("pivotTable"));
657 rStrm
.PushStream(pPivotStrm
);
658 SavePivotTableXml(rStrm
, rObj
, nCacheId
);
667 tools::Long mnPos
; // field index in pivot cache.
668 const ScDPSaveDimension
* mpDim
;
670 DataField( tools::Long nPos
, const ScDPSaveDimension
* pDim
) : mnPos(nPos
), mpDim(pDim
) {}
673 /** Returns an OOXML subtotal function name string. See ECMA-376-1:2016 18.18.43 */
674 OString
GetSubtotalFuncName(ScGeneralFunction eFunc
)
678 case ScGeneralFunction::SUM
: return "sum"_ostr
;
679 case ScGeneralFunction::COUNT
: return "count"_ostr
;
680 case ScGeneralFunction::AVERAGE
: return "avg"_ostr
;
681 case ScGeneralFunction::MAX
: return "max"_ostr
;
682 case ScGeneralFunction::MIN
: return "min"_ostr
;
683 case ScGeneralFunction::PRODUCT
: return "product"_ostr
;
684 case ScGeneralFunction::COUNTNUMS
: return "countA"_ostr
;
685 case ScGeneralFunction::STDEV
: return "stdDev"_ostr
;
686 case ScGeneralFunction::STDEVP
: return "stdDevP"_ostr
;
687 case ScGeneralFunction::VAR
: return "var"_ostr
;
688 case ScGeneralFunction::VARP
: return "varP"_ostr
;
691 return "default"_ostr
;
694 sal_Int32
GetSubtotalAttrToken(ScGeneralFunction eFunc
)
698 case ScGeneralFunction::SUM
: return XML_sumSubtotal
;
699 case ScGeneralFunction::COUNT
: return XML_countSubtotal
;
700 case ScGeneralFunction::AVERAGE
: return XML_avgSubtotal
;
701 case ScGeneralFunction::MAX
: return XML_maxSubtotal
;
702 case ScGeneralFunction::MIN
: return XML_minSubtotal
;
703 case ScGeneralFunction::PRODUCT
: return XML_productSubtotal
;
704 case ScGeneralFunction::COUNTNUMS
: return XML_countASubtotal
;
705 case ScGeneralFunction::STDEV
: return XML_stdDevSubtotal
;
706 case ScGeneralFunction::STDEVP
: return XML_stdDevPSubtotal
;
707 case ScGeneralFunction::VAR
: return XML_varSubtotal
;
708 case ScGeneralFunction::VARP
: return XML_varPSubtotal
;
711 return XML_defaultSubtotal
;
714 // An item is expected to contain sequences of css::xml::FastAttribute and css::xml::Attribute
715 void WriteGrabBagItemToStream(XclExpXmlStream
& rStrm
, sal_Int32 tokenId
, const css::uno::Any
& rItem
)
717 css::uno::Sequence
<css::uno::Any
> aSeqs
;
718 if(!(rItem
>>= aSeqs
))
721 auto& pStrm
= rStrm
.GetCurrentStream();
722 pStrm
->write("<")->writeId(tokenId
);
724 css::uno::Sequence
<css::xml::FastAttribute
> aFastSeq
;
725 css::uno::Sequence
<css::xml::Attribute
> aUnkSeq
;
726 for (const auto& a
: aSeqs
)
730 for (const auto& rAttr
: aFastSeq
)
731 rStrm
.WriteAttributes(rAttr
.Token
, rAttr
.Value
);
733 else if (a
>>= aUnkSeq
)
735 for (const auto& rAttr
: aUnkSeq
)
739 ->writeEscaped(rAttr
.Value
)
748 void XclExpXmlPivotTables::SavePivotTableXml( XclExpXmlStream
& rStrm
, const ScDPObject
& rDPObj
, sal_Int32 nCacheId
)
750 typedef std::unordered_map
<OUString
, long> NameToIdMapType
;
752 const XclExpXmlPivotCaches::Entry
* pCacheEntry
= mrCaches
.GetCache(nCacheId
);
754 // Something is horribly wrong. Check your logic.
757 const ScDPCache
& rCache
= *pCacheEntry
->mpCache
;
759 const ScDPSaveData
& rSaveData
= *rDPObj
.GetSaveData();
761 size_t nFieldCount
= rCache
.GetFieldCount() + rCache
.GetGroupFieldCount();
762 std::vector
<const ScDPSaveDimension
*> aCachedDims
;
763 NameToIdMapType aNameToIdMap
;
765 aCachedDims
.reserve(nFieldCount
);
766 for (size_t i
= 0; i
< nFieldCount
; ++i
)
768 OUString aName
= const_cast<ScDPObject
&>(rDPObj
).GetDimName(i
, o3tl::temporary(bool()));
769 aNameToIdMap
.emplace(aName
, aCachedDims
.size());
770 const ScDPSaveDimension
* pDim
= rSaveData
.GetExistingDimensionByName(aName
);
771 aCachedDims
.push_back(pDim
);
774 std::vector
<tools::Long
> aRowFields
;
775 std::vector
<tools::Long
> aColFields
;
776 std::vector
<tools::Long
> aPageFields
;
777 std::vector
<DataField
> aDataFields
;
779 tools::Long nDataDimCount
= rSaveData
.GetDataDimensionCount();
780 // Use dimensions in the save data to get their correct ordering.
781 // Dimension order here is significant as they specify the order of
782 // appearance in each axis.
783 const ScDPSaveData::DimsType
& rDims
= rSaveData
.GetDimensions();
784 bool bTabularMode
= false;
785 bool bCompactMode
= true;
786 for (const auto & i
: rDims
)
788 const ScDPSaveDimension
& rDim
= *i
;
790 tools::Long nPos
= -1; // position in cache
791 if (rDim
.IsDataLayout())
792 nPos
= -2; // Excel uses an index of -2 to indicate a data layout field.
795 OUString aSrcName
= ScDPUtil::getSourceDimensionName(rDim
.GetName());
796 NameToIdMapType::iterator it
= aNameToIdMap
.find(aSrcName
);
797 if (it
!= aNameToIdMap
.end())
803 if (!aCachedDims
[nPos
])
807 sheet::DataPilotFieldOrientation eOrient
= rDim
.GetOrientation();
811 case sheet::DataPilotFieldOrientation_COLUMN
:
812 if (nPos
== -2 && nDataDimCount
<= 1)
814 aColFields
.push_back(nPos
);
816 case sheet::DataPilotFieldOrientation_ROW
:
817 aRowFields
.push_back(nPos
);
819 case sheet::DataPilotFieldOrientation_PAGE
:
820 aPageFields
.push_back(nPos
);
822 case sheet::DataPilotFieldOrientation_DATA
:
823 aDataFields
.emplace_back(nPos
, &rDim
);
825 case sheet::DataPilotFieldOrientation_HIDDEN
:
829 if(rDim
.GetLayoutInfo())
831 const auto eLayoutMode
= rDim
.GetLayoutInfo()->LayoutMode
;
832 bTabularMode
|= (eLayoutMode
== sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT
);
833 bCompactMode
&= (eLayoutMode
== sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT
);
837 sax_fastparser::FSHelperPtr
& pPivotStrm
= rStrm
.GetCurrentStream();
838 pPivotStrm
->startElement(XML_pivotTableDefinition
,
839 XML_xmlns
, rStrm
.getNamespaceURL(OOX_NS(xls
)).toUtf8(),
840 XML_name
, rDPObj
.GetName().toUtf8(),
841 XML_cacheId
, OString::number(nCacheId
),
842 XML_applyNumberFormats
, ToPsz10(false),
843 XML_applyBorderFormats
, ToPsz10(false),
844 XML_applyFontFormats
, ToPsz10(false),
845 XML_applyPatternFormats
, ToPsz10(false),
846 XML_applyAlignmentFormats
, ToPsz10(false),
847 XML_applyWidthHeightFormats
, ToPsz10(false),
848 XML_dataCaption
, "Values",
849 XML_showDrill
, ToPsz10(rSaveData
.GetExpandCollapse()),
850 XML_useAutoFormatting
, ToPsz10(false),
851 XML_itemPrintTitles
, ToPsz10(true),
852 XML_indent
, ToPsz10(false),
853 XML_outline
, ToPsz10(!bTabularMode
),
854 XML_outlineData
, ToPsz10(!bTabularMode
),
855 XML_compact
, ToPsz10(bCompactMode
),
856 XML_compactData
, ToPsz10(bCompactMode
));
858 // NB: Excel's range does not include page field area (if any).
859 ScRange aOutRange
= rDPObj
.GetOutputRangeByType(sheet::DataPilotOutputRangeType::TABLE
);
861 sal_Int32 nFirstHeaderRow
= rDPObj
.GetHideHeader() ? 0 : (rDPObj
.GetHeaderLayout() ? 2 : 1);
862 sal_Int32 nFirstDataRow
= rDPObj
.GetHideHeader() ? 1 : 2;
863 sal_Int32 nFirstDataCol
= 1;
864 ScRange aResRange
= rDPObj
.GetOutputRangeByType(sheet::DataPilotOutputRangeType::RESULT
);
866 if (!aOutRange
.IsValid())
867 aOutRange
= rDPObj
.GetOutRange();
869 if (aOutRange
.IsValid() && aResRange
.IsValid())
871 nFirstDataRow
= aResRange
.aStart
.Row() - aOutRange
.aStart
.Row();
872 nFirstDataCol
= aResRange
.aStart
.Col() - aOutRange
.aStart
.Col();
875 pPivotStrm
->write("<")->writeId(XML_location
);
876 rStrm
.WriteAttributes(XML_ref
,
877 XclXmlUtils::ToOString(rStrm
.GetRoot().GetDoc(), aOutRange
),
878 XML_firstHeaderRow
, OUString::number(nFirstHeaderRow
),
879 XML_firstDataRow
, OUString::number(nFirstDataRow
),
880 XML_firstDataCol
, OUString::number(nFirstDataCol
));
882 if (!aPageFields
.empty())
884 rStrm
.WriteAttributes(XML_rowPageCount
, OUString::number(static_cast<tools::Long
>(aPageFields
.size())));
885 rStrm
.WriteAttributes(XML_colPageCount
, OUString::number(1));
888 pPivotStrm
->write("/>");
890 // <pivotFields> - It must contain all fields in the pivot cache even if
891 // only some of them are used in the pivot table. The order must be as
892 // they appear in the cache.
894 pPivotStrm
->startElement(XML_pivotFields
,
895 XML_count
, OString::number(static_cast<tools::Long
>(aCachedDims
.size())));
897 for (size_t i
= 0; i
< nFieldCount
; ++i
)
899 const ScDPSaveDimension
* pDim
= aCachedDims
[i
];
902 pPivotStrm
->singleElement(XML_pivotField
,
903 XML_compact
, ToPsz10(false),
904 XML_showAll
, ToPsz10(false));
908 bool bDimInTabularMode
= false;
909 bool bDimInCompactMode
= false;
910 if(pDim
->GetLayoutInfo())
912 const auto eLayoutMode
= pDim
->GetLayoutInfo()->LayoutMode
;
913 bDimInTabularMode
= (eLayoutMode
== sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT
);
914 bDimInCompactMode
= (eLayoutMode
== sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT
);
917 sheet::DataPilotFieldOrientation eOrient
= pDim
->GetOrientation();
919 if (eOrient
== sheet::DataPilotFieldOrientation_HIDDEN
)
921 if(bDimInTabularMode
)
923 pPivotStrm
->singleElement(XML_pivotField
,
924 XML_compact
, ToPsz10(false),
925 XML_showAll
, ToPsz10(false),
926 XML_outline
, ToPsz10(false));
930 if (bDimInCompactMode
)
931 pPivotStrm
->singleElement(XML_pivotField
,
932 XML_showAll
, ToPsz10(false));
934 pPivotStrm
->singleElement(XML_pivotField
,
935 XML_compact
, ToPsz10(false),
936 XML_showAll
, ToPsz10(false));
941 if (eOrient
== sheet::DataPilotFieldOrientation_DATA
)
943 if(bDimInTabularMode
)
945 pPivotStrm
->singleElement(XML_pivotField
,
946 XML_dataField
, ToPsz10(true),
947 XML_compact
, ToPsz10(false),
948 XML_showAll
, ToPsz10(false),
949 XML_outline
, ToPsz10(false));
953 if (bDimInCompactMode
)
954 pPivotStrm
->singleElement(XML_pivotField
,
955 XML_dataField
, ToPsz10(true),
956 XML_showAll
, ToPsz10(false));
958 pPivotStrm
->singleElement(XML_pivotField
,
959 XML_dataField
, ToPsz10(true),
960 XML_compact
, ToPsz10(false),
961 XML_showAll
, ToPsz10(false));
967 std::vector
<ScDPLabelData::Member
> aMembers
;
969 // We need to get the members in actual order, getting which requires non-const reference here
970 auto& dpo
= const_cast<ScDPObject
&>(rDPObj
);
971 dpo
.GetMembers(i
, dpo
.GetUsedHierarchy(i
), aMembers
);
974 std::vector
<OUString
> aCacheFieldItems
;
975 if (i
< rCache
.GetFieldCount() && !rCache
.GetGroupType(i
))
977 for (const auto& it
: rCache
.GetDimMemberValues(i
))
979 OUString sFormattedName
;
980 if (it
.HasStringData() || it
.IsEmpty())
981 sFormattedName
= it
.GetString();
983 sFormattedName
= const_cast<ScDPObject
&>(rDPObj
).GetFormattedString(
984 pDim
->GetName(), it
.GetValue());
985 aCacheFieldItems
.push_back(sFormattedName
);
990 aCacheFieldItems
= SortGroupItems(rCache
, i
);
992 // The pair contains the member index in cache and if it is hidden
993 std::vector
< std::pair
<size_t, bool> > aMemberSequence
;
994 std::set
<size_t> aUsedCachePositions
;
995 for (const auto & rMember
: aMembers
)
997 auto it
= std::find(aCacheFieldItems
.begin(), aCacheFieldItems
.end(), rMember
.maName
);
998 if (it
!= aCacheFieldItems
.end())
1000 size_t nCachePos
= static_cast<size_t>(std::distance(aCacheFieldItems
.begin(), it
));
1001 auto aInserted
= aUsedCachePositions
.insert(nCachePos
);
1002 if (aInserted
.second
)
1003 aMemberSequence
.emplace_back(std::make_pair(nCachePos
, !rMember
.mbVisible
));
1006 // Now add all remaining cache items as hidden
1007 for (size_t nItem
= 0; nItem
< aCacheFieldItems
.size(); ++nItem
)
1009 if (aUsedCachePositions
.find(nItem
) == aUsedCachePositions
.end())
1010 aMemberSequence
.emplace_back(nItem
, true);
1013 // tdf#125086: check if this field *also* appears in Data region
1014 bool bAppearsInData
= false;
1016 OUString aSrcName
= ScDPUtil::getSourceDimensionName(pDim
->GetName());
1017 const auto it
= std::find_if(
1018 aDataFields
.begin(), aDataFields
.end(), [&aSrcName
](const DataField
& rDataField
) {
1020 = ScDPUtil::getSourceDimensionName(rDataField
.mpDim
->GetName());
1021 return aThisName
== aSrcName
;
1023 if (it
!= aDataFields
.end())
1024 bAppearsInData
= true;
1027 auto pAttList
= sax_fastparser::FastSerializerHelper::createAttrList();
1028 pAttList
->add(XML_axis
, toOOXMLAxisType(eOrient
));
1030 pAttList
->add(XML_dataField
, ToPsz10(true));
1032 if (!bDimInCompactMode
)
1033 pAttList
->add(XML_compact
, ToPsz10(false));
1035 pAttList
->add(XML_showAll
, ToPsz10(false));
1037 tools::Long nSubTotalCount
= pDim
->GetSubTotalsCount();
1038 std::vector
<OString
> aSubtotalSequence
;
1039 bool bHasDefaultSubtotal
= false;
1040 for (tools::Long nSubTotal
= 0; nSubTotal
< nSubTotalCount
; ++nSubTotal
)
1042 ScGeneralFunction eFunc
= pDim
->GetSubTotalFunc(nSubTotal
);
1043 aSubtotalSequence
.push_back(GetSubtotalFuncName(eFunc
));
1044 sal_Int32 nAttToken
= GetSubtotalAttrToken(eFunc
);
1045 if (nAttToken
== XML_defaultSubtotal
)
1046 bHasDefaultSubtotal
= true;
1047 else if (!pAttList
->hasAttribute(nAttToken
))
1048 pAttList
->add(nAttToken
, ToPsz10(true));
1050 // XML_defaultSubtotal is true by default; only write it if it's false
1051 if (!bHasDefaultSubtotal
)
1052 pAttList
->add(XML_defaultSubtotal
, ToPsz10(false));
1054 if(bDimInTabularMode
)
1055 pAttList
->add( XML_outline
, ToPsz10(false));
1056 pPivotStrm
->startElement(XML_pivotField
, pAttList
);
1058 pPivotStrm
->startElement(XML_items
,
1059 XML_count
, OString::number(static_cast<tools::Long
>(aMemberSequence
.size() + aSubtotalSequence
.size())));
1061 for (const auto & nMember
: aMemberSequence
)
1063 auto pItemAttList
= sax_fastparser::FastSerializerHelper::createAttrList();
1065 pItemAttList
->add(XML_h
, ToPsz10(true));
1066 pItemAttList
->add(XML_x
, OString::number(static_cast<tools::Long
>(nMember
.first
)));
1067 pPivotStrm
->singleElement(XML_item
, pItemAttList
);
1070 for (const OString
& sSubtotal
: aSubtotalSequence
)
1072 pPivotStrm
->singleElement(XML_item
, XML_t
, sSubtotal
);
1075 pPivotStrm
->endElement(XML_items
);
1076 pPivotStrm
->endElement(XML_pivotField
);
1079 pPivotStrm
->endElement(XML_pivotFields
);
1083 if (!aRowFields
.empty())
1085 pPivotStrm
->startElement(XML_rowFields
,
1086 XML_count
, OString::number(static_cast<tools::Long
>(aRowFields
.size())));
1088 for (const auto& rRowField
: aRowFields
)
1090 pPivotStrm
->singleElement(XML_field
, XML_x
, OString::number(rRowField
));
1093 pPivotStrm
->endElement(XML_rowFields
);
1100 if (!aColFields
.empty())
1102 pPivotStrm
->startElement(XML_colFields
,
1103 XML_count
, OString::number(static_cast<tools::Long
>(aColFields
.size())));
1105 for (const auto& rColField
: aColFields
)
1107 pPivotStrm
->singleElement(XML_field
, XML_x
, OString::number(rColField
));
1110 pPivotStrm
->endElement(XML_colFields
);
1117 if (!aPageFields
.empty())
1119 pPivotStrm
->startElement(XML_pageFields
,
1120 XML_count
, OString::number(static_cast<tools::Long
>(aPageFields
.size())));
1122 for (const auto& rPageField
: aPageFields
)
1124 pPivotStrm
->singleElement(XML_pageField
,
1125 XML_fld
, OString::number(rPageField
),
1126 XML_hier
, OString::number(-1)); // TODO : handle this correctly.
1129 pPivotStrm
->endElement(XML_pageFields
);
1134 if (!aDataFields
.empty())
1136 css::uno::Reference
<css::container::XNameAccess
> xDimsByName
;
1137 if (auto xDimSupplier
= const_cast<ScDPObject
&>(rDPObj
).GetSource())
1138 xDimsByName
= xDimSupplier
->getDimensions();
1140 pPivotStrm
->startElement(XML_dataFields
,
1141 XML_count
, OString::number(static_cast<tools::Long
>(aDataFields
.size())));
1143 for (const auto& rDataField
: aDataFields
)
1145 tools::Long nDimIdx
= rDataField
.mnPos
;
1146 assert(nDimIdx
== -2 || aCachedDims
[nDimIdx
]); // the loop above should have screened for NULL's, skip check for -2 "data field"
1147 const ScDPSaveDimension
& rDim
= *rDataField
.mpDim
;
1148 std::optional
<OUString
> pName
= rDim
.GetLayoutName();
1149 // tdf#124651: despite being optional in CT_DataField according to ECMA-376 Part 1,
1150 // Excel (at least 2016) seems to insist on the presence of "name" attribute in
1151 // dataField element.
1152 // tdf#124881: try to create a meaningful name; don't use empty string.
1154 pName
= ScDPUtil::getDisplayedMeasureName(
1155 rDim
.GetName(), ScDPUtil::toSubTotalFunc(rDim
.GetFunction()));
1156 auto pItemAttList
= sax_fastparser::FastSerializerHelper::createAttrList();
1157 pItemAttList
->add(XML_name
, pName
->toUtf8());
1158 pItemAttList
->add(XML_fld
, OString::number(nDimIdx
));
1159 const char* pSubtotal
= toOOXMLSubtotalType(rDim
.GetFunction());
1161 pItemAttList
->add(XML_subtotal
, pSubtotal
);
1166 css::uno::Reference
<css::beans::XPropertySet
> xDimProps(
1167 xDimsByName
->getByName(rDim
.GetName()), uno::UNO_QUERY_THROW
);
1168 css::uno::Any aVal
= xDimProps
->getPropertyValue(SC_UNONAME_NUMFMT
);
1169 sal_uInt32 nScNumFmt
= aVal
.get
<sal_uInt32
>();
1170 sal_uInt16 nXclNumFmt
= GetRoot().GetNumFmtBuffer().Insert(nScNumFmt
);
1171 pItemAttList
->add(XML_numFmtId
, OString::number(nXclNumFmt
));
1173 catch (uno::Exception
&)
1175 SAL_WARN("sc.filter",
1176 "Couldn't get number format for data field " << rDim
.GetName());
1177 // Just skip exporting number format
1180 pPivotStrm
->singleElement(XML_dataField
, pItemAttList
);
1183 pPivotStrm
->endElement(XML_dataFields
);
1187 savePivotTableFormats(rStrm
, rDPObj
);
1189 // Now add style info (use grab bag, or just a set which is default on Excel 2007 through 2016)
1190 if (const auto [bHas
, aVal
] = rDPObj
.GetInteropGrabBagValue(u
"pivotTableStyleInfo"_ustr
); bHas
)
1191 WriteGrabBagItemToStream(rStrm
, XML_pivotTableStyleInfo
, aVal
);
1193 pPivotStrm
->singleElement(XML_pivotTableStyleInfo
, XML_name
, "PivotStyleLight16",
1194 XML_showRowHeaders
, "1", XML_showColHeaders
, "1",
1195 XML_showRowStripes
, "0", XML_showColStripes
, "0",
1196 XML_showLastColumn
, "1");
1198 OUString aBuf
= "../pivotCache/pivotCacheDefinition" +
1199 OUString::number(nCacheId
) +
1203 pPivotStrm
->getOutputStream(),
1204 CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheDefinition"),
1207 pPivotStrm
->endElement(XML_pivotTableDefinition
);
1210 void XclExpXmlPivotTables::savePivotTableFormats(XclExpXmlStream
& rStream
, ScDPObject
const& rDPObject
)
1212 sax_fastparser::FSHelperPtr
& pPivotStream
= rStream
.GetCurrentStream();
1214 ScDPSaveData
* pSaveData
= rDPObject
.GetSaveData();
1215 if (pSaveData
&& pSaveData
->hasFormats())
1217 sc::PivotTableFormats
const& rFormats
= pSaveData
->getFormats();
1218 if (rFormats
.size() > 0)
1220 pPivotStream
->startElement(XML_formats
, XML_count
, OString::number(rFormats
.size()));
1222 for (auto const& rFormat
: rFormats
.getVector())
1224 if (!rFormat
.pPattern
)
1227 sal_Int32 nDxf
= GetDxfs().GetDxfIdForPattern(rFormat
.pPattern
.get());
1231 pPivotStream
->startElement(XML_format
, XML_dxfId
, OString::number(nDxf
));
1233 auto pAttributeList
= sax_fastparser::FastSerializerHelper::createAttrList();
1234 if (!rFormat
.bDataOnly
) // default is true
1235 pAttributeList
->add(XML_dataOnly
, "0");
1236 if (rFormat
.bLabelOnly
) // default is false
1237 pAttributeList
->add(XML_labelOnly
, "1");
1238 if (!rFormat
.bOutline
) // default is true
1239 pAttributeList
->add(XML_outline
, "0");
1240 if (rFormat
.oFieldPosition
)
1241 pAttributeList
->add(XML_fieldPosition
, OString::number(*rFormat
.oFieldPosition
));
1242 pPivotStream
->startElement(XML_pivotArea
, pAttributeList
);
1244 if (rFormat
.aSelections
.size())
1246 pPivotStream
->startElement(XML_references
, XML_count
, OString::number(rFormat
.aSelections
.size()));
1247 for (sc::Selection
const& rSelection
: rFormat
.getSelections())
1250 auto pRefAttributeList
= sax_fastparser::FastSerializerHelper::createAttrList();
1251 pRefAttributeList
->add(XML_field
, OString::number(sal_uInt32(rSelection
.nField
)));
1252 pRefAttributeList
->add(XML_count
, "1");
1253 if (!rSelection
.bSelected
) // default is true
1254 pRefAttributeList
->add(XML_selected
, "0");
1255 pPivotStream
->startElement(XML_reference
, pRefAttributeList
);
1258 for (sal_uInt32 nIndex
: rSelection
.nIndices
)
1260 pPivotStream
->singleElement(XML_x
, XML_v
, OString::number(nIndex
));
1262 pPivotStream
->endElement(XML_reference
);
1264 pPivotStream
->endElement(XML_references
);
1266 pPivotStream
->endElement(XML_pivotArea
);
1268 pPivotStream
->endElement(XML_format
);
1270 pPivotStream
->endElement(XML_formats
);
1275 void XclExpXmlPivotTables::AppendTable( const ScDPObject
* pTable
, sal_Int32 nCacheId
, sal_Int32 nPivotId
)
1277 maTables
.emplace_back(pTable
, nCacheId
, nPivotId
);
1280 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */