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
10 #include <dputil.hxx>
11 #include <dpitemdata.hxx>
12 #include <dpnumgroupinfo.hxx>
13 #include <globstr.hrc>
14 #include <scresid.hxx>
15 #include <generalfunction.hxx>
16
17 #include <comphelper/string.hxx>
18 #include <unotools/localedatawrapper.hxx>
19 #include <unotools/calendarwrapper.hxx>
20 #include <svl/zforlist.hxx>
21 #include <rtl/math.hxx>
22 #include <osl/diagnose.h>
23
24 #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
25 #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
26
27 using namespace com::sun::star;
28
29 namespace {
30
31 const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
32
getTwoDigitString(sal_Int32 nValue)33 OUString getTwoDigitString(sal_Int32 nValue)
34 {
35 OUString aRet = OUString::number( nValue );
36 if ( aRet.getLength() < 2 )
37 aRet = "0" + aRet;
38 return aRet;
39 }
40
appendDateStr(OUStringBuffer & rBuffer,double fValue,SvNumberFormatter * pFormatter)41 void appendDateStr(OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter)
42 {
43 sal_uInt32 nFormat = pFormatter->GetStandardFormat( SvNumFormatType::DATE, ScGlobal::eLnge );
44 OUString aString;
45 pFormatter->GetInputLineString(fValue, nFormat, aString);
46 rBuffer.append(aString);
47 }
48
getSpecialDateName(double fValue,bool bFirst,SvNumberFormatter * pFormatter)49 OUString getSpecialDateName(double fValue, bool bFirst, SvNumberFormatter* pFormatter)
50 {
51 OUStringBuffer aBuffer;
52 aBuffer.append( bFirst ? '<' : '>' );
53 appendDateStr(aBuffer, fValue, pFormatter);
54 return aBuffer.makeStringAndClear();
55 }
56
57 }
58
isDuplicateDimension(const OUString & rName)59 bool ScDPUtil::isDuplicateDimension(const OUString& rName)
60 {
61 return rName.endsWith("*");
62 }
63
getSourceDimensionName(std::u16string_view rName)64 OUString ScDPUtil::getSourceDimensionName(std::u16string_view rName)
65 {
66 return comphelper::string::stripEnd(rName, '*');
67 }
68
getDuplicateIndex(const OUString & rName)69 sal_uInt8 ScDPUtil::getDuplicateIndex(const OUString& rName)
70 {
71 // Count all trailing '*'s.
72
73 sal_Int32 n = rName.getLength();
74 if (!n)
75 return 0;
76
77 sal_uInt8 nDupCount = 0;
78 const sal_Unicode* p = rName.getStr();
79 const sal_Unicode* pStart = p;
80 p += n-1; // Set it to the last char.
81 for (; p != pStart; --p, ++nDupCount)
82 {
83 if (*p != '*')
84 break;
85 }
86
87 return nDupCount;
88 }
89
createDuplicateDimensionName(const OUString & rOriginal,size_t nDupCount)90 OUString ScDPUtil::createDuplicateDimensionName(const OUString& rOriginal, size_t nDupCount)
91 {
92 if (!nDupCount)
93 return rOriginal;
94
95 OUStringBuffer aBuf(rOriginal);
96 for (size_t i = 0; i < nDupCount; ++i)
97 aBuf.append('*');
98
99 return aBuf.makeStringAndClear();
100 }
101
getDateGroupName(sal_Int32 nDatePart,sal_Int32 nValue,SvNumberFormatter * pFormatter,double fStart,double fEnd)102 OUString ScDPUtil::getDateGroupName(
103 sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter,
104 double fStart, double fEnd)
105 {
106 if (nValue == ScDPItemData::DateFirst)
107 return getSpecialDateName(fStart, true, pFormatter);
108 if (nValue == ScDPItemData::DateLast)
109 return getSpecialDateName(fEnd, false, pFormatter);
110
111 switch ( nDatePart )
112 {
113 case sheet::DataPilotFieldGroupBy::YEARS:
114 return OUString::number(nValue);
115 case sheet::DataPilotFieldGroupBy::QUARTERS:
116 return ScGlobal::getLocaleDataPtr()->getQuarterAbbreviation(sal_Int16(nValue-1)); // nValue is 1-based
117 case css::sheet::DataPilotFieldGroupBy::MONTHS:
118 return ScGlobal::GetCalendar()->getDisplayName(
119 i18n::CalendarDisplayIndex::MONTH, sal_Int16(nValue-1), 0); // 0-based, get short name
120 case sheet::DataPilotFieldGroupBy::DAYS:
121 {
122 Date aDate(1, 1, SC_DP_LEAPYEAR);
123 aDate.AddDays(nValue - 1); // nValue is 1-based
124 tools::Long nDays = aDate - pFormatter->GetNullDate();
125
126 const sal_uInt32 nFormat = pFormatter->GetFormatIndex(NF_DATE_SYS_DDMMM, ScGlobal::eLnge);
127 const Color* pColor;
128 OUString aStr;
129 pFormatter->GetOutputString(nDays, nFormat, aStr, &pColor);
130 return aStr;
131 }
132 case sheet::DataPilotFieldGroupBy::HOURS:
133 {
134 //TODO: allow am/pm format?
135 return getTwoDigitString(nValue);
136 }
137 break;
138 case sheet::DataPilotFieldGroupBy::MINUTES:
139 case sheet::DataPilotFieldGroupBy::SECONDS:
140 {
141 OUStringBuffer aBuf(ScGlobal::getLocaleDataPtr()->getTimeSep());
142 aBuf.append(getTwoDigitString(nValue));
143 return aBuf.makeStringAndClear();
144 }
145 break;
146 default:
147 OSL_FAIL("invalid date part");
148 }
149
150 return "FIXME: unhandled value";
151 }
152
getNumGroupStartValue(double fValue,const ScDPNumGroupInfo & rInfo)153 double ScDPUtil::getNumGroupStartValue(double fValue, const ScDPNumGroupInfo& rInfo)
154 {
155 if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
156 {
157 rtl::math::setInf(&fValue, true);
158 return fValue;
159 }
160
161 if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
162 {
163 rtl::math::setInf(&fValue, false);
164 return fValue;
165 }
166
167 double fDiff = fValue - rInfo.mfStart;
168 double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
169 double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
170
171 if (rtl::math::approxEqual(fGroupStart, rInfo.mfEnd) &&
172 !rtl::math::approxEqual(fGroupStart, rInfo.mfStart))
173 {
174 if (!rInfo.mbDateValues)
175 {
176 // A group that would consist only of the end value is not
177 // created, instead the value is included in the last group
178 // before. So the previous group is used if the calculated group
179 // start value is the selected end value.
180
181 fDiv -= 1.0;
182 return rInfo.mfStart + fDiv * rInfo.mfStep;
183 }
184
185 // For date values, the end value is instead treated as above the
186 // limit if it would be a group of its own.
187
188 return rInfo.mfEnd + rInfo.mfStep;
189 }
190
191 return fGroupStart;
192 }
193
194 namespace {
195
lcl_AppendDateStr(OUStringBuffer & rBuffer,double fValue,SvNumberFormatter * pFormatter)196 void lcl_AppendDateStr( OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter )
197 {
198 sal_uInt32 nFormat = pFormatter->GetStandardFormat( SvNumFormatType::DATE, ScGlobal::eLnge );
199 OUString aString;
200 pFormatter->GetInputLineString( fValue, nFormat, aString );
201 rBuffer.append( aString );
202 }
203
lcl_GetSpecialNumGroupName(double fValue,bool bFirst,sal_Unicode cDecSeparator,bool bDateValues,SvNumberFormatter * pFormatter)204 OUString lcl_GetSpecialNumGroupName( double fValue, bool bFirst, sal_Unicode cDecSeparator,
205 bool bDateValues, SvNumberFormatter* pFormatter )
206 {
207 OSL_ENSURE( cDecSeparator != 0, "cDecSeparator not initialized" );
208
209 OUStringBuffer aBuffer;
210 aBuffer.append( bFirst ? '<' : '>' );
211 if ( bDateValues )
212 lcl_AppendDateStr( aBuffer, fValue, pFormatter );
213 else
214 rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic,
215 rtl_math_DecimalPlaces_Max, cDecSeparator, true );
216 return aBuffer.makeStringAndClear();
217 }
218
lcl_GetNumGroupName(double fStartValue,const ScDPNumGroupInfo & rInfo,sal_Unicode cDecSep,SvNumberFormatter * pFormatter)219 OUString lcl_GetNumGroupName(
220 double fStartValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep,
221 SvNumberFormatter* pFormatter)
222 {
223 OSL_ENSURE( cDecSep != 0, "cDecSeparator not initialized" );
224
225 double fStep = rInfo.mfStep;
226 double fEndValue = fStartValue + fStep;
227 if (rInfo.mbIntegerOnly && (rInfo.mbDateValues || !rtl::math::approxEqual(fEndValue, rInfo.mfEnd)))
228 {
229 // The second number of the group label is
230 // (first number + size - 1) if there are only integer numbers,
231 // (first number + size) if any non-integer numbers are involved.
232 // Exception: The last group (containing the end value) is always
233 // shown as including the end value (but not for dates).
234
235 fEndValue -= 1.0;
236 }
237
238 if ( fEndValue > rInfo.mfEnd && !rInfo.mbAutoEnd )
239 {
240 // limit the last group to the end value
241
242 fEndValue = rInfo.mfEnd;
243 }
244
245 OUStringBuffer aBuffer;
246 if ( rInfo.mbDateValues )
247 {
248 lcl_AppendDateStr( aBuffer, fStartValue, pFormatter );
249 aBuffer.append( " - " ); // with spaces
250 lcl_AppendDateStr( aBuffer, fEndValue, pFormatter );
251 }
252 else
253 {
254 rtl::math::doubleToUStringBuffer( aBuffer, fStartValue, rtl_math_StringFormat_Automatic,
255 rtl_math_DecimalPlaces_Max, cDecSep, true );
256 aBuffer.append( '-' );
257 rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic,
258 rtl_math_DecimalPlaces_Max, cDecSep, true );
259 }
260
261 return aBuffer.makeStringAndClear();
262 }
263
264 }
265
getNumGroupName(double fValue,const ScDPNumGroupInfo & rInfo,sal_Unicode cDecSep,SvNumberFormatter * pFormatter)266 OUString ScDPUtil::getNumGroupName(
267 double fValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep, SvNumberFormatter* pFormatter)
268 {
269 if ( fValue < rInfo.mfStart && !rtl::math::approxEqual( fValue, rInfo.mfStart ) )
270 return lcl_GetSpecialNumGroupName( rInfo.mfStart, true, cDecSep, rInfo.mbDateValues, pFormatter );
271
272 if ( fValue > rInfo.mfEnd && !rtl::math::approxEqual( fValue, rInfo.mfEnd ) )
273 return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
274
275 double fDiff = fValue - rInfo.mfStart;
276 double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
277 double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
278
279 if ( rtl::math::approxEqual( fGroupStart, rInfo.mfEnd ) &&
280 !rtl::math::approxEqual( fGroupStart, rInfo.mfStart ) )
281 {
282 if (rInfo.mbDateValues)
283 {
284 // For date values, the end value is instead treated as above the limit
285 // if it would be a group of its own.
286 return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
287 }
288 }
289
290 return lcl_GetNumGroupName(fGroupStart, rInfo, cDecSep, pFormatter);
291 }
292
getDatePartValue(double fValue,const ScDPNumGroupInfo * pInfo,sal_Int32 nDatePart,const SvNumberFormatter * pFormatter)293 sal_Int32 ScDPUtil::getDatePartValue(
294 double fValue, const ScDPNumGroupInfo* pInfo, sal_Int32 nDatePart,
295 const SvNumberFormatter* pFormatter)
296 {
297 // Start and end are inclusive
298 // (End date without a time value is included, with a time value it's not)
299
300 if (pInfo)
301 {
302 if (fValue < pInfo->mfStart && !rtl::math::approxEqual(fValue, pInfo->mfStart))
303 return ScDPItemData::DateFirst;
304 if (fValue > pInfo->mfEnd && !rtl::math::approxEqual(fValue, pInfo->mfEnd))
305 return ScDPItemData::DateLast;
306 }
307
308 sal_Int32 nResult = 0;
309
310 if (nDatePart == sheet::DataPilotFieldGroupBy::HOURS ||
311 nDatePart == sheet::DataPilotFieldGroupBy::MINUTES ||
312 nDatePart == sheet::DataPilotFieldGroupBy::SECONDS)
313 {
314 // handle time
315 // (do as in the cell functions, ScInterpreter::ScGetHour() etc.)
316
317 sal_uInt16 nHour, nMinute, nSecond;
318 double fFractionOfSecond;
319 tools::Time::GetClock( fValue, nHour, nMinute, nSecond, fFractionOfSecond, 0);
320
321 switch (nDatePart)
322 {
323 case sheet::DataPilotFieldGroupBy::HOURS:
324 nResult = nHour;
325 break;
326 case sheet::DataPilotFieldGroupBy::MINUTES:
327 nResult = nMinute;
328 break;
329 case sheet::DataPilotFieldGroupBy::SECONDS:
330 nResult = nSecond;
331 break;
332 }
333 }
334 else
335 {
336 Date aDate = pFormatter->GetNullDate();
337 aDate.AddDays(::rtl::math::approxFloor(fValue));
338
339 switch ( nDatePart )
340 {
341 case css::sheet::DataPilotFieldGroupBy::YEARS:
342 nResult = aDate.GetYear();
343 break;
344 case css::sheet::DataPilotFieldGroupBy::QUARTERS:
345 nResult = 1 + (aDate.GetMonth() - 1) / 3; // 1..4
346 break;
347 case css::sheet::DataPilotFieldGroupBy::MONTHS:
348 nResult = aDate.GetMonth(); // 1..12
349 break;
350 case css::sheet::DataPilotFieldGroupBy::DAYS:
351 {
352 Date aYearStart(1, 1, aDate.GetYear());
353 nResult = (aDate - aYearStart) + 1; // Jan 01 has value 1
354 if (nResult >= 60 && !aDate.IsLeapYear())
355 {
356 // days are counted from 1 to 366 - if not from a leap year, adjust
357 ++nResult;
358 }
359 }
360 break;
361 default:
362 OSL_FAIL("invalid date part");
363 }
364 }
365
366 return nResult;
367 }
368
369 namespace {
370
371 const char* aFuncStrIds[] = {
372 nullptr, // SUBTOTAL_FUNC_NONE
373 STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE
374 STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT
375 STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2
376 STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX
377 STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN
378 STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD
379 STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD
380 STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP
381 STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM
382 STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR
383 STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VARP
384 STR_FUN_TEXT_MEDIAN, // SUBTOTAL_FUNC_MED
385 nullptr // SUBTOTAL_FUNC_SELECTION_COUNT - not used for pivot table
386 };
387
388 }
389
getDisplayedMeasureName(const OUString & rName,ScSubTotalFunc eFunc)390 OUString ScDPUtil::getDisplayedMeasureName(const OUString& rName, ScSubTotalFunc eFunc)
391 {
392 assert(unsigned(eFunc) < SAL_N_ELEMENTS(aFuncStrIds));
393 const char* pId = aFuncStrIds[eFunc];
394 if (!pId)
395 return rName;
396 return ScResId(pId) + // function name
397 " - " +
398 rName; // field name
399 }
400
toSubTotalFunc(ScGeneralFunction eGenFunc)401 ScSubTotalFunc ScDPUtil::toSubTotalFunc(ScGeneralFunction eGenFunc)
402 {
403 ScSubTotalFunc eSubTotal = SUBTOTAL_FUNC_NONE;
404 switch (eGenFunc)
405 {
406 case ScGeneralFunction::NONE: eSubTotal = SUBTOTAL_FUNC_NONE; break;
407 case ScGeneralFunction::SUM: eSubTotal = SUBTOTAL_FUNC_SUM; break;
408 case ScGeneralFunction::COUNT: eSubTotal = SUBTOTAL_FUNC_CNT2; break;
409 case ScGeneralFunction::AVERAGE: eSubTotal = SUBTOTAL_FUNC_AVE; break;
410 case ScGeneralFunction::MEDIAN: eSubTotal = SUBTOTAL_FUNC_MED; break;
411 case ScGeneralFunction::MAX: eSubTotal = SUBTOTAL_FUNC_MAX; break;
412 case ScGeneralFunction::MIN: eSubTotal = SUBTOTAL_FUNC_MIN; break;
413 case ScGeneralFunction::PRODUCT: eSubTotal = SUBTOTAL_FUNC_PROD; break;
414 case ScGeneralFunction::COUNTNUMS: eSubTotal = SUBTOTAL_FUNC_CNT; break;
415 case ScGeneralFunction::STDEV: eSubTotal = SUBTOTAL_FUNC_STD; break;
416 case ScGeneralFunction::STDEVP: eSubTotal = SUBTOTAL_FUNC_STDP; break;
417 case ScGeneralFunction::VAR: eSubTotal = SUBTOTAL_FUNC_VAR; break;
418 case ScGeneralFunction::VARP: eSubTotal = SUBTOTAL_FUNC_VARP; break;
419 case ScGeneralFunction::AUTO: eSubTotal = SUBTOTAL_FUNC_NONE; break;
420 default:
421 assert(false);
422 }
423 return eSubTotal;
424 }
425
426 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
427