diff options
Diffstat (limited to 'sc/source/core/data/dputil.cxx')
-rw-r--r-- | sc/source/core/data/dputil.cxx | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/sc/source/core/data/dputil.cxx b/sc/source/core/data/dputil.cxx new file mode 100644 index 000000000..651d55093 --- /dev/null +++ b/sc/source/core/data/dputil.cxx @@ -0,0 +1,420 @@ +/* -*- 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 <dputil.hxx> +#include <dpitemdata.hxx> +#include <dpnumgroupinfo.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <generalfunction.hxx> + +#include <comphelper/string.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/calendarwrapper.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <rtl/math.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> + +#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp> +#include <com/sun/star/i18n/CalendarDisplayIndex.hpp> + +using namespace com::sun::star; + +namespace { + +const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations + +OUString getTwoDigitString(sal_Int32 nValue) +{ + OUString aRet = OUString::number( nValue ); + if ( aRet.getLength() < 2 ) + aRet = "0" + aRet; + return aRet; +} + +void appendDateStr(OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter) +{ + sal_uInt32 nFormat = pFormatter->GetStandardFormat( SvNumFormatType::DATE, ScGlobal::eLnge ); + OUString aString; + pFormatter->GetInputLineString(fValue, nFormat, aString); + rBuffer.append(aString); +} + +OUString getSpecialDateName(double fValue, bool bFirst, SvNumberFormatter* pFormatter) +{ + OUStringBuffer aBuffer; + aBuffer.append( bFirst ? '<' : '>' ); + appendDateStr(aBuffer, fValue, pFormatter); + return aBuffer.makeStringAndClear(); +} + +} + +bool ScDPUtil::isDuplicateDimension(std::u16string_view rName) +{ + return o3tl::ends_with(rName, u"*"); +} + +OUString ScDPUtil::getSourceDimensionName(std::u16string_view rName) +{ + return OUString(comphelper::string::stripEnd(rName, '*')); +} + +sal_uInt8 ScDPUtil::getDuplicateIndex(const OUString& rName) +{ + // Count all trailing '*'s. + + sal_Int32 n = rName.getLength(); + if (!n) + return 0; + + sal_uInt8 nDupCount = 0; + const sal_Unicode* p = rName.getStr(); + const sal_Unicode* pStart = p; + p += n-1; // Set it to the last char. + for (; p != pStart; --p, ++nDupCount) + { + if (*p != '*') + break; + } + + return nDupCount; +} + +OUString ScDPUtil::createDuplicateDimensionName(const OUString& rOriginal, size_t nDupCount) +{ + if (!nDupCount) + return rOriginal; + + OUStringBuffer aBuf(rOriginal); + for (size_t i = 0; i < nDupCount; ++i) + aBuf.append('*'); + + return aBuf.makeStringAndClear(); +} + +OUString ScDPUtil::getDateGroupName( + sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter, + double fStart, double fEnd) +{ + if (nValue == ScDPItemData::DateFirst) + return getSpecialDateName(fStart, true, pFormatter); + if (nValue == ScDPItemData::DateLast) + return getSpecialDateName(fEnd, false, pFormatter); + + switch ( nDatePart ) + { + case sheet::DataPilotFieldGroupBy::YEARS: + return OUString::number(nValue); + case sheet::DataPilotFieldGroupBy::QUARTERS: + return ScGlobal::getLocaleData().getQuarterAbbreviation(sal_Int16(nValue-1)); // nValue is 1-based + case css::sheet::DataPilotFieldGroupBy::MONTHS: + return ScGlobal::GetCalendar().getDisplayName( + i18n::CalendarDisplayIndex::MONTH, sal_Int16(nValue-1), 0); // 0-based, get short name + case sheet::DataPilotFieldGroupBy::DAYS: + { + Date aDate(1, 1, SC_DP_LEAPYEAR); + aDate.AddDays(nValue - 1); // nValue is 1-based + tools::Long nDays = aDate - pFormatter->GetNullDate(); + + const sal_uInt32 nFormat = pFormatter->GetFormatIndex(NF_DATE_SYS_DDMMM, ScGlobal::eLnge); + const Color* pColor; + OUString aStr; + pFormatter->GetOutputString(nDays, nFormat, aStr, &pColor); + return aStr; + } + case sheet::DataPilotFieldGroupBy::HOURS: + { + //TODO: allow am/pm format? + return getTwoDigitString(nValue); + } + break; + case sheet::DataPilotFieldGroupBy::MINUTES: + case sheet::DataPilotFieldGroupBy::SECONDS: + { + return ScGlobal::getLocaleData().getTimeSep() + getTwoDigitString(nValue); + } + break; + default: + OSL_FAIL("invalid date part"); + } + + return "FIXME: unhandled value"; +} + +double ScDPUtil::getNumGroupStartValue(double fValue, const ScDPNumGroupInfo& rInfo) +{ + if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart)) + return -std::numeric_limits<double>::infinity(); + + if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd)) + return std::numeric_limits<double>::infinity(); + + double fDiff = fValue - rInfo.mfStart; + double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep ); + double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep; + + if (rtl::math::approxEqual(fGroupStart, rInfo.mfEnd) && + !rtl::math::approxEqual(fGroupStart, rInfo.mfStart)) + { + if (!rInfo.mbDateValues) + { + // A group that would consist only of the end value is not + // created, instead the value is included in the last group + // before. So the previous group is used if the calculated group + // start value is the selected end value. + + fDiv -= 1.0; + return rInfo.mfStart + fDiv * rInfo.mfStep; + } + + // For date values, the end value is instead treated as above the + // limit if it would be a group of its own. + + return rInfo.mfEnd + rInfo.mfStep; + } + + return fGroupStart; +} + +namespace { + +void lcl_AppendDateStr( OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter ) +{ + sal_uInt32 nFormat = pFormatter->GetStandardFormat( SvNumFormatType::DATE, ScGlobal::eLnge ); + OUString aString; + pFormatter->GetInputLineString( fValue, nFormat, aString ); + rBuffer.append( aString ); +} + +OUString lcl_GetSpecialNumGroupName( double fValue, bool bFirst, sal_Unicode cDecSeparator, + bool bDateValues, SvNumberFormatter* pFormatter ) +{ + OSL_ENSURE( cDecSeparator != 0, "cDecSeparator not initialized" ); + + OUStringBuffer aBuffer; + aBuffer.append( bFirst ? '<' : '>' ); + if ( bDateValues ) + lcl_AppendDateStr( aBuffer, fValue, pFormatter ); + else + rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, cDecSeparator, true ); + return aBuffer.makeStringAndClear(); +} + +OUString lcl_GetNumGroupName( + double fStartValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep, + SvNumberFormatter* pFormatter) +{ + OSL_ENSURE( cDecSep != 0, "cDecSeparator not initialized" ); + + double fStep = rInfo.mfStep; + double fEndValue = fStartValue + fStep; + if (rInfo.mbIntegerOnly && (rInfo.mbDateValues || !rtl::math::approxEqual(fEndValue, rInfo.mfEnd))) + { + // The second number of the group label is + // (first number + size - 1) if there are only integer numbers, + // (first number + size) if any non-integer numbers are involved. + // Exception: The last group (containing the end value) is always + // shown as including the end value (but not for dates). + + fEndValue -= 1.0; + } + + if ( fEndValue > rInfo.mfEnd && !rInfo.mbAutoEnd ) + { + // limit the last group to the end value + + fEndValue = rInfo.mfEnd; + } + + OUStringBuffer aBuffer; + if ( rInfo.mbDateValues ) + { + lcl_AppendDateStr( aBuffer, fStartValue, pFormatter ); + aBuffer.append( " - " ); // with spaces + lcl_AppendDateStr( aBuffer, fEndValue, pFormatter ); + } + else + { + rtl::math::doubleToUStringBuffer( aBuffer, fStartValue, rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, cDecSep, true ); + aBuffer.append( '-' ); + rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, cDecSep, true ); + } + + return aBuffer.makeStringAndClear(); +} + +} + +OUString ScDPUtil::getNumGroupName( + double fValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep, SvNumberFormatter* pFormatter) +{ + if ( fValue < rInfo.mfStart && !rtl::math::approxEqual( fValue, rInfo.mfStart ) ) + return lcl_GetSpecialNumGroupName( rInfo.mfStart, true, cDecSep, rInfo.mbDateValues, pFormatter ); + + if ( fValue > rInfo.mfEnd && !rtl::math::approxEqual( fValue, rInfo.mfEnd ) ) + return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter ); + + double fDiff = fValue - rInfo.mfStart; + double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep ); + double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep; + + if ( rtl::math::approxEqual( fGroupStart, rInfo.mfEnd ) && + !rtl::math::approxEqual( fGroupStart, rInfo.mfStart ) ) + { + if (rInfo.mbDateValues) + { + // For date values, the end value is instead treated as above the limit + // if it would be a group of its own. + return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter ); + } + } + + return lcl_GetNumGroupName(fGroupStart, rInfo, cDecSep, pFormatter); +} + +sal_Int32 ScDPUtil::getDatePartValue( + double fValue, const ScDPNumGroupInfo* pInfo, sal_Int32 nDatePart, + const SvNumberFormatter* pFormatter) +{ + // Start and end are inclusive + // (End date without a time value is included, with a time value it's not) + + if (pInfo) + { + if (fValue < pInfo->mfStart && !rtl::math::approxEqual(fValue, pInfo->mfStart)) + return ScDPItemData::DateFirst; + if (fValue > pInfo->mfEnd && !rtl::math::approxEqual(fValue, pInfo->mfEnd)) + return ScDPItemData::DateLast; + } + + sal_Int32 nResult = 0; + + if (nDatePart == sheet::DataPilotFieldGroupBy::HOURS || + nDatePart == sheet::DataPilotFieldGroupBy::MINUTES || + nDatePart == sheet::DataPilotFieldGroupBy::SECONDS) + { + // handle time + // (do as in the cell functions, ScInterpreter::ScGetHour() etc.) + + sal_uInt16 nHour, nMinute, nSecond; + double fFractionOfSecond; + tools::Time::GetClock( fValue, nHour, nMinute, nSecond, fFractionOfSecond, 0); + + switch (nDatePart) + { + case sheet::DataPilotFieldGroupBy::HOURS: + nResult = nHour; + break; + case sheet::DataPilotFieldGroupBy::MINUTES: + nResult = nMinute; + break; + case sheet::DataPilotFieldGroupBy::SECONDS: + nResult = nSecond; + break; + } + } + else + { + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays(::rtl::math::approxFloor(fValue)); + + switch ( nDatePart ) + { + case css::sheet::DataPilotFieldGroupBy::YEARS: + nResult = aDate.GetYear(); + break; + case css::sheet::DataPilotFieldGroupBy::QUARTERS: + nResult = 1 + (aDate.GetMonth() - 1) / 3; // 1..4 + break; + case css::sheet::DataPilotFieldGroupBy::MONTHS: + nResult = aDate.GetMonth(); // 1..12 + break; + case css::sheet::DataPilotFieldGroupBy::DAYS: + { + Date aYearStart(1, 1, aDate.GetYear()); + nResult = (aDate - aYearStart) + 1; // Jan 01 has value 1 + if (nResult >= 60 && !aDate.IsLeapYear()) + { + // days are counted from 1 to 366 - if not from a leap year, adjust + ++nResult; + } + } + break; + default: + OSL_FAIL("invalid date part"); + } + } + + return nResult; +} + +namespace { + +const TranslateId aFuncStrIds[] = { + {}, // SUBTOTAL_FUNC_NONE + STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE + STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT + STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2 + STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX + STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN + STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD + STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD + STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP + STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM + STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR + STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VARP + STR_FUN_TEXT_MEDIAN, // SUBTOTAL_FUNC_MED + {} // SUBTOTAL_FUNC_SELECTION_COUNT - not used for pivot table +}; + +} + +OUString ScDPUtil::getDisplayedMeasureName(const OUString& rName, ScSubTotalFunc eFunc) +{ + assert(unsigned(eFunc) < SAL_N_ELEMENTS(aFuncStrIds)); + TranslateId pId = aFuncStrIds[eFunc]; + if (!pId) + return rName; + return ScResId(pId) + // function name + " - " + + rName; // field name +} + +ScSubTotalFunc ScDPUtil::toSubTotalFunc(ScGeneralFunction eGenFunc) +{ + ScSubTotalFunc eSubTotal = SUBTOTAL_FUNC_NONE; + switch (eGenFunc) + { + case ScGeneralFunction::NONE: eSubTotal = SUBTOTAL_FUNC_NONE; break; + case ScGeneralFunction::SUM: eSubTotal = SUBTOTAL_FUNC_SUM; break; + case ScGeneralFunction::COUNT: eSubTotal = SUBTOTAL_FUNC_CNT2; break; + case ScGeneralFunction::AVERAGE: eSubTotal = SUBTOTAL_FUNC_AVE; break; + case ScGeneralFunction::MEDIAN: eSubTotal = SUBTOTAL_FUNC_MED; break; + case ScGeneralFunction::MAX: eSubTotal = SUBTOTAL_FUNC_MAX; break; + case ScGeneralFunction::MIN: eSubTotal = SUBTOTAL_FUNC_MIN; break; + case ScGeneralFunction::PRODUCT: eSubTotal = SUBTOTAL_FUNC_PROD; break; + case ScGeneralFunction::COUNTNUMS: eSubTotal = SUBTOTAL_FUNC_CNT; break; + case ScGeneralFunction::STDEV: eSubTotal = SUBTOTAL_FUNC_STD; break; + case ScGeneralFunction::STDEVP: eSubTotal = SUBTOTAL_FUNC_STDP; break; + case ScGeneralFunction::VAR: eSubTotal = SUBTOTAL_FUNC_VAR; break; + case ScGeneralFunction::VARP: eSubTotal = SUBTOTAL_FUNC_VARP; break; + case ScGeneralFunction::AUTO: eSubTotal = SUBTOTAL_FUNC_NONE; break; + default: + assert(false); + } + return eSubTotal; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |