Line data Source code
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 "globalnames.hxx"
14 : #include "globstr.hrc"
15 :
16 : #include <comphelper/string.hxx>
17 : #include <unotools/localedatawrapper.hxx>
18 : #include <unotools/calendarwrapper.hxx>
19 : #include <svl/zforlist.hxx>
20 : #include <rtl/math.hxx>
21 : #include <osl/diagnose.h>
22 :
23 : #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
24 : #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
25 :
26 : using namespace com::sun::star;
27 :
28 : namespace {
29 :
30 : const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
31 :
32 0 : OUString getTwoDigitString(sal_Int32 nValue)
33 : {
34 0 : OUString aRet = OUString::number( nValue );
35 0 : if ( aRet.getLength() < 2 )
36 0 : aRet = "0" + aRet;
37 0 : return aRet;
38 : }
39 :
40 10 : void appendDateStr(OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter)
41 : {
42 10 : sal_uLong nFormat = pFormatter->GetStandardFormat( css::util::NumberFormat::DATE, ScGlobal::eLnge );
43 10 : OUString aString;
44 10 : pFormatter->GetInputLineString(fValue, nFormat, aString);
45 10 : rBuffer.append(aString);
46 10 : }
47 :
48 10 : OUString getSpecialDateName(double fValue, bool bFirst, SvNumberFormatter* pFormatter)
49 : {
50 10 : OUStringBuffer aBuffer;
51 10 : aBuffer.append( bFirst ? '<' : '>' );
52 10 : appendDateStr(aBuffer, fValue, pFormatter);
53 10 : return aBuffer.makeStringAndClear();
54 : }
55 :
56 : }
57 :
58 162 : bool ScDPUtil::isDuplicateDimension(const OUString& rName)
59 : {
60 162 : return rName.endsWith("*");
61 : }
62 :
63 902 : OUString ScDPUtil::getSourceDimensionName(const OUString& rName)
64 : {
65 902 : return comphelper::string::stripEnd(rName, '*');
66 : }
67 :
68 4 : sal_uInt8 ScDPUtil::getDuplicateIndex(const OUString& rName)
69 : {
70 : // Count all trailing '*'s.
71 :
72 4 : sal_Int32 n = rName.getLength();
73 4 : if (!n)
74 0 : return 0;
75 :
76 4 : sal_uInt8 nDupCount = 0;
77 4 : const sal_Unicode* p = rName.getStr();
78 4 : const sal_Unicode* pStart = p;
79 4 : p += n-1; // Set it to the last char.
80 5 : for (; p != pStart; --p, ++nDupCount)
81 : {
82 5 : if (*p != '*')
83 4 : break;
84 : }
85 :
86 4 : return nDupCount;
87 : }
88 :
89 4 : OUString ScDPUtil::createDuplicateDimensionName(const OUString& rOriginal, size_t nDupCount)
90 : {
91 4 : if (!nDupCount)
92 0 : return rOriginal;
93 :
94 4 : OUStringBuffer aBuf(rOriginal);
95 8 : for (size_t i = 0; i < nDupCount; ++i)
96 4 : aBuf.append('*');
97 :
98 4 : return aBuf.makeStringAndClear();
99 : }
100 :
101 796 : OUString ScDPUtil::getDateGroupName(
102 : sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter,
103 : double fStart, double fEnd)
104 : {
105 796 : if (nValue == ScDPItemData::DateFirst)
106 5 : return getSpecialDateName(fStart, true, pFormatter);
107 791 : if (nValue == ScDPItemData::DateLast)
108 5 : return getSpecialDateName(fEnd, false, pFormatter);
109 :
110 786 : switch ( nDatePart )
111 : {
112 : case sheet::DataPilotFieldGroupBy::YEARS:
113 10 : return OUString::number(nValue);
114 : case sheet::DataPilotFieldGroupBy::QUARTERS:
115 12 : return ScGlobal::pLocaleData->getQuarterAbbreviation(sal_Int16(nValue-1)); // nValue is 1-based
116 : case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
117 : return ScGlobal::GetCalendar()->getDisplayName(
118 32 : i18n::CalendarDisplayIndex::MONTH, sal_Int16(nValue-1), 0); // 0-based, get short name
119 : case sheet::DataPilotFieldGroupBy::DAYS:
120 : {
121 732 : Date aDate(1, 1, SC_DP_LEAPYEAR);
122 732 : aDate += (nValue - 1); // nValue is 1-based
123 732 : Date aNullDate = *pFormatter->GetNullDate();
124 732 : long nDays = aDate - aNullDate;
125 :
126 732 : sal_uLong nFormat = pFormatter->GetFormatIndex(NF_DATE_SYS_DDMMM, ScGlobal::eLnge);
127 : Color* pColor;
128 732 : OUString aStr;
129 732 : pFormatter->GetOutputString(nDays, nFormat, aStr, &pColor);
130 732 : return aStr;
131 : }
132 : case sheet::DataPilotFieldGroupBy::HOURS:
133 : {
134 : //TODO: allow am/pm format?
135 0 : return getTwoDigitString(nValue);
136 : }
137 : break;
138 : case sheet::DataPilotFieldGroupBy::MINUTES:
139 : case sheet::DataPilotFieldGroupBy::SECONDS:
140 : {
141 0 : OUStringBuffer aBuf(ScGlobal::pLocaleData->getTimeSep());
142 0 : aBuf.append(getTwoDigitString(nValue));
143 0 : return aBuf.makeStringAndClear();
144 : }
145 : break;
146 : default:
147 : OSL_FAIL("invalid date part");
148 : }
149 :
150 0 : return OUString("FIXME: unhandled value");
151 : }
152 :
153 18 : double ScDPUtil::getNumGroupStartValue(double fValue, const ScDPNumGroupInfo& rInfo)
154 : {
155 18 : if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
156 : {
157 6 : rtl::math::setInf(&fValue, true);
158 6 : return fValue;
159 : }
160 :
161 12 : if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
162 : {
163 6 : rtl::math::setInf(&fValue, false);
164 6 : return fValue;
165 : }
166 :
167 6 : double fDiff = fValue - rInfo.mfStart;
168 6 : double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
169 6 : double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
170 :
171 6 : if (rtl::math::approxEqual(fGroupStart, rInfo.mfEnd) &&
172 0 : !rtl::math::approxEqual(fGroupStart, rInfo.mfStart))
173 : {
174 0 : 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 0 : fDiv -= 1.0;
182 0 : 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 0 : return rInfo.mfEnd + rInfo.mfStep;
189 : }
190 :
191 6 : return fGroupStart;
192 : }
193 :
194 : namespace {
195 :
196 0 : void lcl_AppendDateStr( OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter )
197 : {
198 0 : sal_uLong nFormat = pFormatter->GetStandardFormat( css::util::NumberFormat::DATE, ScGlobal::eLnge );
199 0 : OUString aString;
200 0 : pFormatter->GetInputLineString( fValue, nFormat, aString );
201 0 : rBuffer.append( aString );
202 0 : }
203 :
204 4 : 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 4 : OUStringBuffer aBuffer;
210 4 : aBuffer.append( bFirst ? '<' : '>' );
211 4 : if ( bDateValues )
212 0 : lcl_AppendDateStr( aBuffer, fValue, pFormatter );
213 : else
214 : rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic,
215 4 : rtl_math_DecimalPlaces_Max, cDecSeparator, true );
216 4 : return aBuffer.makeStringAndClear();
217 : }
218 :
219 6 : 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 6 : double fStep = rInfo.mfStep;
226 6 : double fEndValue = fStartValue + fStep;
227 6 : 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 4 : fEndValue -= 1.0;
236 : }
237 :
238 6 : if ( fEndValue > rInfo.mfEnd && !rInfo.mbAutoEnd )
239 : {
240 : // limit the last group to the end value
241 :
242 0 : fEndValue = rInfo.mfEnd;
243 : }
244 :
245 6 : OUStringBuffer aBuffer;
246 6 : if ( rInfo.mbDateValues )
247 : {
248 0 : lcl_AppendDateStr( aBuffer, fStartValue, pFormatter );
249 0 : aBuffer.appendAscii( " - " ); // with spaces
250 0 : lcl_AppendDateStr( aBuffer, fEndValue, pFormatter );
251 : }
252 : else
253 : {
254 : rtl::math::doubleToUStringBuffer( aBuffer, fStartValue, rtl_math_StringFormat_Automatic,
255 6 : rtl_math_DecimalPlaces_Max, cDecSep, true );
256 6 : aBuffer.append( '-' );
257 : rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic,
258 6 : rtl_math_DecimalPlaces_Max, cDecSep, true );
259 : }
260 :
261 6 : return aBuffer.makeStringAndClear();
262 : }
263 :
264 : }
265 :
266 10 : OUString ScDPUtil::getNumGroupName(
267 : double fValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep, SvNumberFormatter* pFormatter)
268 : {
269 10 : if ( fValue < rInfo.mfStart && !rtl::math::approxEqual( fValue, rInfo.mfStart ) )
270 2 : return lcl_GetSpecialNumGroupName( rInfo.mfStart, true, cDecSep, rInfo.mbDateValues, pFormatter );
271 :
272 8 : if ( fValue > rInfo.mfEnd && !rtl::math::approxEqual( fValue, rInfo.mfEnd ) )
273 2 : return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
274 :
275 6 : double fDiff = fValue - rInfo.mfStart;
276 6 : double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
277 6 : double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
278 :
279 6 : if ( rtl::math::approxEqual( fGroupStart, rInfo.mfEnd ) &&
280 0 : !rtl::math::approxEqual( fGroupStart, rInfo.mfStart ) )
281 : {
282 0 : 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 0 : return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
287 : }
288 : }
289 :
290 6 : return lcl_GetNumGroupName(fGroupStart, rInfo, cDecSep, pFormatter);
291 : }
292 :
293 54 : sal_Int32 ScDPUtil::getDatePartValue(
294 : double fValue, const ScDPNumGroupInfo* pInfo, sal_Int32 nDatePart,
295 : 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 54 : if (pInfo)
301 : {
302 48 : if (fValue < pInfo->mfStart && !rtl::math::approxEqual(fValue, pInfo->mfStart))
303 0 : return ScDPItemData::DateFirst;
304 48 : if (fValue > pInfo->mfEnd && !rtl::math::approxEqual(fValue, pInfo->mfEnd))
305 0 : return ScDPItemData::DateLast;
306 : }
307 :
308 54 : sal_Int32 nResult = 0;
309 :
310 54 : if (nDatePart == sheet::DataPilotFieldGroupBy::HOURS ||
311 54 : nDatePart == sheet::DataPilotFieldGroupBy::MINUTES ||
312 : nDatePart == sheet::DataPilotFieldGroupBy::SECONDS)
313 : {
314 : // handle time
315 : // (as in the cell functions, ScInterpreter::ScGetHour etc.: seconds are rounded)
316 :
317 0 : double fTime = fValue - rtl::math::approxFloor(fValue);
318 0 : long nSeconds = (long)rtl::math::approxFloor(fTime*DATE_TIME_FACTOR+0.5);
319 :
320 0 : switch (nDatePart)
321 : {
322 : case sheet::DataPilotFieldGroupBy::HOURS:
323 0 : nResult = nSeconds / 3600;
324 0 : break;
325 : case sheet::DataPilotFieldGroupBy::MINUTES:
326 0 : nResult = ( nSeconds % 3600 ) / 60;
327 0 : break;
328 : case sheet::DataPilotFieldGroupBy::SECONDS:
329 0 : nResult = nSeconds % 60;
330 0 : break;
331 0 : }
332 : }
333 : else
334 : {
335 54 : Date aDate = *(pFormatter->GetNullDate());
336 54 : aDate += (long)::rtl::math::approxFloor(fValue);
337 :
338 54 : switch ( nDatePart )
339 : {
340 : case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
341 22 : nResult = aDate.GetYear();
342 22 : break;
343 : case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS:
344 16 : nResult = 1 + (aDate.GetMonth() - 1) / 3; // 1..4
345 16 : break;
346 : case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
347 16 : nResult = aDate.GetMonth(); // 1..12
348 16 : break;
349 : case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS:
350 : {
351 0 : Date aYearStart(1, 1, aDate.GetYear());
352 0 : nResult = (aDate - aYearStart) + 1; // Jan 01 has value 1
353 0 : if (nResult >= 60 && !aDate.IsLeapYear())
354 : {
355 : // days are counted from 1 to 366 - if not from a leap year, adjust
356 0 : ++nResult;
357 : }
358 : }
359 0 : break;
360 : default:
361 : OSL_FAIL("invalid date part");
362 : }
363 : }
364 :
365 54 : return nResult;
366 : }
367 :
368 : namespace {
369 :
370 : sal_uInt16 nFuncStrIds[12] = {
371 : 0, // SUBTOTAL_FUNC_NONE
372 : STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE
373 : STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT
374 : STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2
375 : STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX
376 : STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN
377 : STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD
378 : STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD
379 : STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP
380 : STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM
381 : STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR
382 : STR_FUN_TEXT_VAR // SUBTOTAL_FUNC_VARP
383 : };
384 :
385 : }
386 :
387 149 : OUString ScDPUtil::getDisplayedMeasureName(const OUString& rName, ScSubTotalFunc eFunc)
388 : {
389 149 : OUStringBuffer aRet;
390 149 : sal_uInt16 nId = nFuncStrIds[eFunc];
391 149 : if (nId)
392 : {
393 138 : aRet.append(ScGlobal::GetRscString(nId)); // function name
394 138 : aRet.append(" - ");
395 : }
396 149 : aRet.append(rName); // field name
397 :
398 149 : return aRet.makeStringAndClear();
399 : }
400 :
401 148 : ScSubTotalFunc ScDPUtil::toSubTotalFunc(com::sun::star::sheet::GeneralFunction eGenFunc)
402 : {
403 : ScSubTotalFunc eSubTotal;
404 148 : switch (eGenFunc)
405 : {
406 0 : case sheet::GeneralFunction_NONE: eSubTotal = SUBTOTAL_FUNC_NONE; break;
407 136 : case sheet::GeneralFunction_SUM: eSubTotal = SUBTOTAL_FUNC_SUM; break;
408 11 : case sheet::GeneralFunction_COUNT: eSubTotal = SUBTOTAL_FUNC_CNT2; break;
409 1 : case sheet::GeneralFunction_AVERAGE: eSubTotal = SUBTOTAL_FUNC_AVE; break;
410 0 : case sheet::GeneralFunction_MAX: eSubTotal = SUBTOTAL_FUNC_MAX; break;
411 0 : case sheet::GeneralFunction_MIN: eSubTotal = SUBTOTAL_FUNC_MIN; break;
412 0 : case sheet::GeneralFunction_PRODUCT: eSubTotal = SUBTOTAL_FUNC_PROD; break;
413 0 : case sheet::GeneralFunction_COUNTNUMS: eSubTotal = SUBTOTAL_FUNC_CNT; break;
414 0 : case sheet::GeneralFunction_STDEV: eSubTotal = SUBTOTAL_FUNC_STD; break;
415 0 : case sheet::GeneralFunction_STDEVP: eSubTotal = SUBTOTAL_FUNC_STDP; break;
416 0 : case sheet::GeneralFunction_VAR: eSubTotal = SUBTOTAL_FUNC_VAR; break;
417 0 : case sheet::GeneralFunction_VARP: eSubTotal = SUBTOTAL_FUNC_VARP; break;
418 : case sheet::GeneralFunction_AUTO:
419 : default:
420 0 : eSubTotal = SUBTOTAL_FUNC_NONE;
421 : }
422 148 : return eSubTotal;
423 156 : }
424 :
425 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|