/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace oox; using namespace com::sun::star; namespace { void savePivotCacheRecordsXml( XclExpXmlStream& rStrm, const ScDPCache& rCache ) { SCROW nCount = rCache.GetDataSize(); size_t nFieldCount = rCache.GetFieldCount(); sax_fastparser::FSHelperPtr& pRecStrm = rStrm.GetCurrentStream(); pRecStrm->startElement(XML_pivotCacheRecords, XML_xmlns, XclXmlUtils::ToOString(rStrm.getNamespaceURL(OOX_NS(xls))).getStr(), FSNS(XML_xmlns, XML_r), XclXmlUtils::ToOString(rStrm.getNamespaceURL(OOX_NS(officeRel))).getStr(), XML_count, OString::number(static_cast(nCount)).getStr(), FSEND); for (SCROW i = 0; i < nCount; ++i) { pRecStrm->startElement(XML_r, FSEND); for (size_t nField = 0; nField < nFieldCount; ++nField) { const ScDPCache::IndexArrayType* pArray = rCache.GetFieldIndexArray(nField); assert(pArray); assert(static_cast(i) < pArray->size()); // We are using XML_x reference (like: ), instead of values here (eg: ). // That's why in SavePivotCacheXml method, we need to list all items. pRecStrm->singleElement(XML_x, XML_v, OString::number((*pArray)[i]), FSEND); } pRecStrm->endElement(XML_r); } pRecStrm->endElement(XML_pivotCacheRecords); } const char* toOOXMLAxisType( sheet::DataPilotFieldOrientation eOrient ) { switch (eOrient) { case sheet::DataPilotFieldOrientation_COLUMN: return "axisCol"; case sheet::DataPilotFieldOrientation_ROW: return "axisRow"; case sheet::DataPilotFieldOrientation_PAGE: return "axisPage"; case sheet::DataPilotFieldOrientation_DATA: return "axisValues"; case sheet::DataPilotFieldOrientation_HIDDEN: default: ; } return ""; } const char* toOOXMLSubtotalType(ScGeneralFunction eFunc) { switch (eFunc) { case ScGeneralFunction::SUM: return "sum"; case ScGeneralFunction::COUNT: return "count"; case ScGeneralFunction::AVERAGE: return "average"; case ScGeneralFunction::MAX: return "max"; case ScGeneralFunction::MIN: return "min"; case ScGeneralFunction::PRODUCT: return "product"; case ScGeneralFunction::COUNTNUMS: return "countNums"; case ScGeneralFunction::STDEV: return "stdDev"; case ScGeneralFunction::STDEVP: return "stdDevp"; case ScGeneralFunction::VAR: return "var"; case ScGeneralFunction::VARP: return "varp"; default: ; } return nullptr; } } XclExpXmlPivotCaches::XclExpXmlPivotCaches( const XclExpRoot& rRoot ) : XclExpRoot(rRoot) {} void XclExpXmlPivotCaches::SaveXml( XclExpXmlStream& rStrm ) { sax_fastparser::FSHelperPtr& pWorkbookStrm = rStrm.GetCurrentStream(); pWorkbookStrm->startElement(XML_pivotCaches, FSEND); for (size_t i = 0, n = maCaches.size(); i < n; ++i) { const Entry& rEntry = maCaches[i]; sal_Int32 nCacheId = i + 1; OUString aRelId; sax_fastparser::FSHelperPtr pPCStrm = rStrm.CreateOutputStream( XclXmlUtils::GetStreamName("xl/pivotCache/", "pivotCacheDefinition", nCacheId), XclXmlUtils::GetStreamName(nullptr, "pivotCache/pivotCacheDefinition", nCacheId), rStrm.GetCurrentStream()->getOutputStream(), CREATE_XL_CONTENT_TYPE("pivotCacheDefinition"), CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheDefinition"), &aRelId); pWorkbookStrm->singleElement(XML_pivotCache, XML_cacheId, OString::number(nCacheId).getStr(), FSNS(XML_r, XML_id), XclXmlUtils::ToOString(aRelId).getStr(), FSEND); rStrm.PushStream(pPCStrm); SavePivotCacheXml(rStrm, rEntry, nCacheId); rStrm.PopStream(); } pWorkbookStrm->endElement(XML_pivotCaches); } void XclExpXmlPivotCaches::SetCaches( const std::vector& rCaches ) { maCaches = rCaches; } bool XclExpXmlPivotCaches::HasCaches() const { return !maCaches.empty(); } const XclExpXmlPivotCaches::Entry* XclExpXmlPivotCaches::GetCache( sal_Int32 nCacheId ) const { if (nCacheId <= 0) // cache ID is 1-based. return nullptr; size_t nPos = nCacheId - 1; if (nPos >= maCaches.size()) return nullptr; return &maCaches[nPos]; } namespace { /** * Create combined date and time string according the requirements of Excel. * A single point in time can be represented by concatenating a complete date expression, * the letter T as a delimiter, and a valid time expression. For example, "2007-04-05T14:30". * * fSerialDateTime - a number representing the number of days since 1900-Jan-0 (integer portion of the number), * plus a fractional portion of a 24 hour day (fractional portion of the number). */ OUString GetExcelFormattedDate( double fSerialDateTime, const SvNumberFormatter& rFormatter ) { //::sax::Converter::convertDateTime(sBuf, (DateTime(rFormatter.GetNullDate()) + fSerialDateTime).GetUNODateTime(), 0, true); css::util::DateTime aUDateTime = (DateTime(rFormatter.GetNullDate()) + fSerialDateTime).GetUNODateTime(); // We need to reset nanoseconds, to avoid string like: "1982-02-18T16:04:47.999999849" aUDateTime.NanoSeconds = 0; OUStringBuffer sBuf; ::sax::Converter::convertDateTime(sBuf, aUDateTime, nullptr, true); return sBuf.makeStringAndClear(); } } void XclExpXmlPivotCaches::SavePivotCacheXml( XclExpXmlStream& rStrm, const Entry& rEntry, sal_Int32 nCounter ) { assert(rEntry.mpCache); const ScDPCache& rCache = *rEntry.mpCache; sax_fastparser::FSHelperPtr& pDefStrm = rStrm.GetCurrentStream(); OUString aRelId; sax_fastparser::FSHelperPtr pRecStrm = rStrm.CreateOutputStream( XclXmlUtils::GetStreamName("xl/pivotCache/", "pivotCacheRecords", nCounter), XclXmlUtils::GetStreamName(nullptr, "pivotCacheRecords", nCounter), pDefStrm->getOutputStream(), CREATE_XL_CONTENT_TYPE("pivotCacheRecords"), CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheRecords"), &aRelId); rStrm.PushStream(pRecStrm); savePivotCacheRecordsXml(rStrm, rCache); rStrm.PopStream(); pDefStrm->startElement(XML_pivotCacheDefinition, XML_xmlns, XclXmlUtils::ToOString(rStrm.getNamespaceURL(OOX_NS(xls))).getStr(), FSNS(XML_xmlns, XML_r), XclXmlUtils::ToOString(rStrm.getNamespaceURL(OOX_NS(officeRel))).getStr(), FSNS(XML_r, XML_id), XclXmlUtils::ToOString(aRelId).getStr(), XML_recordCount, OString::number(rEntry.mpCache->GetDataSize()).getStr(), XML_createdVersion, "3", // MS Excel 2007, tdf#112936: setting version number makes MSO to handle the pivot table differently FSEND); if (rEntry.meType == Worksheet) { pDefStrm->startElement(XML_cacheSource, XML_type, "worksheet", FSEND); OUString aSheetName; GetDoc().GetName(rEntry.maSrcRange.aStart.Tab(), aSheetName); pDefStrm->singleElement(XML_worksheetSource, XML_ref, XclXmlUtils::ToOString(rEntry.maSrcRange).getStr(), XML_sheet, XclXmlUtils::ToOString(aSheetName).getStr(), FSEND); pDefStrm->endElement(XML_cacheSource); } size_t nCount = rCache.GetFieldCount(); pDefStrm->startElement(XML_cacheFields, XML_count, OString::number(static_cast(nCount)).getStr(), FSEND); for (size_t i = 0; i < nCount; ++i) { OUString aName = rCache.GetDimensionName(i); pDefStrm->startElement(XML_cacheField, XML_name, XclXmlUtils::ToOString(aName).getStr(), XML_numFmtId, OString::number(0).getStr(), FSEND); const ScDPCache::ScDPItemDataVec& rFieldItems = rCache.GetDimMemberValues(i); ScDPCache::ScDPItemDataVec::const_iterator it = rFieldItems.begin(), itEnd = rFieldItems.end(); std::set aDPTypes; double fMin = std::numeric_limits::infinity(), fMax = -std::numeric_limits::infinity(); bool isValueInteger = true; bool isContainsDate = rCache.IsDateDimension(i); for (; it != itEnd; ++it) { ScDPItemData::Type eType = it->GetType(); aDPTypes.insert(eType); if (eType == ScDPItemData::Value) { double fVal = it->GetValue(); fMin = std::min(fMin, fVal); fMax = std::max(fMax, fVal); // Check if all values are integers if (isValueInteger && (modf(fVal, &o3tl::temporary(double())) != 0.0)) { isValueInteger = false; } } } auto aDPTypeEnd = aDPTypes.cend(); auto pAttList = sax_fastparser::FastSerializerHelper::createAttrList(); // TODO In same cases, disable listing of items, as it is done in MS Excel. // Exporting savePivotCacheRecordsXml method needs to be updated accordingly bool bListItems = true; std::set aDPTypesWithoutBlank = aDPTypes; aDPTypesWithoutBlank.erase(ScDPItemData::Empty); bool isContainsString = aDPTypesWithoutBlank.find(ScDPItemData::String) != aDPTypesWithoutBlank.end() || aDPTypesWithoutBlank.find(ScDPItemData::Error) != aDPTypesWithoutBlank.end(); bool isContainsBlank = aDPTypes.find(ScDPItemData::Empty) != aDPTypeEnd; bool isContainsNumber = !isContainsDate && aDPTypesWithoutBlank.find(ScDPItemData::Value) != aDPTypesWithoutBlank.end(); bool isContainsNonDate = !(isContainsDate && aDPTypesWithoutBlank.size() <= 1); // XML_containsSemiMixedTypes possible values: // 1 - (Default) at least one text value, or can also contain a mix of other data types and blank values, // or blank values only // 0 - the field does not have a mix of text and other values if (!(isContainsString || (aDPTypes.size() > 1) || (isContainsBlank && aDPTypesWithoutBlank.size() == 0))) pAttList->add(XML_containsSemiMixedTypes, ToPsz10(false)); if (!isContainsNonDate) pAttList->add(XML_containsNonDate, ToPsz10(false)); if (isContainsDate) pAttList->add(XML_containsDate, ToPsz10(true)); // default for containsString field is true, so we are writing only when is false if (!isContainsString) pAttList->add(XML_containsString, ToPsz10(false)); if (isContainsBlank) pAttList->add(XML_containsBlank, ToPsz10(true)); // XML_containsMixedType possible values: // 1 - field contains more than one data type // 0 - (Default) only one data type. The field can still contain blank values (that's why we are using aDPTypesWithoutBlank) if (aDPTypesWithoutBlank.size() > 1) pAttList->add(XML_containsMixedTypes, ToPsz10(true)); // If field contain mixed types (Date and Numbers), MS Excel is saving only "minDate" and "maxDate" and not "minValue" and "maxValue" // Example how Excel is saving mixed Date and Numbers: // // Example how Excel is saving Dates only: // if (isContainsNumber) pAttList->add(XML_containsNumber, ToPsz10(true)); if (isValueInteger && isContainsNumber) pAttList->add(XML_containsInteger, ToPsz10(true)); // Number type fields could be mixed with blank types, and it shouldn't be treated as listed items. // Example: // // // if (isContainsNumber) { pAttList->add(XML_minValue, OString::number(fMin)); pAttList->add(XML_maxValue, OString::number(fMax)); } if (isContainsDate) { pAttList->add(XML_minDate, XclXmlUtils::ToOString(GetExcelFormattedDate(fMin, GetFormatter()))); pAttList->add(XML_maxDate, XclXmlUtils::ToOString(GetExcelFormattedDate(fMax, GetFormatter()))); } if (bListItems) { pAttList->add(XML_count, OString::number(static_cast(rFieldItems.size()))); } sax_fastparser::XFastAttributeListRef xAttributeList(pAttList); pDefStrm->startElement(XML_sharedItems, xAttributeList); if (bListItems) { it = rFieldItems.begin(); for (; it != itEnd; ++it) { const ScDPItemData& rItem = *it; switch (rItem.GetType()) { case ScDPItemData::String: pDefStrm->singleElement(XML_s, XML_v, XclXmlUtils::ToOString(rItem.GetString()), FSEND); break; case ScDPItemData::Value: if (isContainsDate) { pDefStrm->singleElement(XML_d, XML_v, XclXmlUtils::ToOString(GetExcelFormattedDate(rItem.GetValue(), GetFormatter())), FSEND); } else pDefStrm->singleElement(XML_n, XML_v, OString::number(rItem.GetValue()), FSEND); break; case ScDPItemData::Empty: pDefStrm->singleElement(XML_m, FSEND); break; case ScDPItemData::Error: pDefStrm->singleElement(XML_e, XML_v, XclXmlUtils::ToOString(rItem.GetString()), FSEND); break; case ScDPItemData::GroupValue: case ScDPItemData::RangeStart: // TODO : What do we do with these types? pDefStrm->singleElement(XML_m, FSEND); break; default: ; } } } pDefStrm->endElement(XML_sharedItems); pDefStrm->endElement(XML_cacheField); } pDefStrm->endElement(XML_cacheFields); pDefStrm->endElement(XML_pivotCacheDefinition); } XclExpXmlPivotTableManager::XclExpXmlPivotTableManager( const XclExpRoot& rRoot ) : XclExpRoot(rRoot), maCaches(rRoot) {} void XclExpXmlPivotTableManager::Initialize() { const ScDocument& rDoc = GetDoc(); if (!rDoc.HasPivotTable()) // No pivot table to export. return; const ScDPCollection* pDPColl = rDoc.GetDPCollection(); if (!pDPColl) return; // Go through the caches first. std::vector aCaches; const ScDPCollection::SheetCaches& rSheetCaches = pDPColl->GetSheetCaches(); const std::vector& rRanges = rSheetCaches.getAllRanges(); for (const auto & rRange : rRanges) { const ScDPCache* pCache = rSheetCaches.getExistingCache(rRange); if (!pCache) continue; // Get all pivot objects that reference this cache, and set up an // object to cache ID mapping. const ScDPCache::ScDPObjectSet& rRefs = pCache->GetAllReferences(); ScDPCache::ScDPObjectSet::const_iterator it = rRefs.begin(), itEnd = rRefs.end(); for (; it != itEnd; ++it) maCacheIdMap.emplace(*it, aCaches.size()+1); XclExpXmlPivotCaches::Entry aEntry; aEntry.meType = XclExpXmlPivotCaches::Worksheet; aEntry.mpCache = pCache; aEntry.maSrcRange = rRange; aCaches.push_back(aEntry); // Cache ID equals position + 1. } // TODO : Handle name and database caches as well. for (size_t i = 0, n = pDPColl->GetCount(); i < n; ++i) { const ScDPObject& rDPObj = (*pDPColl)[i]; // Get the cache ID for this pivot table. CacheIdMapType::iterator itCache = maCacheIdMap.find(&rDPObj); if (itCache == maCacheIdMap.end()) // No cache ID found. Something is wrong here.... continue; sal_Int32 nCacheId = itCache->second; SCTAB nTab = rDPObj.GetOutRange().aStart.Tab(); TablesType::iterator it = m_Tables.find(nTab); if (it == m_Tables.end()) { // Insert a new instance for this sheet index. std::pair r = m_Tables.insert(std::make_pair(nTab, o3tl::make_unique(GetRoot(), maCaches))); it = r.first; } XclExpXmlPivotTables *const p = it->second.get(); p->AppendTable(&rDPObj, nCacheId, i+1); } maCaches.SetCaches(aCaches); } XclExpXmlPivotCaches& XclExpXmlPivotTableManager::GetCaches() { return maCaches; } XclExpXmlPivotTables* XclExpXmlPivotTableManager::GetTablesBySheet( SCTAB nTab ) { TablesType::iterator const it = m_Tables.find(nTab); return it == m_Tables.end() ? nullptr : it->second.get(); } XclExpXmlPivotTables::Entry::Entry( const ScDPObject* pTable, sal_Int32 nCacheId, sal_Int32 nPivotId ) : mpTable(pTable), mnCacheId(nCacheId), mnPivotId(nPivotId) {} XclExpXmlPivotTables::XclExpXmlPivotTables( const XclExpRoot& rRoot, const XclExpXmlPivotCaches& rCaches ) : XclExpRoot(rRoot), mrCaches(rCaches) {} void XclExpXmlPivotTables::SaveXml( XclExpXmlStream& rStrm ) { sax_fastparser::FSHelperPtr& pWSStrm = rStrm.GetCurrentStream(); // worksheet stream sal_Int32 nCounter = 1; // 1-based TablesType::iterator it = maTables.begin(), itEnd = maTables.end(); for (; it != itEnd; ++it, ++nCounter) { const ScDPObject& rObj = *it->mpTable; sal_Int32 nCacheId = it->mnCacheId; sal_Int32 nPivotId = it->mnPivotId; sax_fastparser::FSHelperPtr pPivotStrm = rStrm.CreateOutputStream( XclXmlUtils::GetStreamName("xl/pivotTables/", "pivotTable", nPivotId), XclXmlUtils::GetStreamName(nullptr, "../pivotTables/pivotTable", nPivotId), pWSStrm->getOutputStream(), CREATE_XL_CONTENT_TYPE("pivotTable"), CREATE_OFFICEDOC_RELATION_TYPE("pivotTable")); rStrm.PushStream(pPivotStrm); SavePivotTableXml(rStrm, rObj, nCacheId); rStrm.PopStream(); } } namespace { struct DataField { long mnPos; // field index in pivot cache. const ScDPSaveDimension* mpDim; DataField( long nPos, const ScDPSaveDimension* pDim ) : mnPos(nPos), mpDim(pDim) {} }; /** Returns a OOXML subtotal function name string. See ECMA-376-1:2016 18.18.43 */ OString GetSubtotalFuncName(ScGeneralFunction eFunc) { switch (eFunc) { case ScGeneralFunction::SUM: return "sum"; case ScGeneralFunction::COUNT: return "count"; case ScGeneralFunction::AVERAGE: return "avg"; case ScGeneralFunction::MAX: return "max"; case ScGeneralFunction::MIN: return "min"; case ScGeneralFunction::PRODUCT: return "product"; case ScGeneralFunction::COUNTNUMS: return "countA"; case ScGeneralFunction::STDEV: return "stdDev"; case ScGeneralFunction::STDEVP: return "stdDevP"; case ScGeneralFunction::VAR: return "var"; case ScGeneralFunction::VARP: return "varP"; default:; } return "default"; } sal_Int32 GetSubtotalAttrToken(ScGeneralFunction eFunc) { switch (eFunc) { case ScGeneralFunction::SUM: return XML_sumSubtotal; case ScGeneralFunction::COUNT: return XML_countSubtotal; case ScGeneralFunction::AVERAGE: return XML_avgSubtotal; case ScGeneralFunction::MAX: return XML_maxSubtotal; case ScGeneralFunction::MIN: return XML_minSubtotal; case ScGeneralFunction::PRODUCT: return XML_productSubtotal; case ScGeneralFunction::COUNTNUMS: return XML_countASubtotal; case ScGeneralFunction::STDEV: return XML_stdDevSubtotal; case ScGeneralFunction::STDEVP: return XML_stdDevPSubtotal; case ScGeneralFunction::VAR: return XML_varSubtotal; case ScGeneralFunction::VARP: return XML_varPSubtotal; default:; } return XML_defaultSubtotal; } } void XclExpXmlPivotTables::SavePivotTableXml( XclExpXmlStream& rStrm, const ScDPObject& rDPObj, sal_Int32 nCacheId ) { typedef std::unordered_map NameToIdMapType; const XclExpXmlPivotCaches::Entry* pCacheEntry = mrCaches.GetCache(nCacheId); if (!pCacheEntry) // Something is horribly wrong. Check your logic. return; const ScDPCache& rCache = *pCacheEntry->mpCache; const ScDPSaveData& rSaveData = *rDPObj.GetSaveData(); size_t nFieldCount = rCache.GetFieldCount(); std::vector aCachedDims; NameToIdMapType aNameToIdMap; aCachedDims.reserve(nFieldCount); for (size_t i = 0; i < nFieldCount; ++i) { OUString aName = rCache.GetDimensionName(i); aNameToIdMap.emplace(aName, aCachedDims.size()); const ScDPSaveDimension* pDim = rSaveData.GetExistingDimensionByName(aName); aCachedDims.push_back(pDim); } std::vector aRowFields; std::vector aColFields; std::vector aPageFields; std::vector aDataFields; // Use dimensions in the save data to get their correct ordering. // Dimension order here is significant as they specify the order of // appearance in each axis. const ScDPSaveData::DimsType& rDims = rSaveData.GetDimensions(); bool bTabularMode = false; for (const auto & i : rDims) { const ScDPSaveDimension& rDim = *i; long nPos = -1; // position in cache if (rDim.IsDataLayout()) nPos = -2; // Excel uses an index of -2 to indicate a data layout field. else { OUString aSrcName = ScDPUtil::getSourceDimensionName(rDim.GetName()); NameToIdMapType::iterator it = aNameToIdMap.find(aSrcName); if (it != aNameToIdMap.end()) nPos = it->second; if (nPos == -1) continue; if (!aCachedDims[nPos]) continue; } sheet::DataPilotFieldOrientation eOrient = rDim.GetOrientation(); switch (eOrient) { case sheet::DataPilotFieldOrientation_COLUMN: aColFields.push_back(nPos); break; case sheet::DataPilotFieldOrientation_ROW: aRowFields.push_back(nPos); break; case sheet::DataPilotFieldOrientation_PAGE: aPageFields.push_back(nPos); break; case sheet::DataPilotFieldOrientation_DATA: aDataFields.emplace_back(nPos, &rDim); break; case sheet::DataPilotFieldOrientation_HIDDEN: default: ; } if(rDim.GetLayoutInfo()) bTabularMode |= (rDim.GetLayoutInfo()->LayoutMode == sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT); } sax_fastparser::FSHelperPtr& pPivotStrm = rStrm.GetCurrentStream(); pPivotStrm->startElement(XML_pivotTableDefinition, XML_xmlns, XclXmlUtils::ToOString(rStrm.getNamespaceURL(OOX_NS(xls))).getStr(), XML_name, XclXmlUtils::ToOString(rDPObj.GetName()).getStr(), XML_cacheId, OString::number(nCacheId).getStr(), XML_applyNumberFormats, ToPsz10(false), XML_applyBorderFormats, ToPsz10(false), XML_applyFontFormats, ToPsz10(false), XML_applyPatternFormats, ToPsz10(false), XML_applyAlignmentFormats, ToPsz10(false), XML_applyWidthHeightFormats, ToPsz10(false), XML_dataCaption, "Values", XML_useAutoFormatting, ToPsz10(false), XML_itemPrintTitles, ToPsz10(true), XML_indent, ToPsz10(false), XML_outline, ToPsz10(!bTabularMode), XML_outlineData, ToPsz10(!bTabularMode), XML_compact, ToPsz10(false), XML_compactData, ToPsz10(false), FSEND); // NB: Excel's range does not include page field area (if any). ScRange aOutRange = rDPObj.GetOutputRangeByType(sheet::DataPilotOutputRangeType::TABLE); sal_Int32 nFirstHeaderRow = rDPObj.GetHeaderLayout() ? 2 : 1; sal_Int32 nFirstDataRow = 2; sal_Int32 nFirstDataCol = 1; ScRange aResRange = rDPObj.GetOutputRangeByType(sheet::DataPilotOutputRangeType::RESULT); if (aOutRange.IsValid() && aResRange.IsValid()) { nFirstDataRow = aResRange.aStart.Row() - aOutRange.aStart.Row(); nFirstDataCol = aResRange.aStart.Col() - aOutRange.aStart.Col(); } if (!aOutRange.IsValid()) aOutRange = rDPObj.GetOutRange(); pPivotStrm->write("<")->writeId(XML_location); rStrm.WriteAttributes(XML_ref, XclXmlUtils::ToOString(aOutRange), XML_firstHeaderRow, OString::number(nFirstHeaderRow).getStr(), XML_firstDataRow, OString::number(nFirstDataRow).getStr(), XML_firstDataCol, OString::number(nFirstDataCol).getStr(), FSEND); if (!aPageFields.empty()) { rStrm.WriteAttributes(XML_rowPageCount, OString::number(static_cast(aPageFields.size())).getStr(), FSEND); rStrm.WriteAttributes(XML_colPageCount, OString::number(1).getStr(), FSEND); } pPivotStrm->write("/>"); // - It must contain all fields in the pivot cache even if // only some of them are used in the pivot table. The order must be as // they appear in the cache. pPivotStrm->startElement(XML_pivotFields, XML_count, OString::number(static_cast(aCachedDims.size())).getStr(), FSEND); for (size_t i = 0; i < nFieldCount; ++i) { const ScDPSaveDimension* pDim = aCachedDims[i]; if (!pDim) { pPivotStrm->singleElement(XML_pivotField, XML_showAll, ToPsz10(false), XML_compact, ToPsz10(false), FSEND); continue; } bool bDimInTabularMode = false; if(pDim->GetLayoutInfo()) bDimInTabularMode = (pDim->GetLayoutInfo()->LayoutMode == sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT); sheet::DataPilotFieldOrientation eOrient = pDim->GetOrientation(); if (eOrient == sheet::DataPilotFieldOrientation_HIDDEN) { if(bDimInTabularMode) { pPivotStrm->singleElement(XML_pivotField, XML_showAll, ToPsz10(false), XML_compact, ToPsz10(false), XML_outline, ToPsz10(false), FSEND); } else { pPivotStrm->singleElement(XML_pivotField, XML_showAll, ToPsz10(false), XML_compact, ToPsz10(false), FSEND); } continue; } if (eOrient == sheet::DataPilotFieldOrientation_DATA) { if(bDimInTabularMode) { pPivotStrm->singleElement(XML_pivotField, XML_dataField, ToPsz10(true), XML_showAll, ToPsz10(false), XML_compact, ToPsz10(false), XML_outline, ToPsz10(false), FSEND); } else { pPivotStrm->singleElement(XML_pivotField, XML_dataField, ToPsz10(true), XML_showAll, ToPsz10(false), XML_compact, ToPsz10(false), FSEND); } continue; } // Dump field items. std::vector aMembers; { // We need to get the members in actual order, getting which requires non-const reference here auto& dpo = const_cast(rDPObj); dpo.GetMembers(i, dpo.GetUsedHierarchy(i), aMembers); } const ScDPCache::ScDPItemDataVec& rCacheFieldItems = rCache.GetDimMemberValues(i); const auto iCacheFieldItems_begin = rCacheFieldItems.begin(), iCacheFieldItems_end = rCacheFieldItems.end(); // The pair contains the member index in cache and if it is hidden std::vector< std::pair > aMemberSequence; std::set aUsedCachePositions; for (const auto & rMember : aMembers) { for (auto it = iCacheFieldItems_begin; it != iCacheFieldItems_end; ++it) { OUString sFormattedName; if (it->HasStringData() || it->IsEmpty()) { sFormattedName = it->GetString(); } else { sFormattedName = const_cast(rDPObj).GetFormattedString(pDim->GetName(), it->GetValue()); } if (sFormattedName == rMember.maName) { size_t nCachePos = it - iCacheFieldItems_begin; auto aInserted = aUsedCachePositions.insert(nCachePos); if (aInserted.second) aMemberSequence.emplace_back(std::make_pair(nCachePos, !rMember.mbVisible)); break; } } } // Now add all remaining cache items as hidden for (size_t nItem = 0; nItem < rCacheFieldItems.size(); ++nItem) { if (aUsedCachePositions.find(nItem) == aUsedCachePositions.end()) aMemberSequence.emplace_back(nItem, true); } auto pAttList = sax_fastparser::FastSerializerHelper::createAttrList(); pAttList->add(XML_axis, toOOXMLAxisType(eOrient)); pAttList->add(XML_showAll, ToPsz10(false)); long nSubTotalCount = pDim->GetSubTotalsCount(); std::vector aSubtotalSequence; bool bHasDefaultSubtotal = false; for (long nSubTotal = 0; nSubTotal < nSubTotalCount; ++nSubTotal) { ScGeneralFunction eFunc = pDim->GetSubTotalFunc(nSubTotal); aSubtotalSequence.push_back(GetSubtotalFuncName(eFunc)); sal_Int32 nAttToken = GetSubtotalAttrToken(eFunc); if (nAttToken == XML_defaultSubtotal) bHasDefaultSubtotal = true; else if (!pAttList->hasAttribute(nAttToken)) pAttList->add(nAttToken, ToPsz10(true)); } // XML_defaultSubtotal is true by default; only write it if it's false if (!bHasDefaultSubtotal) pAttList->add(XML_defaultSubtotal, ToPsz10(false)); pAttList->add( XML_compact, ToPsz10(false)); if(bDimInTabularMode) pAttList->add( XML_outline, ToPsz10(false)); sax_fastparser::XFastAttributeListRef xAttributeList(pAttList); pPivotStrm->startElement(XML_pivotField, xAttributeList); pPivotStrm->startElement(XML_items, XML_count, OString::number(static_cast(aMemberSequence.size() + aSubtotalSequence.size())), FSEND); for (const auto & nMember : aMemberSequence) { auto pItemAttList = sax_fastparser::FastSerializerHelper::createAttrList(); if (nMember.second) pItemAttList->add(XML_h, ToPsz10(true)); pItemAttList->add(XML_x, OString::number(static_cast(nMember.first))); sax_fastparser::XFastAttributeListRef xItemAttributeList(pItemAttList); pPivotStrm->singleElement(XML_item, xItemAttributeList); } for (const OString& sSubtotal : aSubtotalSequence) { pPivotStrm->singleElement(XML_item, XML_t, sSubtotal, FSEND); } pPivotStrm->endElement(XML_items); pPivotStrm->endElement(XML_pivotField); } pPivotStrm->endElement(XML_pivotFields); // if (!aRowFields.empty()) { pPivotStrm->startElement(XML_rowFields, XML_count, OString::number(static_cast(aRowFields.size())), FSEND); std::vector::iterator it = aRowFields.begin(), itEnd = aRowFields.end(); for (; it != itEnd; ++it) { pPivotStrm->singleElement(XML_field, XML_x, OString::number(*it), FSEND); } pPivotStrm->endElement(XML_rowFields); } // // if (!aColFields.empty()) { pPivotStrm->startElement(XML_colFields, XML_count, OString::number(static_cast(aColFields.size())), FSEND); std::vector::iterator it = aColFields.begin(), itEnd = aColFields.end(); for (; it != itEnd; ++it) { pPivotStrm->singleElement(XML_field, XML_x, OString::number(*it), FSEND); } pPivotStrm->endElement(XML_colFields); } // // if (!aPageFields.empty()) { pPivotStrm->startElement(XML_pageFields, XML_count, OString::number(static_cast(aPageFields.size())), FSEND); std::vector::iterator it = aPageFields.begin(), itEnd = aPageFields.end(); for (; it != itEnd; ++it) { pPivotStrm->singleElement(XML_pageField, XML_fld, OString::number(*it), XML_hier, OString::number(-1), // TODO : handle this correctly. FSEND); } pPivotStrm->endElement(XML_pageFields); } // if (!aDataFields.empty()) { pPivotStrm->startElement(XML_dataFields, XML_count, OString::number(static_cast(aDataFields.size())), FSEND); std::vector::iterator it = aDataFields.begin(), itEnd = aDataFields.end(); for (; it != itEnd; ++it) { long nDimIdx = it->mnPos; assert(aCachedDims[nDimIdx]); // the loop above should have screened for NULL's. const ScDPSaveDimension& rDim = *it->mpDim; const boost::optional & pName = rDim.GetLayoutName(); pPivotStrm->write("<")->writeId(XML_dataField); if (pName) rStrm.WriteAttributes(XML_name, XclXmlUtils::ToOString(*pName), FSEND); rStrm.WriteAttributes(XML_fld, OString::number(nDimIdx).getStr(), FSEND); ScGeneralFunction eFunc = rDim.GetFunction(); const char* pSubtotal = toOOXMLSubtotalType(eFunc); if (pSubtotal) rStrm.WriteAttributes(XML_subtotal, pSubtotal, FSEND); pPivotStrm->write("/>"); } pPivotStrm->endElement(XML_dataFields); } OUStringBuffer aBuf("../pivotCache/pivotCacheDefinition"); aBuf.append(nCacheId); aBuf.append(".xml"); rStrm.addRelation( pPivotStrm->getOutputStream(), CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheDefinition"), aBuf.makeStringAndClear()); pPivotStrm->endElement(XML_pivotTableDefinition); } void XclExpXmlPivotTables::AppendTable( const ScDPObject* pTable, sal_Int32 nCacheId, sal_Int32 nPivotId ) { maTables.emplace_back(pTable, nCacheId, nPivotId); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */