Kaydet (Commit) 0871804b authored tarafından Mike Kaganski's avatar Mike Kaganski

tdf#124736: Sort group field items

Excel expects the group field items to be in ascending order starting
from "<01/02/2010", then "Jan", "Feb", ..., then end with ">01/02/2020".

Change-Id: I29e9b55f43091ed007f59e10dec64f46a37c7d5f
Reviewed-on: https://gerrit.libreoffice.org/70800
Tested-by: Jenkins
Reviewed-by: 's avatarMike Kaganski <mike.kaganski@collabora.com>
üst d7263d05
...@@ -87,6 +87,7 @@ public: ...@@ -87,6 +87,7 @@ public:
void testTdf123923(); void testTdf123923();
void testTdf123939(); void testTdf123939();
void testTdf124651(); void testTdf124651();
void testTdf124736();
CPPUNIT_TEST_SUITE(ScPivotTableFiltersTest); CPPUNIT_TEST_SUITE(ScPivotTableFiltersTest);
...@@ -131,6 +132,7 @@ public: ...@@ -131,6 +132,7 @@ public:
CPPUNIT_TEST(testTdf123923); CPPUNIT_TEST(testTdf123923);
CPPUNIT_TEST(testTdf123939); CPPUNIT_TEST(testTdf123939);
CPPUNIT_TEST(testTdf124651); CPPUNIT_TEST(testTdf124651);
CPPUNIT_TEST(testTdf124736);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
...@@ -2482,6 +2484,60 @@ void ScPivotTableFiltersTest::testTdf124651() ...@@ -2482,6 +2484,60 @@ void ScPivotTableFiltersTest::testTdf124651()
assertXPath(pDoc, "/x:pivotTableDefinition/x:dataFields/x:dataField", "name", ""); assertXPath(pDoc, "/x:pivotTableDefinition/x:dataFields/x:dataField", "name", "");
} }
void ScPivotTableFiltersTest::testTdf124736()
{
ScDocShellRef xDocSh = loadDoc("pivot-table/shared-dategroup.", FORMAT_XLSX);
CPPUNIT_ASSERT(xDocSh.is());
std::shared_ptr<utl::TempFile> pXPathFile
= ScBootstrapFixture::exportTo(xDocSh.get(), FORMAT_XLSX);
xDocSh->DoClose();
xmlDocPtr pTable = XPathHelper::parseExport(pXPathFile, m_xSFactory,
"xl/pivotCache/pivotCacheDefinition1.xml");
CPPUNIT_ASSERT(pTable);
assertXPath(pTable,
"/x:pivotCacheDefinition/x:cacheFields/x:cacheField[1]/x:fieldGroup/x:groupItems",
"count", "45");
// Group items must start with "<05/16/1958", then years sorted ascending, then ">06/11/2009"
// They used to have years in the beginning, then "<05/16/1958", then ">06/11/2009".
// The "<" and ">" date strings are locale-dependent, so test depends on en_US locale
assertXPath(
pTable,
"/x:pivotCacheDefinition/x:cacheFields/x:cacheField[1]/x:fieldGroup/x:groupItems/x:s[1]",
"v", "<05/16/1958");
for (int i = 2; i <= 44; ++i)
assertXPath(
pTable,
"/x:pivotCacheDefinition/x:cacheFields/x:cacheField[1]/x:fieldGroup/x:groupItems/x:s["
+ OString::number(i) + "]",
"v", OUString::number(1963 + i));
assertXPath(
pTable,
"/x:pivotCacheDefinition/x:cacheFields/x:cacheField[1]/x:fieldGroup/x:groupItems/x:s[45]",
"v", ">06/11/2009");
// Now check that table references these in correct order (document-dependent, so this is how
// it should be in this specific testdoc which shows "<" and ">" values in the end)
pTable = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/pivotTables/pivotTable1.xml");
CPPUNIT_ASSERT(pTable);
assertXPath(pTable, "/x:pivotTableDefinition/x:pivotFields/x:pivotField[1]/x:items", "count",
"46");
const int vals[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 0, 44 };
for (size_t i = 0; i < SAL_N_ELEMENTS(vals); ++i)
{
assertXPath(pTable,
"/x:pivotTableDefinition/x:pivotFields/x:pivotField[1]/x:items/x:item["
+ OString::number(i + 1) + "]",
"x", OUString::number(vals[i]));
}
assertXPath(pTable, "/x:pivotTableDefinition/x:pivotFields/x:pivotField[1]/x:items/x:item[46]",
"t", "default");
}
CPPUNIT_TEST_SUITE_REGISTRATION(ScPivotTableFiltersTest); CPPUNIT_TEST_SUITE_REGISTRATION(ScPivotTableFiltersTest);
CPPUNIT_PLUGIN_IMPLEMENT(); CPPUNIT_PLUGIN_IMPLEMENT();
......
...@@ -197,7 +197,42 @@ OUString GetExcelFormattedDate( double fSerialDateTime, const SvNumberFormatter& ...@@ -197,7 +197,42 @@ OUString GetExcelFormattedDate( double fSerialDateTime, const SvNumberFormatter&
::sax::Converter::convertDateTime(sBuf, aUDateTime, nullptr, true); ::sax::Converter::convertDateTime(sBuf, aUDateTime, nullptr, true);
return sBuf.makeStringAndClear(); return sBuf.makeStringAndClear();
} }
// Excel seems to expect different order of group item values; we need to rearrange elements
// to output "<date1" first, then elements, then ">date2" last.
// Since ScDPItemData::DateFirst is -1, ScDPItemData::DateLast is 10000, and other date group
// items would fit between those in order (like 0 = Jan, 1 = Feb, etc.), we can simply sort
// the items by value.
std::vector<OUString> SortGroupItems(const ScDPCache& rCache, long nDim)
{
struct ItemData
{
sal_Int32 nVal;
const ScDPItemData* pData;
};
std::vector<ItemData> aDataToSort;
ScfInt32Vec aGIIds;
rCache.GetGroupDimMemberIds(nDim, aGIIds);
for (sal_Int32 id : aGIIds)
{
const ScDPItemData* pGIData = rCache.GetItemDataById(nDim, id);
if (pGIData->GetType() == ScDPItemData::GroupValue)
{
auto aGroupVal = pGIData->GetGroupValue();
aDataToSort.push_back({ aGroupVal.mnValue, pGIData });
}
}
std::sort(aDataToSort.begin(), aDataToSort.end(),
[](const ItemData& a, const ItemData& b) { return a.nVal < b.nVal; });
std::vector<OUString> aSortedResult;
for (const auto& el : aDataToSort)
{
aSortedResult.push_back(rCache.GetFormattedString(nDim, *el.pData, false));
}
return aSortedResult;
} }
} // namespace
void XclExpXmlPivotCaches::SavePivotCacheXml( XclExpXmlStream& rStrm, const Entry& rEntry, sal_Int32 nCounter ) void XclExpXmlPivotCaches::SavePivotCacheXml( XclExpXmlStream& rStrm, const Entry& rEntry, sal_Int32 nCounter )
{ {
...@@ -293,17 +328,11 @@ void XclExpXmlPivotCaches::SavePivotCacheXml( XclExpXmlStream& rStrm, const Entr ...@@ -293,17 +328,11 @@ void XclExpXmlPivotCaches::SavePivotCacheXml( XclExpXmlStream& rStrm, const Entr
pDefStrm->singleElement(XML_rangePr, pGroupAttList); pDefStrm->singleElement(XML_rangePr, pGroupAttList);
// groupItems element // groupItems element
ScfInt32Vec aGIIds; auto aElemVec = SortGroupItems(rCache, i);
rCache.GetGroupDimMemberIds(i, aGIIds); pDefStrm->startElement(XML_groupItems, XML_count, OString::number(aElemVec.size()), FSEND);
pDefStrm->startElement(XML_groupItems, XML_count, OString::number(aGIIds.size()), FSEND); for (const auto& sElem : aElemVec)
for (auto nGIId : aGIIds)
{ {
const ScDPItemData* pGIData = rCache.GetItemDataById(i, nGIId); pDefStrm->singleElement(XML_s, XML_v, sElem.toUtf8(), FSEND);
if (pGIData->GetType() == ScDPItemData::GroupValue)
{
OUString sVal = rCache.GetFormattedString(i, *pGIData, false);
pDefStrm->singleElement(XML_s, XML_v, sVal.toUtf8(), FSEND);
}
} }
pDefStrm->endElement(XML_groupItems); pDefStrm->endElement(XML_groupItems);
pDefStrm->endElement(XML_fieldGroup); pDefStrm->endElement(XML_fieldGroup);
...@@ -878,44 +907,40 @@ void XclExpXmlPivotTables::SavePivotTableXml( XclExpXmlStream& rStrm, const ScDP ...@@ -878,44 +907,40 @@ void XclExpXmlPivotTables::SavePivotTableXml( XclExpXmlStream& rStrm, const ScDP
dpo.GetMembers(i, dpo.GetUsedHierarchy(i), aMembers); dpo.GetMembers(i, dpo.GetUsedHierarchy(i), aMembers);
} }
std::vector<const ScDPItemData*> rCacheFieldItems; std::vector<OUString> aCacheFieldItems;
if (i < rCache.GetFieldCount() && !rCache.GetGroupType(i)) if (i < rCache.GetFieldCount() && !rCache.GetGroupType(i))
{
for (const auto& it : rCache.GetDimMemberValues(i)) for (const auto& it : rCache.GetDimMemberValues(i))
rCacheFieldItems.push_back(&it); {
OUString sFormattedName;
if (it.HasStringData() || it.IsEmpty())
sFormattedName = it.GetString();
else
sFormattedName = const_cast<ScDPObject&>(rDPObj).GetFormattedString(
pDim->GetName(), it.GetValue());
aCacheFieldItems.push_back(sFormattedName);
}
}
else else
{ {
ScfInt32Vec aGIIds; aCacheFieldItems = SortGroupItems(rCache, i);
rCache.GetGroupDimMemberIds(i, aGIIds);
for (const sal_Int32 id : aGIIds)
rCacheFieldItems.push_back(rCache.GetItemDataById(i, id));
} }
// The pair contains the member index in cache and if it is hidden // The pair contains the member index in cache and if it is hidden
std::vector< std::pair<size_t, bool> > aMemberSequence; std::vector< std::pair<size_t, bool> > aMemberSequence;
std::set<size_t> aUsedCachePositions; std::set<size_t> aUsedCachePositions;
for (const auto & rMember : aMembers) for (const auto & rMember : aMembers)
{ {
auto it = std::find_if(rCacheFieldItems.begin(), rCacheFieldItems.end(), auto it = std::find(aCacheFieldItems.begin(), aCacheFieldItems.end(), rMember.maName);
[&rDPObj, &pDim, &rMember, &rCache, i](const ScDPItemData* pItem) { if (it != aCacheFieldItems.end())
OUString sFormattedName;
if (pItem->GetType() == ScDPItemData::GroupValue)
sFormattedName = rCache.GetFormattedString(i, *pItem, false);
else if (pItem->HasStringData() || pItem->IsEmpty())
sFormattedName = pItem->GetString();
else
sFormattedName = const_cast<ScDPObject&>(rDPObj).GetFormattedString(
pDim->GetName(), pItem->GetValue());
return sFormattedName == rMember.maName;
});
if (it != rCacheFieldItems.end())
{ {
size_t nCachePos = static_cast<size_t>(std::distance(rCacheFieldItems.begin(), it)); size_t nCachePos = static_cast<size_t>(std::distance(aCacheFieldItems.begin(), it));
auto aInserted = aUsedCachePositions.insert(nCachePos); auto aInserted = aUsedCachePositions.insert(nCachePos);
if (aInserted.second) if (aInserted.second)
aMemberSequence.emplace_back(std::make_pair(nCachePos, !rMember.mbVisible)); aMemberSequence.emplace_back(std::make_pair(nCachePos, !rMember.mbVisible));
} }
} }
// Now add all remaining cache items as hidden // Now add all remaining cache items as hidden
for (size_t nItem = 0; nItem < rCacheFieldItems.size(); ++nItem) for (size_t nItem = 0; nItem < aCacheFieldItems.size(); ++nItem)
{ {
if (aUsedCachePositions.find(nItem) == aUsedCachePositions.end()) if (aUsedCachePositions.find(nItem) == aUsedCachePositions.end())
aMemberSequence.emplace_back(nItem, true); aMemberSequence.emplace_back(nItem, true);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment