use insert function instead of for loop
[LibreOffice.git] / sc / source / filter / excel / xepivotxml.cxx
blob90f58aebc574cece1f022d5323f06224ddfaf644
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
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>
16 #include <dpsave.hxx>
17 #include <dputil.hxx>
18 #include <document.hxx>
19 #include <generalfunction.hxx>
20 #include <unonames.hxx>
21 #include <xestyle.hxx>
22 #include <xeroot.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>
41 #include <vector>
43 using namespace oox;
44 using namespace com::sun::star;
46 namespace {
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);
65 assert(pArray);
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 )
80 switch (eOrient)
82 case sheet::DataPilotFieldOrientation_COLUMN:
83 return "axisCol";
84 case sheet::DataPilotFieldOrientation_ROW:
85 return "axisRow";
86 case sheet::DataPilotFieldOrientation_PAGE:
87 return "axisPage";
88 case sheet::DataPilotFieldOrientation_DATA:
89 return "axisValues";
90 case sheet::DataPilotFieldOrientation_HIDDEN:
91 default:
95 return "";
98 const char* toOOXMLSubtotalType(ScGeneralFunction eFunc)
100 switch (eFunc)
102 case ScGeneralFunction::SUM:
103 return "sum";
104 case ScGeneralFunction::COUNT:
105 return "count";
106 case ScGeneralFunction::AVERAGE:
107 return "average";
108 case ScGeneralFunction::MAX:
109 return "max";
110 case ScGeneralFunction::MIN:
111 return "min";
112 case ScGeneralFunction::PRODUCT:
113 return "product";
114 case ScGeneralFunction::COUNTNUMS:
115 return "countNums";
116 case ScGeneralFunction::STDEV:
117 return "stdDev";
118 case ScGeneralFunction::STDEVP:
119 return "stdDevp";
120 case ScGeneralFunction::VAR:
121 return "var";
122 case ScGeneralFunction::VARP:
123 return "varp";
124 default:
127 return nullptr;
132 XclExpXmlPivotCaches::XclExpXmlPivotCaches( const XclExpRoot& rRoot ) :
133 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;
145 OUString aRelId;
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"),
152 &aRelId);
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);
160 rStrm.PopStream();
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
178 if (nCacheId <= 0)
179 // cache ID is 1-based.
180 return nullptr;
182 size_t nPos = nCacheId - 1;
183 if (nPos >= maCaches.size())
184 return nullptr;
186 return &maCaches[nPos];
189 namespace {
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;
206 OUStringBuffer sBuf;
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)
218 struct ItemData
220 sal_Int32 nVal;
221 const ScDPItemData* pData;
223 std::vector<ItemData> aDataToSort;
224 ScfInt32Vec aGIIds;
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;
245 } // namespace
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();
254 OUString aRelId;
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"),
261 &aRelId);
263 rStrm.PushStream(pRecStrm);
264 savePivotCacheRecordsXml(rStrm, rCache);
265 rStrm.PopStream();
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");
276 OUString aSheetName;
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);
291 if (!nDatePart)
292 return;
293 OString sGroupBy;
294 switch (nDatePart)
296 case sheet::DataPilotFieldGroupBy::SECONDS:
297 sGroupBy = "seconds"_ostr;
298 break;
299 case sheet::DataPilotFieldGroupBy::MINUTES:
300 sGroupBy = "minutes"_ostr;
301 break;
302 case sheet::DataPilotFieldGroupBy::HOURS:
303 sGroupBy = "hours"_ostr;
304 break;
305 case sheet::DataPilotFieldGroupBy::DAYS:
306 sGroupBy = "days"_ostr;
307 break;
308 case sheet::DataPilotFieldGroupBy::MONTHS:
309 sGroupBy = "months"_ostr;
310 break;
311 case sheet::DataPilotFieldGroupBy::QUARTERS:
312 sGroupBy = "quarters"_ostr;
313 break;
314 case sheet::DataPilotFieldGroupBy::YEARS:
315 sGroupBy = "years"_ostr;
316 break;
319 // fieldGroup element
320 pDefStrm->startElement(XML_fieldGroup, XML_base, OString::number(base));
322 SvNumberFormatter& rFormatter = GetFormatter();
324 // rangePr element
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());
331 if (pGI->mfStep)
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));
410 if (isContainsDate)
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));
417 if (isContainsBlank)
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.
439 // Example:
440 // <cacheField name="employeeID" numFmtId="0">
441 // <sharedItems containsString="0" containsBlank="1" containsNumber="1" containsInteger="1" minValue="35" maxValue="89"/>
442 // </cacheField>
443 if (isContainsNumber)
445 pAttList->add(XML_minValue, OString::number(fMin));
446 pAttList->add(XML_maxValue, OString::number(fMax));
449 if (isContainsDate)
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())));
460 if (isLongText)
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());
475 break;
476 case ScDPItemData::Value:
477 if (isContainsDate)
479 pDefStrm->singleElement(XML_d,
480 XML_v, GetExcelFormattedDate(rItem.GetValue(), GetFormatter()).toUtf8());
482 else
483 pDefStrm->singleElement(XML_n,
484 XML_v, OString::number(rItem.GetValue()));
485 break;
486 case ScDPItemData::Empty:
487 pDefStrm->singleElement(XML_m);
488 break;
489 case ScDPItemData::Error:
490 pDefStrm->singleElement(XML_e,
491 XML_v, rItem.GetString().toUtf8());
492 break;
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);
497 break;
498 default:
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
523 // cache reference.
524 if (aName.isEmpty())
525 break;
527 ScDPSaveData* pSaveData = pDPObject->GetSaveData();
528 assert(pSaveData);
530 const ScDPSaveGroupDimension* pDim = pSaveData->GetDimensionData()->GetNamedGroupDim(aName);
531 assert(pDim);
533 const SCCOL nBase = rCache.GetDimensionIndex(pDim->GetSourceDimName());
534 assert(nBase >= 0);
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.
555 return;
557 ScDPCollection* pDPColl = rDoc.GetDPCollection();
558 if (!pDPColl)
559 return;
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);
577 if (!pCache)
578 continue;
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...
602 continue;
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)));
613 it = r.first;
616 XclExpXmlPivotTables *const p = it->second.get();
617 p->AppendTable(&rDPObj, nCacheId, i+1);
620 maCaches.SetCaches(std::move(aCaches));
623 XclExpXmlPivotCaches& XclExpXmlPivotTableManager::GetCaches()
625 return maCaches;
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);
659 rStrm.PopStream();
663 namespace {
665 struct DataField
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)
676 switch (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;
689 default:;
691 return "default"_ostr;
694 sal_Int32 GetSubtotalAttrToken(ScGeneralFunction eFunc)
696 switch (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;
709 default:;
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))
719 return;
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)
728 if (a >>= aFastSeq)
730 for (const auto& rAttr : aFastSeq)
731 rStrm.WriteAttributes(rAttr.Token, rAttr.Value);
733 else if (a >>= aUnkSeq)
735 for (const auto& rAttr : aUnkSeq)
736 pStrm->write(" ")
737 ->write(rAttr.Name)
738 ->write("=\"")
739 ->writeEscaped(rAttr.Value)
740 ->write("\"");
744 pStrm->write("/>");
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);
753 if (!pCacheEntry)
754 // Something is horribly wrong. Check your logic.
755 return;
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.
793 else
795 OUString aSrcName = ScDPUtil::getSourceDimensionName(rDim.GetName());
796 NameToIdMapType::iterator it = aNameToIdMap.find(aSrcName);
797 if (it != aNameToIdMap.end())
798 nPos = it->second;
800 if (nPos == -1)
801 continue;
803 if (!aCachedDims[nPos])
804 continue;
807 sheet::DataPilotFieldOrientation eOrient = rDim.GetOrientation();
809 switch (eOrient)
811 case sheet::DataPilotFieldOrientation_COLUMN:
812 if (nPos == -2 && nDataDimCount <= 1)
813 break;
814 aColFields.push_back(nPos);
815 break;
816 case sheet::DataPilotFieldOrientation_ROW:
817 aRowFields.push_back(nPos);
818 break;
819 case sheet::DataPilotFieldOrientation_PAGE:
820 aPageFields.push_back(nPos);
821 break;
822 case sheet::DataPilotFieldOrientation_DATA:
823 aDataFields.emplace_back(nPos, &rDim);
824 break;
825 case sheet::DataPilotFieldOrientation_HIDDEN:
826 default:
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];
900 if (!pDim)
902 pPivotStrm->singleElement(XML_pivotField,
903 XML_compact, ToPsz10(false),
904 XML_showAll, ToPsz10(false));
905 continue;
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));
928 else
930 if (bDimInCompactMode)
931 pPivotStrm->singleElement(XML_pivotField,
932 XML_showAll, ToPsz10(false));
933 else
934 pPivotStrm->singleElement(XML_pivotField,
935 XML_compact, ToPsz10(false),
936 XML_showAll, ToPsz10(false));
938 continue;
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));
951 else
953 if (bDimInCompactMode)
954 pPivotStrm->singleElement(XML_pivotField,
955 XML_dataField, ToPsz10(true),
956 XML_showAll, ToPsz10(false));
957 else
958 pPivotStrm->singleElement(XML_pivotField,
959 XML_dataField, ToPsz10(true),
960 XML_compact, ToPsz10(false),
961 XML_showAll, ToPsz10(false));
963 continue;
966 // Dump field items.
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();
982 else
983 sFormattedName = const_cast<ScDPObject&>(rDPObj).GetFormattedString(
984 pDim->GetName(), it.GetValue());
985 aCacheFieldItems.push_back(sFormattedName);
988 else
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) {
1019 OUString aThisName
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));
1029 if (bAppearsInData)
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();
1064 if (nMember.second)
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);
1081 // <rowFields>
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);
1096 // <rowItems>
1098 // <colFields>
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);
1113 // <colItems>
1115 // <pageFields>
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);
1132 // <dataFields>
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.
1153 if (!pName)
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());
1160 if (pSubtotal)
1161 pItemAttList->add(XML_subtotal, pSubtotal);
1162 if (xDimsByName)
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);
1186 // <formats>
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);
1192 else
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) +
1200 ".xml";
1202 rStrm.addRelation(
1203 pPivotStrm->getOutputStream(),
1204 CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheDefinition"),
1205 aBuf);
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)
1225 continue;
1227 sal_Int32 nDxf = GetDxfs().GetDxfIdForPattern(rFormat.pPattern.get());
1228 if (nDxf == -1)
1229 continue;
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: */