1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <dpcache.hxx>
21 #include <dpdimsave.hxx>
22 #include <dpgroup.hxx>
23 #include <dpobject.hxx>
24 #include <dputil.hxx>
25 #include <document.hxx>
26 
27 #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
28 
29 #include <svl/zforlist.hxx>
30 #include <osl/diagnose.h>
31 #include <rtl/math.hxx>
32 #include <algorithm>
33 
34 #include <globstr.hrc>
35 #include <scresid.hxx>
36 
37 using namespace com::sun::star;
38 
ScDPSaveGroupItem(const OUString & rName)39 ScDPSaveGroupItem::ScDPSaveGroupItem( const OUString& rName ) :
40     aGroupName(rName) {}
41 
~ScDPSaveGroupItem()42 ScDPSaveGroupItem::~ScDPSaveGroupItem() {}
43 
AddElement(const OUString & rName)44 void ScDPSaveGroupItem::AddElement( const OUString& rName )
45 {
46     aElements.push_back(rName);
47 }
48 
AddElementsFromGroup(const ScDPSaveGroupItem & rGroup)49 void ScDPSaveGroupItem::AddElementsFromGroup( const ScDPSaveGroupItem& rGroup )
50 {
51     // add all elements of the other group (used for nested grouping)
52 
53     for ( const auto& rElement : rGroup.aElements )
54         aElements.push_back( rElement );
55 }
56 
RemoveElement(const OUString & rName)57 bool ScDPSaveGroupItem::RemoveElement( const OUString& rName )
58 {
59     auto it = std::find(aElements.begin(), aElements.end(), rName); //TODO: ignore case
60     if (it != aElements.end())
61     {
62         aElements.erase(it);
63         return true;
64     }
65     return false;   // not found
66 }
67 
IsEmpty() const68 bool ScDPSaveGroupItem::IsEmpty() const
69 {
70     return aElements.empty();
71 }
72 
GetElementCount() const73 size_t ScDPSaveGroupItem::GetElementCount() const
74 {
75     return aElements.size();
76 }
77 
GetElementByIndex(size_t nIndex) const78 const OUString* ScDPSaveGroupItem::GetElementByIndex(size_t nIndex) const
79 {
80     return (nIndex < aElements.size()) ? &aElements[ nIndex ] : nullptr;
81 }
82 
Rename(const OUString & rNewName)83 void ScDPSaveGroupItem::Rename( const OUString& rNewName )
84 {
85     aGroupName = rNewName;
86 }
87 
RemoveElementsFromGroups(ScDPSaveGroupDimension & rDimension) const88 void ScDPSaveGroupItem::RemoveElementsFromGroups( ScDPSaveGroupDimension& rDimension ) const
89 {
90     // remove this group's elements from their groups in rDimension
91     // (rDimension must be a different dimension from the one which contains this)
92 
93     for ( const auto& rElement : aElements )
94         rDimension.RemoveFromGroups( rElement );
95 }
96 
ConvertElementsToItems(SvNumberFormatter * pFormatter) const97 void ScDPSaveGroupItem::ConvertElementsToItems(SvNumberFormatter* pFormatter) const
98 {
99     maItems.reserve(aElements.size());
100     for (const auto& rElement : aElements)
101     {
102         sal_uInt32 nFormat = 0;
103         double fValue;
104         ScDPItemData aData;
105         if (pFormatter->IsNumberFormat(rElement, nFormat, fValue))
106             aData.SetValue(fValue);
107         else
108             aData.SetString(rElement);
109 
110         maItems.push_back(aData);
111     }
112 }
113 
HasInGroup(const ScDPItemData & rItem) const114 bool ScDPSaveGroupItem::HasInGroup(const ScDPItemData& rItem) const
115 {
116     return std::find(maItems.begin(), maItems.end(), rItem) != maItems.end();
117 }
118 
AddToData(ScDPGroupDimension & rDataDim) const119 void ScDPSaveGroupItem::AddToData(ScDPGroupDimension& rDataDim) const
120 {
121     ScDPGroupItem aGroup(aGroupName);
122     for (const auto& rItem : maItems)
123         aGroup.AddElement(rItem);
124 
125     rDataDim.AddItem(aGroup);
126 }
127 
ScDPSaveGroupDimension(const OUString & rSource,const OUString & rName)128 ScDPSaveGroupDimension::ScDPSaveGroupDimension( const OUString& rSource, const OUString& rName ) :
129     aSourceDim( rSource ),
130     aGroupDimName( rName ),
131     nDatePart( 0 )
132 {
133 }
134 
ScDPSaveGroupDimension(const OUString & rSource,const OUString & rName,const ScDPNumGroupInfo & rDateInfo,sal_Int32 nPart)135 ScDPSaveGroupDimension::ScDPSaveGroupDimension( const OUString& rSource, const OUString& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) :
136     aSourceDim( rSource ),
137     aGroupDimName( rName ),
138     aDateInfo( rDateInfo ),
139     nDatePart( nPart )
140 {
141 }
142 
SetDateInfo(const ScDPNumGroupInfo & rInfo,sal_Int32 nPart)143 void ScDPSaveGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
144 {
145     aDateInfo = rInfo;
146     nDatePart = nPart;
147 }
148 
AddGroupItem(const ScDPSaveGroupItem & rItem)149 void ScDPSaveGroupDimension::AddGroupItem( const ScDPSaveGroupItem& rItem )
150 {
151     aGroups.push_back( rItem );
152 }
153 
CreateGroupName(std::u16string_view rPrefix)154 OUString ScDPSaveGroupDimension::CreateGroupName(std::u16string_view rPrefix)
155 {
156     // create a name for a new group, using "Group1", "Group2" etc. (translated prefix in rPrefix)
157 
158     //TODO: look in all dimensions, to avoid clashes with automatic groups (=name of base element)?
159     //TODO: (only dimensions for the same base)
160 
161     sal_Int32 nAdd = 1;                                 // first try is "Group1"
162     const sal_Int32 nMaxAdd = nAdd + aGroups.size();    // limit the loop
163     while ( nAdd <= nMaxAdd )
164     {
165         OUString aGroupName = rPrefix + OUString::number( nAdd );
166 
167         // look for existing groups
168         bool bExists = std::any_of(aGroups.begin(), aGroups.end(),
169             [&aGroupName](const ScDPSaveGroupItem& rGroup) {
170                 return rGroup.GetGroupName() == aGroupName; //TODO: ignore case
171             });
172 
173         if ( !bExists )
174             return aGroupName;          // found a new name
175 
176         ++nAdd;                         // continue with higher number
177     }
178 
179     OSL_FAIL("CreateGroupName: no valid name found");
180     return OUString();
181 }
182 
GetNamedGroup(const OUString & rGroupName) const183 const ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroup( const OUString& rGroupName ) const
184 {
185     return const_cast< ScDPSaveGroupDimension* >( this )->GetNamedGroupAcc( rGroupName );
186 }
187 
GetNamedGroupAcc(const OUString & rGroupName)188 ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroupAcc( const OUString& rGroupName )
189 {
190     auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
191         [&rGroupName](const ScDPSaveGroupItem& rGroup) {
192             return rGroup.GetGroupName() == rGroupName; //TODO: ignore case
193         });
194     if (aIter != aGroups.end())
195         return &*aIter;
196 
197     return nullptr;        // none found
198 }
199 
GetGroupCount() const200 tools::Long ScDPSaveGroupDimension::GetGroupCount() const
201 {
202     return aGroups.size();
203 }
204 
GetGroupByIndex(tools::Long nIndex) const205 const ScDPSaveGroupItem& ScDPSaveGroupDimension::GetGroupByIndex( tools::Long nIndex ) const
206 {
207     return aGroups[nIndex];
208 }
209 
210 
RemoveFromGroups(const OUString & rItemName)211 void ScDPSaveGroupDimension::RemoveFromGroups( const OUString& rItemName )
212 {
213     //  if the item is in any group, remove it from the group,
214     //  also remove the group if it is empty afterwards
215 
216     for ( ScDPSaveGroupItemVec::iterator aIter(aGroups.begin()); aIter != aGroups.end(); ++aIter )
217         if ( aIter->RemoveElement( rItemName ) )
218         {
219             if ( aIter->IsEmpty() )         // removed last item from the group?
220                 aGroups.erase( aIter );     // then remove the group
221 
222             return;     // don't have to look further
223         }
224 }
225 
RemoveGroup(const OUString & rGroupName)226 void ScDPSaveGroupDimension::RemoveGroup(const OUString& rGroupName)
227 {
228     auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
229         [&rGroupName](const ScDPSaveGroupItem& rGroup) {
230             return rGroup.GetGroupName() == rGroupName; //TODO: ignore case
231         });
232     if (aIter != aGroups.end())
233         aGroups.erase( aIter );
234 }
235 
IsEmpty() const236 bool ScDPSaveGroupDimension::IsEmpty() const
237 {
238     return aGroups.empty();
239 }
240 
HasOnlyHidden(const ScDPUniqueStringSet & rVisible)241 bool ScDPSaveGroupDimension::HasOnlyHidden(const ScDPUniqueStringSet& rVisible)
242 {
243     // check if there are only groups that don't appear in the list of visible names
244 
245     return std::none_of(aGroups.begin(), aGroups.end(),
246         [&rVisible](const ScDPSaveGroupItem& rGroup) { return rVisible.count(rGroup.GetGroupName()) > 0; });
247 }
248 
Rename(const OUString & rNewName)249 void ScDPSaveGroupDimension::Rename( const OUString& rNewName )
250 {
251     aGroupDimName = rNewName;
252 }
253 
IsInGroup(const ScDPItemData & rItem) const254 bool ScDPSaveGroupDimension::IsInGroup(const ScDPItemData& rItem) const
255 {
256     return std::any_of(aGroups.begin(), aGroups.end(),
257         [&rItem](const ScDPSaveGroupItem& rGroup) { return rGroup.HasInGroup(rItem); });
258 }
259 
260 namespace {
261 
isInteger(double fValue)262 bool isInteger(double fValue)
263 {
264     return rtl::math::approxEqual(fValue, rtl::math::approxFloor(fValue));
265 }
266 
fillDateGroupDimension(ScDPCache & rCache,ScDPNumGroupInfo & rDateInfo,tools::Long nSourceDim,tools::Long nGroupDim,sal_Int32 nDatePart,const SvNumberFormatter * pFormatter)267 void fillDateGroupDimension(
268     ScDPCache& rCache, ScDPNumGroupInfo& rDateInfo, tools::Long nSourceDim, tools::Long nGroupDim,
269     sal_Int32 nDatePart, const SvNumberFormatter* pFormatter)
270 {
271     // Auto min/max is only used for "Years" part, but the loop is always
272     // needed.
273     double fSourceMin = 0.0;
274     double fSourceMax = 0.0;
275     bool bFirst = true;
276 
277     const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nSourceDim);
278     for (const ScDPItemData& rItem : rItems)
279     {
280         if (rItem.GetType() != ScDPItemData::Value)
281             continue;
282 
283         double fVal = rItem.GetValue();
284         if (bFirst)
285         {
286             fSourceMin = fSourceMax = fVal;
287             bFirst = false;
288         }
289         else
290         {
291             if (fVal < fSourceMin)
292                 fSourceMin = fVal;
293             if ( fVal > fSourceMax )
294                 fSourceMax = fVal;
295         }
296     }
297 
298     // For the start/end values, use the same date rounding as in
299     // ScDPNumGroupDimension::GetNumEntries (but not for the list of
300     // available years).
301     if (rDateInfo.mbAutoStart)
302         rDateInfo.mfStart = rtl::math::approxFloor(fSourceMin);
303     if (rDateInfo.mbAutoEnd)
304         rDateInfo.mfEnd = rtl::math::approxFloor(fSourceMax) + 1;
305 
306     //TODO: if not automatic, limit fSourceMin/fSourceMax for list of year values?
307 
308     tools::Long nStart = 0, nEnd = 0; // end is inclusive
309 
310     switch (nDatePart)
311     {
312         case sheet::DataPilotFieldGroupBy::YEARS:
313             nStart = ScDPUtil::getDatePartValue(
314                 fSourceMin, nullptr, sheet::DataPilotFieldGroupBy::YEARS, pFormatter);
315             nEnd = ScDPUtil::getDatePartValue(fSourceMax, nullptr, sheet::DataPilotFieldGroupBy::YEARS, pFormatter);
316             break;
317         case sheet::DataPilotFieldGroupBy::QUARTERS: nStart = 1; nEnd = 4;   break;
318         case sheet::DataPilotFieldGroupBy::MONTHS:   nStart = 1; nEnd = 12;  break;
319         case sheet::DataPilotFieldGroupBy::DAYS:     nStart = 1; nEnd = 366; break;
320         case sheet::DataPilotFieldGroupBy::HOURS:    nStart = 0; nEnd = 23;  break;
321         case sheet::DataPilotFieldGroupBy::MINUTES:  nStart = 0; nEnd = 59;  break;
322         case sheet::DataPilotFieldGroupBy::SECONDS:  nStart = 0; nEnd = 59;  break;
323         default:
324             OSL_FAIL("invalid date part");
325     }
326 
327     // Now, populate the group items in the cache.
328     rCache.ResetGroupItems(nGroupDim, rDateInfo, nDatePart);
329 
330     for (tools::Long nValue = nStart; nValue <= nEnd; ++nValue)
331         rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, nValue));
332 
333     // add first/last entry (min/max)
334     rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, ScDPItemData::DateFirst));
335     rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, ScDPItemData::DateLast));
336 }
337 
338 }
339 
AddToData(ScDPGroupTableData & rData) const340 void ScDPSaveGroupDimension::AddToData( ScDPGroupTableData& rData ) const
341 {
342     tools::Long nSourceIndex = rData.GetDimensionIndex( aSourceDim );
343     if ( nSourceIndex < 0 )
344         return;
345 
346     ScDPGroupDimension aDim( nSourceIndex, aGroupDimName );
347     if ( nDatePart )
348     {
349         // date grouping
350 
351         aDim.SetDateDimension();
352     }
353     else
354     {
355         // normal (manual) grouping
356 
357         for (const auto& rGroup : aGroups)
358             rGroup.AddToData(aDim);
359     }
360 
361     rData.AddGroupDimension( aDim );
362 }
363 
AddToCache(ScDPCache & rCache) const364 void ScDPSaveGroupDimension::AddToCache(ScDPCache& rCache) const
365 {
366     tools::Long nSourceDim = rCache.GetDimensionIndex(aSourceDim);
367     if (nSourceDim < 0)
368         return;
369 
370     tools::Long nDim = rCache.AppendGroupField();
371     SvNumberFormatter* pFormatter = rCache.GetDoc().GetFormatTable();
372 
373     if (nDatePart)
374     {
375         fillDateGroupDimension(rCache, aDateInfo, nSourceDim, nDim, nDatePart, pFormatter);
376         return;
377     }
378 
379     rCache.ResetGroupItems(nDim, aDateInfo, 0);
380     for (const ScDPSaveGroupItem& rGI : aGroups)
381     {
382         rGI.ConvertElementsToItems(pFormatter);
383         rCache.SetGroupItem(nDim, ScDPItemData(rGI.GetGroupName()));
384     }
385 
386     const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nSourceDim);
387     for (const ScDPItemData& rItem : rItems)
388     {
389         if (!IsInGroup(rItem))
390             // Not in any group.  Add as its own group.
391             rCache.SetGroupItem(nDim, rItem);
392     }
393 }
394 
ScDPSaveNumGroupDimension(const OUString & rName,const ScDPNumGroupInfo & rInfo)395 ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const OUString& rName, const ScDPNumGroupInfo& rInfo ) :
396     aDimensionName( rName ),
397     aGroupInfo( rInfo ),
398     nDatePart( 0 )
399 {
400 }
401 
ScDPSaveNumGroupDimension(const OUString & rName,const ScDPNumGroupInfo & rDateInfo,sal_Int32 nPart)402 ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const OUString& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) :
403     aDimensionName( rName ),
404     aDateInfo( rDateInfo ),
405     nDatePart( nPart )
406 {
407 }
408 
AddToData(ScDPGroupTableData & rData) const409 void ScDPSaveNumGroupDimension::AddToData( ScDPGroupTableData& rData ) const
410 {
411     tools::Long nSource = rData.GetDimensionIndex( aDimensionName );
412     if ( nSource >= 0 )
413     {
414         ScDPNumGroupDimension aDim( aGroupInfo );           // aGroupInfo: value grouping
415         if ( nDatePart )
416             aDim.SetDateDimension();
417 
418         rData.SetNumGroupDimension( nSource, aDim );
419     }
420 }
421 
AddToCache(ScDPCache & rCache) const422 void ScDPSaveNumGroupDimension::AddToCache(ScDPCache& rCache) const
423 {
424     tools::Long nDim = rCache.GetDimensionIndex(aDimensionName);
425     if (nDim < 0)
426         return;
427 
428     if (aDateInfo.mbEnable)
429     {
430         // Date grouping
431         SvNumberFormatter* pFormatter = rCache.GetDoc().GetFormatTable();
432         fillDateGroupDimension(rCache, aDateInfo, nDim, nDim, nDatePart, pFormatter);
433     }
434     else if (aGroupInfo.mbEnable)
435     {
436         // Number-range grouping
437 
438         // Look through the source entries for non-integer numbers, minimum
439         // and maximum.
440 
441         // non-integer GroupInfo values count, too
442         aGroupInfo.mbIntegerOnly =
443             (aGroupInfo.mbAutoStart || isInteger(aGroupInfo.mfStart)) &&
444             (aGroupInfo.mbAutoEnd || isInteger(aGroupInfo.mfEnd)) &&
445             isInteger(aGroupInfo.mfStep);
446 
447         double fSourceMin = 0.0;
448         double fSourceMax = 0.0;
449         bool bFirst = true;
450 
451         const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nDim);
452         for (const ScDPItemData& rItem : rItems)
453         {
454             if (rItem.GetType() != ScDPItemData::Value)
455                 continue;
456 
457             double fValue = rItem.GetValue();
458             if (bFirst)
459             {
460                 fSourceMin = fSourceMax = fValue;
461                 bFirst = false;
462                 continue;
463             }
464 
465             if (fValue < fSourceMin)
466                 fSourceMin = fValue;
467             if (fValue > fSourceMax)
468                 fSourceMax = fValue;
469 
470             if (aGroupInfo.mbIntegerOnly && !isInteger(fValue))
471             {
472                 // If any non-integer numbers are involved, the group labels
473                 // are shown including their upper limit.
474                 aGroupInfo.mbIntegerOnly = false;
475             }
476         }
477 
478         if (aGroupInfo.mbDateValues)
479         {
480             // special handling for dates: always integer, round down limits
481             aGroupInfo.mbIntegerOnly = true;
482             fSourceMin = rtl::math::approxFloor(fSourceMin);
483             fSourceMax = rtl::math::approxFloor(fSourceMax) + 1;
484         }
485 
486         if (aGroupInfo.mbAutoStart)
487             aGroupInfo.mfStart = fSourceMin;
488         if (aGroupInfo.mbAutoEnd)
489             aGroupInfo.mfEnd = fSourceMax;
490 
491         //TODO: limit number of entries?
492 
493         tools::Long nLoopCount = 0;
494         double fLoop = aGroupInfo.mfStart;
495 
496         rCache.ResetGroupItems(nDim, aGroupInfo, 0);
497 
498         // Use "less than" instead of "less or equal" for the loop - don't
499         // create a group that consists only of the end value. Instead, the
500         // end value is then included in the last group (last group is bigger
501         // than the others). The first group has to be created nonetheless.
502         // GetNumGroupForValue has corresponding logic.
503 
504         bool bFirstGroup = true;
505         while (bFirstGroup || (fLoop < aGroupInfo.mfEnd && !rtl::math::approxEqual(fLoop, aGroupInfo.mfEnd)))
506         {
507             ScDPItemData aItem;
508             aItem.SetRangeStart(fLoop);
509             rCache.SetGroupItem(nDim, aItem);
510             ++nLoopCount;
511             fLoop = aGroupInfo.mfStart + nLoopCount * aGroupInfo.mfStep;
512             bFirstGroup = false;
513 
514             // ScDPItemData values are compared with approxEqual
515         }
516 
517         ScDPItemData aItem;
518         aItem.SetRangeFirst();
519         rCache.SetGroupItem(nDim, aItem);
520 
521         aItem.SetRangeLast();
522         rCache.SetGroupItem(nDim, aItem);
523     }
524 }
525 
SetGroupInfo(const ScDPNumGroupInfo & rNew)526 void ScDPSaveNumGroupDimension::SetGroupInfo( const ScDPNumGroupInfo& rNew )
527 {
528     aGroupInfo = rNew;
529 }
530 
SetDateInfo(const ScDPNumGroupInfo & rInfo,sal_Int32 nPart)531 void ScDPSaveNumGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
532 {
533     aDateInfo = rInfo;
534     nDatePart = nPart;
535 }
536 
537 namespace {
538 
539 struct ScDPSaveGroupDimNameFunc
540 {
541     OUString       maDimName;
ScDPSaveGroupDimNameFunc__anonde2870710711::ScDPSaveGroupDimNameFunc542     explicit     ScDPSaveGroupDimNameFunc( const OUString& rDimName ) : maDimName( rDimName ) {}
operator ()__anonde2870710711::ScDPSaveGroupDimNameFunc543     bool         operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetGroupDimName() == maDimName; }
544 };
545 
546 struct ScDPSaveGroupSourceNameFunc
547 {
548     OUString       maSrcDimName;
ScDPSaveGroupSourceNameFunc__anonde2870710711::ScDPSaveGroupSourceNameFunc549     explicit     ScDPSaveGroupSourceNameFunc( const OUString& rSrcDimName ) : maSrcDimName( rSrcDimName ) {}
operator ()__anonde2870710711::ScDPSaveGroupSourceNameFunc550     bool         operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetSourceDimName() == maSrcDimName; }
551 };
552 
553 } // namespace
554 
ScDPDimensionSaveData()555 ScDPDimensionSaveData::ScDPDimensionSaveData()
556 {
557 }
558 
operator ==(const ScDPDimensionSaveData &) const559 bool ScDPDimensionSaveData::operator==( const ScDPDimensionSaveData& ) const
560 {
561     return false;
562 }
563 
AddGroupDimension(const ScDPSaveGroupDimension & rGroupDim)564 void ScDPDimensionSaveData::AddGroupDimension( const ScDPSaveGroupDimension& rGroupDim )
565 {
566     OSL_ENSURE( ::std::none_of( maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) ),
567         "ScDPDimensionSaveData::AddGroupDimension - group dimension exists already" );
568     // ReplaceGroupDimension() adds new or replaces existing
569     ReplaceGroupDimension( rGroupDim );
570 }
571 
ReplaceGroupDimension(const ScDPSaveGroupDimension & rGroupDim)572 void ScDPDimensionSaveData::ReplaceGroupDimension( const ScDPSaveGroupDimension& rGroupDim )
573 {
574     ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
575         maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) );
576     if( aIt == maGroupDims.end() )
577         maGroupDims.push_back( rGroupDim );
578     else
579         *aIt = rGroupDim;
580 }
581 
RemoveGroupDimension(const OUString & rGroupDimName)582 void ScDPDimensionSaveData::RemoveGroupDimension( const OUString& rGroupDimName )
583 {
584     ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
585         maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
586     if( aIt != maGroupDims.end() )
587         maGroupDims.erase( aIt );
588 }
589 
AddNumGroupDimension(const ScDPSaveNumGroupDimension & rGroupDim)590 void ScDPDimensionSaveData::AddNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim )
591 {
592     OSL_ENSURE( maNumGroupDims.count( rGroupDim.GetDimensionName() ) == 0,
593         "ScDPDimensionSaveData::AddNumGroupDimension - numeric group dimension exists already" );
594     // ReplaceNumGroupDimension() adds new or replaces existing
595     ReplaceNumGroupDimension( rGroupDim );
596 }
597 
ReplaceNumGroupDimension(const ScDPSaveNumGroupDimension & rGroupDim)598 void ScDPDimensionSaveData::ReplaceNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim )
599 {
600     ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDim.GetDimensionName() );
601     if( aIt == maNumGroupDims.end() )
602         maNumGroupDims.emplace( rGroupDim.GetDimensionName(), rGroupDim );
603     else
604         aIt->second = rGroupDim;
605 }
606 
RemoveNumGroupDimension(const OUString & rGroupDimName)607 void ScDPDimensionSaveData::RemoveNumGroupDimension( const OUString& rGroupDimName )
608 {
609     maNumGroupDims.erase( rGroupDimName );
610 }
611 
WriteToData(ScDPGroupTableData & rData) const612 void ScDPDimensionSaveData::WriteToData( ScDPGroupTableData& rData ) const
613 {
614     //  rData is assumed to be empty
615     //  AddToData also handles date grouping
616 
617     for( const auto& rGroupDim : maGroupDims )
618         rGroupDim.AddToData( rData );
619 
620     for( const auto& rEntry : maNumGroupDims )
621         rEntry.second.AddToData( rData );
622 }
623 
WriteToCache(ScDPCache & rCache) const624 void ScDPDimensionSaveData::WriteToCache(ScDPCache& rCache) const
625 {
626     for (const auto& rEntry : maGroupDims)
627         rEntry.AddToCache(rCache);
628     for (const auto& rEntry : maNumGroupDims)
629         rEntry.second.AddToCache(rCache);
630 }
631 
GetGroupDimForBase(const OUString & rBaseDimName) const632 const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimForBase( const OUString& rBaseDimName ) const
633 {
634     return const_cast< ScDPDimensionSaveData* >( this )->GetGroupDimAccForBase( rBaseDimName );
635 }
636 
GetNamedGroupDim(const OUString & rGroupDimName) const637 const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDim( const OUString& rGroupDimName ) const
638 {
639     return const_cast< ScDPDimensionSaveData* >( this )->GetNamedGroupDimAcc( rGroupDimName );
640 }
641 
GetFirstNamedGroupDim(const OUString & rBaseDimName) const642 const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDim( const OUString& rBaseDimName ) const
643 {
644     return const_cast< ScDPDimensionSaveData* >( this )->GetFirstNamedGroupDimAcc( rBaseDimName );
645 }
646 
GetNextNamedGroupDim(const OUString & rGroupDimName) const647 const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDim( const OUString& rGroupDimName ) const
648 {
649     return const_cast< ScDPDimensionSaveData* >( this )->GetNextNamedGroupDimAcc( rGroupDimName );
650 }
651 
GetNumGroupDim(const OUString & rGroupDimName) const652 const ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDim( const OUString& rGroupDimName ) const
653 {
654     return const_cast< ScDPDimensionSaveData* >( this )->GetNumGroupDimAcc( rGroupDimName );
655 }
656 
GetGroupDimAccForBase(const OUString & rBaseDimName)657 ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimAccForBase( const OUString& rBaseDimName )
658 {
659     ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDimAcc( rBaseDimName );
660     return pGroupDim ? pGroupDim : GetNextNamedGroupDimAcc( rBaseDimName );
661 }
662 
GetNamedGroupDimAcc(const OUString & rGroupDimName)663 ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDimAcc( const OUString& rGroupDimName )
664 {
665     ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
666         maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
667     return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
668 }
669 
GetFirstNamedGroupDimAcc(const OUString & rBaseDimName)670 ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDimAcc( const OUString& rBaseDimName )
671 {
672     ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
673         maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupSourceNameFunc( rBaseDimName ) );
674     return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
675 }
676 
GetNextNamedGroupDimAcc(const OUString & rGroupDimName)677 ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDimAcc( const OUString& rGroupDimName )
678 {
679     // find the group dimension with the passed name
680     ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
681         maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
682     // find next group dimension based on the same source dimension name
683     if( aIt != maGroupDims.end() )
684         aIt = ::std::find_if( aIt + 1, maGroupDims.end(), ScDPSaveGroupSourceNameFunc( aIt->GetSourceDimName() ) );
685     return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
686 }
687 
GetNumGroupDimAcc(const OUString & rGroupDimName)688 ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDimAcc( const OUString& rGroupDimName )
689 {
690     ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDimName );
691     return (aIt == maNumGroupDims.end()) ? nullptr : &aIt->second;
692 }
693 
HasGroupDimensions() const694 bool ScDPDimensionSaveData::HasGroupDimensions() const
695 {
696     return !maGroupDims.empty() || !maNumGroupDims.empty();
697 }
698 
CollectDateParts(const OUString & rBaseDimName) const699 sal_Int32 ScDPDimensionSaveData::CollectDateParts( const OUString& rBaseDimName ) const
700 {
701     sal_Int32 nParts = 0;
702     // start with part of numeric group
703     if( const ScDPSaveNumGroupDimension* pNumDim = GetNumGroupDim( rBaseDimName ) )
704         nParts |= pNumDim->GetDatePart();
705     // collect parts from all matching group dimensions
706     for( const ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDim( rBaseDimName ); pGroupDim; pGroupDim = GetNextNamedGroupDim( pGroupDim->GetGroupDimName() ) )
707         nParts |= pGroupDim->GetDatePart();
708 
709     return nParts;
710 }
711 
CreateGroupDimName(const OUString & rSourceName,const ScDPObject & rObject,bool bAllowSource,const std::vector<OUString> * pDeletedNames)712 OUString ScDPDimensionSaveData::CreateGroupDimName(
713     const OUString& rSourceName, const ScDPObject& rObject, bool bAllowSource,
714     const std::vector<OUString>* pDeletedNames )
715 {
716     // create a name for the new dimension by appending a number to the original
717     // dimension's name
718 
719     bool bUseSource = bAllowSource;     // if set, try the unchanged original name first
720 
721     sal_Int32 nAdd = 2;                 // first try is "Name2"
722     const sal_Int32 nMaxAdd = 1000;     // limit the loop
723     while ( nAdd <= nMaxAdd )
724     {
725         OUString aDimName( rSourceName );
726         if ( !bUseSource )
727             aDimName += OUString::number(nAdd);
728 
729         // look for existing group dimensions
730         bool bExists = std::any_of(maGroupDims.begin(), maGroupDims.end(),
731             [&aDimName](const ScDPSaveGroupDimension& rDim) {
732                 return rDim.GetGroupDimName() == aDimName; //TODO: ignore case
733             });
734 
735         // look for base dimensions that happen to have that name
736         if ( !bExists && rObject.IsDimNameInUse( aDimName ) )
737         {
738             if ( pDeletedNames &&
739                  std::find( pDeletedNames->begin(), pDeletedNames->end(), aDimName ) != pDeletedNames->end() )
740             {
741                 // allow the name anyway if the name is in pDeletedNames
742             }
743             else
744                 bExists = true;
745         }
746 
747         if ( !bExists )
748             return aDimName;            // found a new name
749 
750         if ( bUseSource )
751             bUseSource = false;
752         else
753             ++nAdd;                     // continue with higher number
754     }
755     OSL_FAIL("CreateGroupDimName: no valid name found");
756     return OUString();
757 }
758 
759 namespace
760 {
761     const char* aDatePartIds[] =
762     {
763         STR_DPFIELD_GROUP_BY_SECONDS,
764         STR_DPFIELD_GROUP_BY_MINUTES,
765         STR_DPFIELD_GROUP_BY_HOURS,
766         STR_DPFIELD_GROUP_BY_DAYS,
767         STR_DPFIELD_GROUP_BY_MONTHS,
768         STR_DPFIELD_GROUP_BY_QUARTERS,
769         STR_DPFIELD_GROUP_BY_YEARS
770     };
771 }
772 
CreateDateGroupDimName(sal_Int32 nDatePart,const ScDPObject & rObject,bool bAllowSource,const std::vector<OUString> * pDeletedNames)773 OUString ScDPDimensionSaveData::CreateDateGroupDimName(
774     sal_Int32 nDatePart, const ScDPObject& rObject, bool bAllowSource,
775     const std::vector<OUString>* pDeletedNames )
776 {
777     using namespace css::sheet::DataPilotFieldGroupBy;
778     OUString aPartName;
779     switch( nDatePart )
780     {
781         case SECONDS:  aPartName = ScResId(aDatePartIds[0]); break;
782         case MINUTES:  aPartName = ScResId(aDatePartIds[1]); break;
783         case HOURS:    aPartName = ScResId(aDatePartIds[2]); break;
784         case DAYS:     aPartName = ScResId(aDatePartIds[3]); break;
785         case MONTHS:   aPartName = ScResId(aDatePartIds[4]); break;
786         case QUARTERS: aPartName = ScResId(aDatePartIds[5]); break;
787         case YEARS:    aPartName = ScResId(aDatePartIds[6]); break;
788     }
789     OSL_ENSURE(!aPartName.isEmpty(), "ScDPDimensionSaveData::CreateDateGroupDimName - invalid date part");
790     return CreateGroupDimName( aPartName, rObject, bAllowSource, pDeletedNames );
791 }
792 
793 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
794