/* -*- 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 <xepivotxml.hxx>
#include <dpcache.hxx>
#include <dpitemdata.hxx>
#include <dpobject.hxx>
#include <dpsave.hxx>
#include <dputil.hxx>
#include <document.hxx>
#include <generalfunction.hxx>
#include <o3tl/temporary.hxx>
#include <oox/export/utils.hxx>
#include <oox/token/namespaces.hxx>
#include <sax/tools/converter.hxx>
#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp>
#include <com/sun/star/sheet/DataPilotFieldLayoutMode.hpp>
#include <com/sun/star/sheet/DataPilotOutputRangeType.hpp>
#include <com/sun/star/sheet/GeneralFunction.hpp>
#include <o3tl/make_unique.hxx>
#include <vector>
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<long>(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<size_t>(i) < pArray->size());
// We are using XML_x reference (like: <x v="0"/>), instead of values here (eg: <s v="No Discount"/>).
// 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<Entry>& 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<long>(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<ScDPItemData::Type> aDPTypes;
double fMin = std::numeric_limits<double>::infinity(), fMax = -std::numeric_limits<double>::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<ScDPItemData::Type> 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:
// <sharedItems containsSemiMixedTypes="0" containsDate="1" containsString="0" containsMixedTypes="1" minDate="1900-01-03T22:26:04" maxDate="1900-01-07T14:02:04" />
// Example how Excel is saving Dates only:
// <sharedItems containsSemiMixedTypes="0" containsNonDate="0" containsDate="1" containsString="0" minDate="1903-08-24T07:40:48" maxDate="2024-05-23T07:12:00"/>
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:
// <cacheField name="employeeID" numFmtId="0">
// <sharedItems containsString="0" containsBlank="1" containsNumber="1" containsInteger="1" minValue="35" maxValue="89"/>
// </cacheField>
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<long>(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<XclExpXmlPivotCaches::Entry> aCaches;
const ScDPCollection::SheetCaches& rSheetCaches = pDPColl->GetSheetCaches();
const std::vector<ScRange>& 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<TablesType::iterator, bool> r =
m_Tables.insert(std::make_pair(nTab, o3tl::make_unique<XclExpXmlPivotTables>(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<OUString, long> 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<const ScDPSaveDimension*> 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<long> aRowFields;
std::vector<long> aColFields;
std::vector<long> aPageFields;
std::vector<DataField> 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<long>(aPageFields.size())).getStr(), FSEND);
rStrm.WriteAttributes(XML_colPageCount, OString::number(1).getStr(), FSEND);
}
pPivotStrm->write("/>");
// <pivotFields> - 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<long>(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<ScDPLabelData::Member> aMembers;
{
// We need to get the members in actual order, getting which requires non-const reference here
auto& dpo = const_cast<ScDPObject&>(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<size_t, bool> > aMemberSequence;
std::set<size_t> 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<ScDPObject&>(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<OString> 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<long>(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<long>(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);
// <rowFields>
if (!aRowFields.empty())
{
pPivotStrm->startElement(XML_rowFields,
XML_count, OString::number(static_cast<long>(aRowFields.size())),
FSEND);
std::vector<long>::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);
}
// <rowItems>
// <colFields>
if (!aColFields.empty())
{
pPivotStrm->startElement(XML_colFields,
XML_count, OString::number(static_cast<long>(aColFields.size())),
FSEND);
std::vector<long>::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);
}
// <colItems>
// <pageFields>
if (!aPageFields.empty())
{
pPivotStrm->startElement(XML_pageFields,
XML_count, OString::number(static_cast<long>(aPageFields.size())),
FSEND);
std::vector<long>::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);
}
// <dataFields>
if (!aDataFields.empty())
{
pPivotStrm->startElement(XML_dataFields,
XML_count, OString::number(static_cast<long>(aDataFields.size())),
FSEND);
std::vector<DataField>::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<OUString> & 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: */
↑ V547 Expression 'bListItems' is always true.
↑ V547 Expression 'bListItems' is always true.