diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /svl/source/numbers/zformat.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svl/source/numbers/zformat.cxx')
-rw-r--r-- | svl/source/numbers/zformat.cxx | 6035 |
1 files changed, 6035 insertions, 0 deletions
diff --git a/svl/source/numbers/zformat.cxx b/svl/source/numbers/zformat.cxx new file mode 100644 index 000000000..0ddd6e084 --- /dev/null +++ b/svl/source/numbers/zformat.cxx @@ -0,0 +1,6035 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <stdio.h> +#include <string_view> + +#include <o3tl/string_view.hxx> +#include <comphelper/string.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <tools/long.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <rtl/math.hxx> +#include <unotools/charclass.hxx> +#include <unotools/calendarwrapper.hxx> +#include <unotools/nativenumberwrapper.hxx> +#include <com/sun/star/i18n/CalendarFieldIndex.hpp> +#include <com/sun/star/i18n/CalendarDisplayIndex.hpp> +#include <com/sun/star/i18n/CalendarDisplayCode.hpp> +#include <com/sun/star/i18n/AmPmValue.hpp> +#include <com/sun/star/i18n/NativeNumberMode.hpp> +#include <com/sun/star/i18n/NativeNumberXmlAttributes2.hpp> + +#include <svl/zformat.hxx> +#include "zforscan.hxx" + +#include "zforfind.hxx" +#include <svl/zforlist.hxx> +#include <unotools/digitgroupingiterator.hxx> +#include <svl/nfsymbol.hxx> + +#include <cmath> +#include <array> + +using namespace svt; + +namespace { + +constexpr OUStringLiteral GREGORIAN = u"gregorian"; + +const sal_uInt16 UPPER_PRECISION = 300; // entirely arbitrary... +const double EXP_LOWER_BOUND = 1.0E-4; // prefer scientific notation below this value. +const double EXP_ABS_UPPER_BOUND = 1.0E15; // use exponential notation above that absolute value. + // Back in time was E16 that lead + // to display rounding errors, see + // also sal/rtl/math.cxx + // doubleToString() + +} // namespace + +const double D_MAX_U_INT32 = double(0xffffffff); // 4294967295.0 +constexpr double D_MAX_INTEGER = (sal_uInt64(1) << 53) - 1; + +const double D_MAX_D_BY_100 = 1.7E306; +const double D_MIN_M_BY_1000 = 2.3E-305; + +const sal_uInt8 cCharWidths[ 128-32 ] = { + 1,1,1,2,2,3,2,1,1,1,1,2,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2, + 3,2,2,2,2,2,2,3,2,1,2,2,2,3,3,3, + 2,3,2,2,2,2,2,3,2,2,2,1,1,1,2,2, + 1,2,2,2,2,2,1,2,2,1,1,2,1,3,2,2, + 2,2,1,2,1,2,2,2,2,2,2,1,1,1,2,1 +}; + +// static +sal_Int32 SvNumberformat::InsertBlanks( OUStringBuffer& r, sal_Int32 nPos, sal_Unicode c ) +{ + if( c >= 32 ) + { + int n = 2; // Default for chars > 128 (HACK!) + if( c <= 127 ) + { + n = static_cast<int>(cCharWidths[ c - 32 ]); + } + while( n-- ) + { + r.insert( nPos++, ' '); + } + } + return nPos; +} + +static tools::Long GetPrecExp( double fAbsVal ) +{ + DBG_ASSERT( fAbsVal > 0.0, "GetPrecExp: fAbsVal <= 0.0" ); + if ( fAbsVal < 1e-7 || fAbsVal > 1e7 ) + { + // Shear: whether it's faster or not, falls in between 1e6 and 1e7 + return static_cast<tools::Long>(floor( log10( fAbsVal ) )) + 1; + } + else + { + tools::Long nPrecExp = 1; + while( fAbsVal < 1 ) + { + fAbsVal *= 10; + nPrecExp--; + } + while( fAbsVal >= 10 ) + { + fAbsVal /= 10; + nPrecExp++; + } + return nPrecExp; + } +} + +/** + * SvNumberformatInfo + * */ + +void ImpSvNumberformatInfo::Copy( const ImpSvNumberformatInfo& rNumFor, sal_uInt16 nCnt ) +{ + for (sal_uInt16 i = 0; i < nCnt; ++i) + { + sStrArray[i] = rNumFor.sStrArray[i]; + nTypeArray[i] = rNumFor.nTypeArray[i]; + } + eScannedType = rNumFor.eScannedType; + bThousand = rNumFor.bThousand; + nThousand = rNumFor.nThousand; + nCntPre = rNumFor.nCntPre; + nCntPost = rNumFor.nCntPost; + nCntExp = rNumFor.nCntExp; +} + +const std::map<LanguageType, std::array<sal_uInt8, 4>> tblDBNumToNatNum + = { { primary(LANGUAGE_CHINESE), { 4, 5, 3, 0 } }, + { primary(LANGUAGE_JAPANESE), { 4, 5, 3, 0 } }, + { primary(LANGUAGE_KOREAN), { 4, 5, 6, 10 } } }; + +// static +sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang, bool bDate ) +{ + sal_uInt8 nNatNum = 0; + eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc. + eLang = primary(eLang); // 10 bit primary language + if ( bDate ) + { + if ( nDBNum == 4 && eLang == primary(LANGUAGE_KOREAN) ) + { + nNatNum = 10; + } + else if ( nDBNum <= 3 ) + { + nNatNum = nDBNum; // known to be good for: zh,ja,ko / 1,2,3 + } + } + else + { + if (1 <= nDBNum && nDBNum <= 4) + { + auto const it = tblDBNumToNatNum.find(eLang); + if (it != tblDBNumToNatNum.end()) + nNatNum = it->second[nDBNum - 1]; + + } + } + return nNatNum; +} + +const std::map<LanguageType, std::array<sal_uInt8, 9>> tblNatNumToDBNum + = { { primary(LANGUAGE_CHINESE), { 1, 0, 0, 1, 2, 3, 0, 0, 0 } }, + { primary(LANGUAGE_JAPANESE), { 1, 2, 3, 1, 2, 3, 1, 2, 0 } }, + { primary(LANGUAGE_KOREAN), { 1, 2, 3, 1, 2, 3, 1, 2, 4 } } }; + +// static +sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLang, bool bDate ) +{ + sal_uInt8 nDBNum = 0; + eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc. + eLang = primary(eLang); // 10 bit primary language + if ( bDate ) + { + if ( nNatNum == 10 && eLang == primary(LANGUAGE_KOREAN) ) + { + nDBNum = 4; + } + else if ( nNatNum <= 3 ) + { + nDBNum = nNatNum; // known to be good for: zh,ja,ko / 1,2,3 + } + } + else + { + if (1 <= nNatNum && nNatNum <= 9) + { + auto const it = tblNatNumToDBNum.find(eLang); + if (it != tblNatNumToDBNum.end()) + nDBNum = it->second[nNatNum - 1]; + } + } + return nDBNum; +} + +/** + * SvNumFor + */ + +ImpSvNumFor::ImpSvNumFor() +{ + nStringsCnt = 0; + aI.eScannedType = SvNumFormatType::UNDEFINED; + aI.bThousand = false; + aI.nThousand = 0; + aI.nCntPre = 0; + aI.nCntPost = 0; + aI.nCntExp = 0; + pColor = nullptr; +} + +ImpSvNumFor::~ImpSvNumFor() +{ +} + +void ImpSvNumFor::Enlarge(sal_uInt16 nCnt) +{ + if ( nStringsCnt != nCnt ) + { + nStringsCnt = nCnt; + aI.nTypeArray.resize(nCnt); + aI.sStrArray.resize(nCnt); + } +} + +void ImpSvNumFor::Copy( const ImpSvNumFor& rNumFor, const ImpSvNumberformatScan* pSc ) +{ + Enlarge( rNumFor.nStringsCnt ); + aI.Copy( rNumFor.aI, nStringsCnt ); + sColorName = rNumFor.sColorName; + if ( pSc ) + { + pColor = pSc->GetColor( sColorName ); // #121103# don't copy pointer between documents + } + else + { + pColor = rNumFor.pColor; + } + aNatNum = rNumFor.aNatNum; +} + +bool ImpSvNumFor::HasNewCurrency() const +{ + for ( sal_uInt16 j=0; j<nStringsCnt; j++ ) + { + if ( aI.nTypeArray[j] == NF_SYMBOLTYPE_CURRENCY ) + { + return true; + } + } + return false; +} + +bool ImpSvNumFor::GetNewCurrencySymbol( OUString& rSymbol, + OUString& rExtension ) const +{ + for ( sal_uInt16 j=0; j<nStringsCnt; j++ ) + { + if ( aI.nTypeArray[j] == NF_SYMBOLTYPE_CURRENCY ) + { + rSymbol = aI.sStrArray[j]; + if ( j < nStringsCnt-1 && aI.nTypeArray[j+1] == NF_SYMBOLTYPE_CURREXT ) + { + rExtension = aI.sStrArray[j+1]; + } + else + { + rExtension.clear(); + } + return true; + } + } + //! No Erase at rSymbol, rExtension + return false; +} + +/** + * SvNumberformat + */ + +namespace { + +enum BracketFormatSymbolType +{ + BRACKET_SYMBOLTYPE_FORMAT = -1, // subformat string + BRACKET_SYMBOLTYPE_COLOR = -2, // color + BRACKET_SYMBOLTYPE_ERROR = -3, // error + BRACKET_SYMBOLTYPE_DBNUM1 = -4, // DoubleByteNumber, represent numbers + BRACKET_SYMBOLTYPE_DBNUM2 = -5, // using CJK characters, Excel compatible + BRACKET_SYMBOLTYPE_DBNUM3 = -6, + BRACKET_SYMBOLTYPE_DBNUM4 = -7, + BRACKET_SYMBOLTYPE_DBNUM5 = -8, + BRACKET_SYMBOLTYPE_DBNUM6 = -9, + BRACKET_SYMBOLTYPE_DBNUM7 = -10, + BRACKET_SYMBOLTYPE_DBNUM8 = -11, + BRACKET_SYMBOLTYPE_DBNUM9 = -12, + BRACKET_SYMBOLTYPE_LOCALE = -13, + BRACKET_SYMBOLTYPE_NATNUM0 = -14, // Our NativeNumber support, ASCII + BRACKET_SYMBOLTYPE_NATNUM1 = -15, // Our NativeNumber support, represent + BRACKET_SYMBOLTYPE_NATNUM2 = -16, // numbers using CJK, CTL, ... + BRACKET_SYMBOLTYPE_NATNUM3 = -17, + BRACKET_SYMBOLTYPE_NATNUM4 = -18, + BRACKET_SYMBOLTYPE_NATNUM5 = -19, + BRACKET_SYMBOLTYPE_NATNUM6 = -20, + BRACKET_SYMBOLTYPE_NATNUM7 = -21, + BRACKET_SYMBOLTYPE_NATNUM8 = -22, + BRACKET_SYMBOLTYPE_NATNUM9 = -23, + BRACKET_SYMBOLTYPE_NATNUM10 = -24, + BRACKET_SYMBOLTYPE_NATNUM11 = -25, + BRACKET_SYMBOLTYPE_NATNUM12 = -26, + BRACKET_SYMBOLTYPE_NATNUM13 = -27, + BRACKET_SYMBOLTYPE_NATNUM14 = -28, + BRACKET_SYMBOLTYPE_NATNUM15 = -29, + BRACKET_SYMBOLTYPE_NATNUM16 = -30, + BRACKET_SYMBOLTYPE_NATNUM17 = -31, + BRACKET_SYMBOLTYPE_NATNUM18 = -32, + BRACKET_SYMBOLTYPE_NATNUM19 = -33 +}; + +} + +void SvNumberformat::ImpCopyNumberformat( const SvNumberformat& rFormat ) +{ + sFormatstring = rFormat.sFormatstring; + eType = rFormat.eType; + maLocale = rFormat.maLocale; + fLimit1 = rFormat.fLimit1; + fLimit2 = rFormat.fLimit2; + eOp1 = rFormat.eOp1; + eOp2 = rFormat.eOp2; + bStandard = rFormat.bStandard; + bIsUsed = rFormat.bIsUsed; + sComment = rFormat.sComment; + bAdditionalBuiltin = rFormat.bAdditionalBuiltin; + + // #121103# when copying between documents, get color pointers from own scanner + ImpSvNumberformatScan* pColorSc = ( &rScan != &rFormat.rScan ) ? &rScan : nullptr; + + for (sal_uInt16 i = 0; i < 4; i++) + { + NumFor[i].Copy(rFormat.NumFor[i], pColorSc); + } +} + +SvNumberformat::SvNumberformat( SvNumberformat const & rFormat ) + : rScan(rFormat.rScan), bStarFlag( rFormat.bStarFlag ) +{ + ImpCopyNumberformat( rFormat ); +} + +SvNumberformat::SvNumberformat( SvNumberformat const & rFormat, ImpSvNumberformatScan& rSc ) + : rScan(rSc) + , bStarFlag( rFormat.bStarFlag ) +{ + ImpCopyNumberformat( rFormat ); +} + +static bool lcl_SvNumberformat_IsBracketedPrefix( short nSymbolType ) +{ + if ( nSymbolType > 0 ) + { + return true; // conditions + } + switch ( nSymbolType ) + { + case BRACKET_SYMBOLTYPE_COLOR : + case BRACKET_SYMBOLTYPE_DBNUM1 : + case BRACKET_SYMBOLTYPE_DBNUM2 : + case BRACKET_SYMBOLTYPE_DBNUM3 : + case BRACKET_SYMBOLTYPE_DBNUM4 : + case BRACKET_SYMBOLTYPE_DBNUM5 : + case BRACKET_SYMBOLTYPE_DBNUM6 : + case BRACKET_SYMBOLTYPE_DBNUM7 : + case BRACKET_SYMBOLTYPE_DBNUM8 : + case BRACKET_SYMBOLTYPE_DBNUM9 : + case BRACKET_SYMBOLTYPE_LOCALE : + case BRACKET_SYMBOLTYPE_NATNUM0 : + case BRACKET_SYMBOLTYPE_NATNUM1 : + case BRACKET_SYMBOLTYPE_NATNUM2 : + case BRACKET_SYMBOLTYPE_NATNUM3 : + case BRACKET_SYMBOLTYPE_NATNUM4 : + case BRACKET_SYMBOLTYPE_NATNUM5 : + case BRACKET_SYMBOLTYPE_NATNUM6 : + case BRACKET_SYMBOLTYPE_NATNUM7 : + case BRACKET_SYMBOLTYPE_NATNUM8 : + case BRACKET_SYMBOLTYPE_NATNUM9 : + case BRACKET_SYMBOLTYPE_NATNUM10 : + case BRACKET_SYMBOLTYPE_NATNUM11 : + case BRACKET_SYMBOLTYPE_NATNUM12 : + case BRACKET_SYMBOLTYPE_NATNUM13 : + case BRACKET_SYMBOLTYPE_NATNUM14 : + case BRACKET_SYMBOLTYPE_NATNUM15 : + case BRACKET_SYMBOLTYPE_NATNUM16 : + case BRACKET_SYMBOLTYPE_NATNUM17 : + case BRACKET_SYMBOLTYPE_NATNUM18 : + case BRACKET_SYMBOLTYPE_NATNUM19 : + return true; + } + return false; +} + +/** Import extended LCID from Excel + */ +OUString SvNumberformat::ImpObtainCalendarAndNumerals( OUStringBuffer& rString, sal_Int32 nPos, + LanguageType& nLang, const LocaleType& aTmpLocale ) +{ + OUString sCalendar; + sal_uInt16 nNatNum = 0; + LanguageType nLocaleLang = MsLangId::getRealLanguage( maLocale.meLanguage ); + LanguageType nTmpLocaleLang = MsLangId::getRealLanguage( aTmpLocale.meLanguage ); + /* NOTE: enhancement to allow other possible locale dependent + * calendars and numerals. BUT only if our locale data allows it! For LCID + * numerals and calendars see + * http://office.microsoft.com/en-us/excel/HA010346351033.aspx + * Calendar is inserted after + * all prefixes have been consumed as it is actually a format modifier + * and not a prefix. + * Currently calendars are tied to the locale of the entire number + * format, e.g. [~buddhist] in en_US doesn't work. + * => Having different locales in sub formats does not work! + * */ + /* TODO: calendars could be tied to a sub format's NatNum info + * instead, or even better be available for any locale. Needs a + * different implementation of GetCal() and locale data calendars. + * */ + switch ( aTmpLocale.mnCalendarType & 0x7F ) + { + case 0x03 : // Gengou calendar + // Only Japanese language support Gengou calendar. + // It is an implicit "other" calendar where E, EE, R and RR + // automatically switch to and YY and YYYY switch to Gregorian. Do + // not add the "[~gengou]" modifier. + if ( nLocaleLang != LANGUAGE_JAPANESE ) + { + nLang = maLocale.meLanguage = LANGUAGE_JAPANESE; + } + break; + case 0x05 : // Korean Dangi calendar + sCalendar = "[~dangi]"; + // Only Korean language support dangi calendar + if ( nLocaleLang != LANGUAGE_KOREAN ) + { + nLang = maLocale.meLanguage = LANGUAGE_KOREAN; + } + break; + case 0x06 : // Hijri calendar + case 0x17 : // same? + sCalendar = "[~hijri]"; + // Only Arabic or Farsi languages support Hijri calendar + if ( ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) + && nLocaleLang != LANGUAGE_FARSI ) + { + if ( ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY ) + || nTmpLocaleLang == LANGUAGE_FARSI ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_ARABIC_SAUDI_ARABIA; + } + } + break; + case 0x07 : // Buddhist calendar + sCalendar="[~buddhist]"; + // Only Thai or Lao languages support Buddhist calendar + if ( nLocaleLang != LANGUAGE_THAI && nLocaleLang != LANGUAGE_LAO ) + { + if ( nTmpLocaleLang == LANGUAGE_THAI || nTmpLocaleLang == LANGUAGE_LAO ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_THAI; + } + } + break; + case 0x08 : // Hebrew calendar + sCalendar = "[~jewish]"; + // Many languages (but not all) support Jewish calendar + // Unable to find any logic => keep same language + break; + case 0x0E : // unknown calendar + case 0x0F : // unknown calendar + case 0x10 : // Indian calendar (unsupported) + case 0x11 : // unknown calendar + case 0x12 : // unknown calendar + case 0x13 : // unknown calendar + default : // other calendars (see tdf#36038) are not handle by LibO + break; + } + /** Reference language for each numeral ID */ + static const LanguageType aNumeralIDtoLanguage []= + { + LANGUAGE_DONTKNOW, // 0x00 + LANGUAGE_ENGLISH_US, // 0x01 + LANGUAGE_ARABIC_SAUDI_ARABIA, // 0x02 + all Arabic + LANGUAGE_FARSI, // 0x03 + LANGUAGE_HINDI, // 0x04 + Devanagari + LANGUAGE_BENGALI, // 0x05 + LANGUAGE_PUNJABI, // 0x06 + LANGUAGE_GUJARATI, // 0x07 + LANGUAGE_ODIA, // 0x08 + LANGUAGE_TAMIL, // 0x09 + LANGUAGE_TELUGU, // 0x0A + LANGUAGE_KANNADA, // 0x0B + LANGUAGE_MALAYALAM, // 0x0C + LANGUAGE_THAI, // 0x0D + LANGUAGE_LAO, // 0x0E + LANGUAGE_TIBETAN, // 0x0F + LANGUAGE_BURMESE, // 0x10 + LANGUAGE_TIGRIGNA_ETHIOPIA, // 0x11 + LANGUAGE_KHMER, // 0x12 + LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA, // 0x13 + LANGUAGE_DONTKNOW, // 0x14 + LANGUAGE_DONTKNOW, // 0x15 + LANGUAGE_DONTKNOW, // 0x16 + LANGUAGE_DONTKNOW, // 0x17 + LANGUAGE_DONTKNOW, // 0x18 + LANGUAGE_DONTKNOW, // 0x19 + LANGUAGE_DONTKNOW, // 0x1A + LANGUAGE_JAPANESE, // 0x1B + LANGUAGE_JAPANESE, // 0x1C + LANGUAGE_JAPANESE, // 0x1D + LANGUAGE_CHINESE_SIMPLIFIED, // 0x1E + LANGUAGE_CHINESE_SIMPLIFIED, // 0x1F + LANGUAGE_CHINESE_SIMPLIFIED, // 0x20 + LANGUAGE_CHINESE_TRADITIONAL, // 0x21 + LANGUAGE_CHINESE_TRADITIONAL, // 0x22 + LANGUAGE_CHINESE_TRADITIONAL, // 0x23 + LANGUAGE_KOREAN, // 0x24 + LANGUAGE_KOREAN, // 0x25 + LANGUAGE_KOREAN, // 0x26 + LANGUAGE_KOREAN // 0x27 + }; + + sal_uInt16 nNumeralID = aTmpLocale.mnNumeralShape & 0x7F; + LanguageType nReferenceLanguage = nNumeralID <= 0x27 ? aNumeralIDtoLanguage[nNumeralID] : LANGUAGE_DONTKNOW; + + switch ( nNumeralID ) + { + // Regular cases: all languages with same primary mask use same numerals + case 0x03 : // Perso-Arabic (Farsi) numerals + case 0x05 : // Bengali numerals + case 0x06 : // Punjabi numerals + case 0x07 : // Gujarati numerals + case 0x08 : // Odia (Orya) numerals + case 0x09 : // Tamil numerals + case 0x0A : // Telugu numerals + case 0x0B : // Kannada numerals + case 0x0C : // Malayalam numerals + case 0x0D : // Thai numerals + case 0x0E : // Lao numerals + case 0x0F : // Tibetan numerals + case 0x10 : // Burmese (Myanmar) numerals + case 0x11 : // Tigrigna (Ethiopia) numerals + case 0x12 : // Khmer numerals + if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) ) + { + if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = nReferenceLanguage; + } + } + break; + // Special cases + case 0x04 : // Devanagari (Hindi) numerals + // same numerals (Devanagari) for languages with different primary masks + if ( nLocaleLang != LANGUAGE_HINDI && nLocaleLang != LANGUAGE_MARATHI + && primary( nLocaleLang ) != primary( LANGUAGE_NEPALI ) ) + { + if ( nTmpLocaleLang == LANGUAGE_HINDI || nTmpLocaleLang == LANGUAGE_MARATHI + || primary( nTmpLocaleLang ) == primary( LANGUAGE_NEPALI ) ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_HINDI; + } + } + break; + case 0x13 : // Mongolian numerals + // not all Mongolian languages use Mongolian numerals + if ( nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA + && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA + && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_LSO ) + { + if ( nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA + || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA + || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_LSO ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA; + } + } + break; + case 0x02 : // Eastern-Arabic numerals + // all arabic primary mask + LANGUAGE_PUNJABI_ARABIC_LSO + if ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY + && nLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO ) + { + if ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY + || nTmpLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = nReferenceLanguage; + } + } + break; + // CJK numerals + case 0x1B : // simple Asian numerals, Japanese + case 0x1C : // financial Asian numerals, Japanese + case 0x1D : // Arabic fullwidth numerals, Japanese + case 0x24 : // simple Asian numerals, Korean + case 0x25 : // financial Asian numerals, Korean + case 0x26 : // Arabic fullwidth numerals, Korean + case 0x27 : // Korean Hangul numerals + // Japanese and Korean are regular + if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) ) + { + if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = nReferenceLanguage; + } + } + [[fallthrough]]; + case 0x1E : // simple Asian numerals, Chinese-PRC + case 0x1F : // financial Asian numerals, Chinese-PRC + case 0x20 : // Arabic fullwidth numerals, Chinese-PRC + case 0x21 : // simple Asian numerals, Chinese-Taiwan + case 0x22 : // financial Asian numerals, Chinese-Taiwan + case 0x23 : // Arabic fullwidth numerals, Chinese-Taiwan + nNatNum = nNumeralID == 0x27 ? 9 : ( ( nNumeralID - 0x1B ) % 3 ) + 1; + // [NatNum1] simple numerals + // [natNum2] financial numerals + // [NatNum3] Arabic fullwidth numerals + // Chinese simplified and Chinese traditional have same primary mask + // Chinese-PRC + if ( nReferenceLanguage == LANGUAGE_CHINESE_SIMPLIFIED + && nLocaleLang != LANGUAGE_CHINESE_SIMPLIFIED + && nLocaleLang != LANGUAGE_CHINESE_SINGAPORE + && nLocaleLang != LANGUAGE_CHINESE_LSO ) + { + if ( nTmpLocaleLang == LANGUAGE_CHINESE_SIMPLIFIED + || nTmpLocaleLang == LANGUAGE_CHINESE_SINGAPORE + || nTmpLocaleLang == LANGUAGE_CHINESE_LSO ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_CHINESE_SIMPLIFIED; + } + } + // Chinese-Taiwan + else if ( nReferenceLanguage == LANGUAGE_CHINESE_TRADITIONAL + && nLocaleLang != LANGUAGE_CHINESE_TRADITIONAL + && nLocaleLang != LANGUAGE_CHINESE_HONGKONG + && nLocaleLang != LANGUAGE_CHINESE_MACAU ) + { + if ( nTmpLocaleLang == LANGUAGE_CHINESE_TRADITIONAL + || nTmpLocaleLang == LANGUAGE_CHINESE_HONGKONG + || nTmpLocaleLang == LANGUAGE_CHINESE_MACAU ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_CHINESE_TRADITIONAL; + } + } + break; + } + if ( nNumeralID >= 0x02 && nNumeralID <= 0x13 ) + nNatNum = 1; + if ( nNatNum ) + rString.insert( nPos, OUStringConcatenation("[NatNum"+OUString::number(nNatNum)+"]")); + return sCalendar; +} + +namespace +{ +bool NatNumTakesParameters(sal_Int16 nNum) +{ + return (nNum == css::i18n::NativeNumberMode::NATNUM12); +} +} + +SvNumberformat::SvNumberformat(OUString& rString, + ImpSvNumberformatScan* pSc, + ImpSvNumberInputScan* pISc, + sal_Int32& nCheckPos, + LanguageType& eLan, + bool bReplaceBooleanEquivalent) + : rScan(*pSc) + , bAdditionalBuiltin( false ) + , bStarFlag( false ) +{ + if (bReplaceBooleanEquivalent) + rScan.ReplaceBooleanEquivalent( rString); + + OUStringBuffer sBuff(rString); + + // If the group (AKA thousand) separator is a No-Break Space (French) + // replace all occurrences by a simple space. + // The same for Narrow No-Break Space just in case some locale uses it. + // The tokens will be changed to the LocaleData separator again later on. + const OUString& rThSep = GetFormatter().GetNumThousandSep(); + if ( rThSep.getLength() == 1) + { + const sal_Unicode cNBSp = 0xA0; + const sal_Unicode cNNBSp = 0x202F; + if (rThSep[0] == cNBSp ) + sBuff.replace( cNBSp, ' '); + else if (rThSep[0] == cNNBSp ) + sBuff.replace( cNNBSp, ' '); + } + + OUString aConvertFromDecSep; + OUString aConvertToDecSep; + if (rScan.GetConvertMode()) + { + aConvertFromDecSep = GetFormatter().GetNumDecimalSep(); + maLocale.meLanguage = rScan.GetNewLnge(); + eLan = maLocale.meLanguage; // Make sure to return switch + } + else + { + maLocale.meLanguage = eLan; + } + bStandard = false; + bIsUsed = false; + fLimit1 = 0.0; + fLimit2 = 0.0; + eOp1 = NUMBERFORMAT_OP_NO; + eOp2 = NUMBERFORMAT_OP_NO; + eType = SvNumFormatType::DEFINED; + + bool bCancel = false; + bool bCondition = false; + short eSymbolType; + sal_Int32 nPos = 0; + sal_Int32 nPosOld; + nCheckPos = 0; + + // Split into 4 sub formats + sal_uInt16 nIndex; + for ( nIndex = 0; nIndex < 4 && !bCancel; nIndex++ ) + { + // Original language/country may have to be reestablished + if (rScan.GetConvertMode()) + { + rScan.GetNumberformatter()->ChangeIntl(rScan.GetTmpLnge()); + } + OUString sInsertCalendar; // a calendar resulting from parsing LCID + OUString sStr; + nPosOld = nPos; // Start position of substring + // first get bracketed prefixes; e.g. conditions, color + do + { + eSymbolType = ImpNextSymbol(sBuff, nPos, sStr); + if (eSymbolType > 0) // condition + { + if ( nIndex == 0 && !bCondition ) + { + bCondition = true; + eOp1 = static_cast<SvNumberformatLimitOps>(eSymbolType); + } + else if ( nIndex == 1 && bCondition ) + { + eOp2 = static_cast<SvNumberformatLimitOps>(eSymbolType); + } + else // error + { + bCancel = true; // break for + nCheckPos = nPosOld; + } + if (!bCancel) + { + double fNumber; + sal_Int32 nCntChars = ImpGetNumber(sBuff, nPos, sStr); + if (nCntChars > 0) + { + sal_Int32 nDecPos; + SvNumFormatType F_Type = SvNumFormatType::UNDEFINED; + if (!pISc->IsNumberFormat(sStr, F_Type, fNumber, nullptr, SvNumInputOptions::NONE) || + ( F_Type != SvNumFormatType::NUMBER && + F_Type != SvNumFormatType::SCIENTIFIC) ) + { + fNumber = 0.0; + nPos = nPos - nCntChars; + sBuff.remove(nPos, nCntChars); + sBuff.insert(nPos, '0'); + nPos++; + } + else if (rScan.GetConvertMode() && ((nDecPos = sStr.indexOf( aConvertFromDecSep)) >= 0)) + { + if (aConvertToDecSep.isEmpty()) + aConvertToDecSep = GetFormatter().GetLangDecimalSep( rScan.GetNewLnge()); + if (aConvertToDecSep != aConvertFromDecSep) + { + const OUString aStr( sStr.replaceAt( nDecPos, + aConvertFromDecSep.getLength(), aConvertToDecSep)); + nPos = nPos - nCntChars; + sBuff.remove(nPos, nCntChars); + sBuff.insert(nPos, aStr); + nPos += aStr.getLength(); + } + } + } + else + { + fNumber = 0.0; + sBuff.insert(nPos++, '0'); + } + if (nIndex == 0) + { + fLimit1 = fNumber; + } + else + { + fLimit2 = fNumber; + } + if ( nPos < sBuff.getLength() && sBuff[nPos] == ']' ) + { + nPos++; + } + else + { + bCancel = true; // break for + nCheckPos = nPos; + } + } + nPosOld = nPos; // position before string + } + else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) ) + { + OUString sSymbol( sStr); + switch ( eSymbolType ) + { + case BRACKET_SYMBOLTYPE_COLOR : + if ( NumFor[nIndex].GetColor() != nullptr ) + { // error, more than one color + bCancel = true; // break for + nCheckPos = nPosOld; + } + else + { + const Color* pColor = pSc->GetColor( sStr); + NumFor[nIndex].SetColor( pColor, sStr); + if (pColor == nullptr) + { // error + bCancel = true; // break for + nCheckPos = nPosOld; + } + } + break; + case BRACKET_SYMBOLTYPE_NATNUM0 : + case BRACKET_SYMBOLTYPE_NATNUM1 : + case BRACKET_SYMBOLTYPE_NATNUM2 : + case BRACKET_SYMBOLTYPE_NATNUM3 : + case BRACKET_SYMBOLTYPE_NATNUM4 : + case BRACKET_SYMBOLTYPE_NATNUM5 : + case BRACKET_SYMBOLTYPE_NATNUM6 : + case BRACKET_SYMBOLTYPE_NATNUM7 : + case BRACKET_SYMBOLTYPE_NATNUM8 : + case BRACKET_SYMBOLTYPE_NATNUM9 : + case BRACKET_SYMBOLTYPE_NATNUM10 : + case BRACKET_SYMBOLTYPE_NATNUM11 : + case BRACKET_SYMBOLTYPE_NATNUM12 : + case BRACKET_SYMBOLTYPE_NATNUM13 : + case BRACKET_SYMBOLTYPE_NATNUM14 : + case BRACKET_SYMBOLTYPE_NATNUM15 : + case BRACKET_SYMBOLTYPE_NATNUM16 : + case BRACKET_SYMBOLTYPE_NATNUM17 : + case BRACKET_SYMBOLTYPE_NATNUM18 : + case BRACKET_SYMBOLTYPE_NATNUM19 : + if ( NumFor[nIndex].GetNatNum().IsSet() ) + { + bCancel = true; // break for + nCheckPos = nPosOld; + } + else + { + OUString sParams; + sal_Int32 nSpacePos = sStr.indexOf(' '); + if (nSpacePos >= 0) + { + sParams = o3tl::trim(sStr.subView(nSpacePos+1)); + } + //! eSymbolType is negative + sal_uInt8 nNum = static_cast<sal_uInt8>(0 - (eSymbolType - BRACKET_SYMBOLTYPE_NATNUM0)); + if (!sParams.isEmpty() && !NatNumTakesParameters(nNum)) + { + bCancel = true; // break for + nCheckPos = nPosOld; + break; + } + sStr = "NatNum" + OUString::number(nNum); + NumFor[nIndex].SetNatNumNum( nNum, false ); + // NatNum12 supports arguments + if (nNum == 12) + { + if (sParams.isEmpty()) + sParams = "cardinal"; // default NatNum12 format is "cardinal" + NumFor[nIndex].SetNatNumParams(sParams); + sStr += " " + sParams; + } + } + break; + case BRACKET_SYMBOLTYPE_DBNUM1 : + case BRACKET_SYMBOLTYPE_DBNUM2 : + case BRACKET_SYMBOLTYPE_DBNUM3 : + case BRACKET_SYMBOLTYPE_DBNUM4 : + case BRACKET_SYMBOLTYPE_DBNUM5 : + case BRACKET_SYMBOLTYPE_DBNUM6 : + case BRACKET_SYMBOLTYPE_DBNUM7 : + case BRACKET_SYMBOLTYPE_DBNUM8 : + case BRACKET_SYMBOLTYPE_DBNUM9 : + if ( NumFor[nIndex].GetNatNum().IsSet() ) + { + bCancel = true; // break for + nCheckPos = nPosOld; + } + else + { + //! eSymbolType is negative + sal_uInt8 nNum = static_cast<sal_uInt8>(1 - (eSymbolType - BRACKET_SYMBOLTYPE_DBNUM1)); + sStr = "DBNum" + OUStringChar(sal_Unicode('0' + nNum)); + NumFor[nIndex].SetNatNumNum( nNum, true ); + } + break; + case BRACKET_SYMBOLTYPE_LOCALE : + if ( NumFor[nIndex].GetNatNum().GetLang() != LANGUAGE_DONTKNOW || + sBuff[nPos-1] != ']' ) + // Check also for ']' to avoid pulling in + // locale data for the preview string for not + // yet completed LCIDs in the dialog. + { + bCancel = true; // break for + nCheckPos = nPosOld; + } + else + { + sal_Int32 nTmp = 2; + LocaleType aTmpLocale( ImpGetLocaleType( sStr, nTmp)); + if (aTmpLocale.meLanguage == LANGUAGE_DONTKNOW) + { + bCancel = true; // break for + nCheckPos = nPosOld; + } + else + { + // Only the first sub format's locale will be + // used as the format's overall locale. + // Sorts this also under the corresponding + // locale for the dialog. + // If we don't support the locale this would + // result in an unknown (empty) language + // listbox entry and the user would never see + // this format. + if (nIndex == 0 && (aTmpLocale.meLanguage == LANGUAGE_SYSTEM || + SvNumberFormatter::IsLocaleInstalled( aTmpLocale.meLanguage))) + { + maLocale = aTmpLocale; + eLan = aTmpLocale.meLanguage; // return to caller + + // Set new target locale also at scanner. + // We have to do this because switching locale + // may make replacing keywords and separators + // necessary. + // We can do this because it's the first + // subformat and we're still at parsing the + // modifiers, not keywords. + rScan.SetNewLnge( eLan); + // We can not force conversion though because + // the caller may have explicitly not set it. + // In the usual case the target locale is the + // originating locale the conversion is not + // necessary, when reading alien documents + // conversion is enabled anyway. + + /* TODO: fiddle with scanner to make this + * known? A change in the locale may affect + * separators and keywords. On the other + * hand they may have been entered as used + * in the originating locale, there's no + * way to predict other than analyzing the + * format code, we assume here the current + * context is used, which is most likely + * the case. + * */ + + // Strip a plain locale identifier if locale + // data is available to avoid duplicated + // formats with and without LCID for the same + // locale. Besides it looks ugly and confusing + // and is unnecessary as the format will be + // listed for the resulting locale. + if (aTmpLocale.isPlainLocale()) + sStr.clear(); + else + sStr = "$-" + aTmpLocale.generateCode(); + } + else + { + if (nIndex == 0) + // Locale data not available, remember. + maLocale.meLanguageWithoutLocaleData = aTmpLocale.meLanguage; + + sStr = "$-" + aTmpLocale.generateCode(); + } + NumFor[nIndex].SetNatNumLang( MsLangId::getRealLanguage( aTmpLocale.meLanguage)); + + // "$-NNCCLLLL" Numerals and Calendar + if (sSymbol.getLength() > 6) + { + sInsertCalendar = ImpObtainCalendarAndNumerals( sBuff, nPos, eLan, aTmpLocale); + } + /* NOTE: there can be only one calendar + * inserted so the last one wins, though + * our own calendar modifiers support + * multiple calendars within one sub format + * code if at different positions. */ + } + } + break; + } + if ( !bCancel ) + { + if (sStr == sSymbol) + { + nPosOld = nPos; + } + else + { + sBuff.remove(nPosOld, nPos - nPosOld); + if (!sStr.isEmpty()) + { + sBuff.insert(nPosOld, sStr); + nPos = nPosOld + sStr.getLength(); + sBuff.insert(nPos, "]"); + sBuff.insert(nPosOld, "["); + nPos += 2; + nPosOld = nPos; // position before string + } + else + { + nPos = nPosOld; // prefix removed for whatever reason + } + } + } + } + } + while ( !bCancel && lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) ); + + // The remaining format code string + if ( !bCancel ) + { + if (eSymbolType == BRACKET_SYMBOLTYPE_FORMAT) + { + if (nIndex == 1 && eOp1 == NUMBERFORMAT_OP_NO) + { + eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0 + } + else if (nIndex == 2 && eOp2 == NUMBERFORMAT_OP_NO) + { + eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0 + } + if (sStr.isEmpty()) + { + // Empty sub format. + NumFor[nIndex].Info().eScannedType = SvNumFormatType::EMPTY; + } + else + { + if (!sInsertCalendar.isEmpty()) + { + sStr = sInsertCalendar + sStr; + } + sal_Int32 nStrPos = pSc->ScanFormat( sStr); + sal_uInt16 nCnt = pSc->GetResultStringsCnt(); + if (nCnt == 0 && nStrPos == 0) // error + { + nStrPos = 1; + } + if (nStrPos == 0) // ok + { + // e.g. Thai T speciality + if (pSc->GetNatNumModifier() && !NumFor[nIndex].GetNatNum().IsSet()) + { + sStr = "[NatNum" + OUString::number( pSc->GetNatNumModifier()) + "]" + sStr; + NumFor[nIndex].SetNatNumNum( pSc->GetNatNumModifier(), false ); + } + // #i53826# #i42727# For the Thai T speciality we need + // to freeze the locale and immunize it against + // conversions during exports, just in case we want to + // save to Xcl. This disables the feature of being able + // to convert a NatNum to another locale. You can't + // have both. + // FIXME: implement a specialized export conversion + // that works on tokens (have to tokenize all first) + // and doesn't use the format string and + // PutandConvertEntry() to LANGUAGE_ENGLISH_US in + // sc/source/filter/excel/xestyle.cxx + // XclExpNumFmtBuffer::WriteFormatRecord(). + LanguageType eLanguage; + if (NumFor[nIndex].GetNatNum().GetNatNum() == 1 && + ((eLanguage = MsLangId::getRealLanguage( eLan)) == LANGUAGE_THAI) && + NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW) + { + sStr = "[$-" + OUString::number( sal_uInt16(eLanguage), 16 ).toAsciiUpperCase() + "]" + sStr; + NumFor[nIndex].SetNatNumLang( eLanguage); + } + sBuff.remove(nPosOld, nPos - nPosOld); + sBuff.insert(nPosOld, sStr); + nPos = nPosOld + sStr.getLength(); + if (nPos < sBuff.getLength()) + { + sBuff.insert(nPos, ";"); + nPos++; + } + else if (nIndex > 0) + { + // The last subformat. If it is a trailing text + // format the omitted subformats act like they were + // not specified and "inherited" the first format, + // e.g. 0;@ behaves like 0;-0;0;@ + if (pSc->GetScannedType() == SvNumFormatType::TEXT) + { + // Reset conditions, reverting any set above. + if (nIndex == 1) + eOp1 = NUMBERFORMAT_OP_NO; + else if (nIndex == 2) + eOp2 = NUMBERFORMAT_OP_NO; + nIndex = 3; + } + } + NumFor[nIndex].Enlarge(nCnt); + pSc->CopyInfo(&(NumFor[nIndex].Info()), nCnt); + // type check + if (nIndex == 0) + { + eType = NumFor[nIndex].Info().eScannedType; + } + else if (nIndex == 3) + { // #77026# Everything recognized IS text + NumFor[nIndex].Info().eScannedType = SvNumFormatType::TEXT; + } + else if ( NumFor[nIndex].Info().eScannedType != eType) + { + eType = SvNumFormatType::DEFINED; + } + } + else + { + nCheckPos = nPosOld + nStrPos; // error in string + bCancel = true; // break for + } + } + } + else if (eSymbolType == BRACKET_SYMBOLTYPE_ERROR) // error + { + nCheckPos = nPosOld; + bCancel = true; + } + else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) ) + { + nCheckPos = nPosOld + 1; // error, prefix in string + bCancel = true; // break for + } + } + if ( bCancel && !nCheckPos ) + { + nCheckPos = 1; // nCheckPos is used as an error condition + } + if ( !bCancel ) + { + if ( NumFor[nIndex].GetNatNum().IsSet() && + NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW ) + { + NumFor[nIndex].SetNatNumLang( eLan ); + } + } + if (sBuff.getLength() == nPos) + { + if (nIndex < 3 && rString[rString.getLength()-1] == ';') + { + // A trailing ';' is significant and specifies the following + // subformat to be empty. We don't enter the scanning loop + // above again though. + // Note that the operators apply to the current last scanned + // subformat. + if (nIndex == 0 && eOp1 == NUMBERFORMAT_OP_NO) + { + eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0 + } + else if (nIndex == 1 && eOp2 == NUMBERFORMAT_OP_NO) + { + eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0 + } + NumFor[nIndex+1].Info().eScannedType = SvNumFormatType::EMPTY; + if (sBuff[nPos-1] != ';') + sBuff.insert( nPos++, ';'); + } + if (nIndex == 2 && eSymbolType == BRACKET_SYMBOLTYPE_FORMAT && sBuff[nPos-1] == ';') + { + // #83510# A 4th subformat explicitly specified to be empty + // hides any text. Need the type here for HasTextFormat() + NumFor[3].Info().eScannedType = SvNumFormatType::TEXT; + } + bCancel = true; + } + if ( NumFor[nIndex].GetNatNum().IsSet() ) + { + NumFor[nIndex].SetNatNumDate( bool(NumFor[nIndex].Info().eScannedType & SvNumFormatType::DATE) ); + } + } + + if (!nCheckPos && IsSubstituted()) + { + // For to be substituted formats the scanned type must match the + // substitute type. + if (IsSystemTimeFormat()) + { + if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::TIME) + nCheckPos = std::max<sal_Int32>( sBuff.indexOf(']') + 1, 1); + } + else if (IsSystemLongDateFormat()) + { + if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::DATE) + nCheckPos = std::max<sal_Int32>( sBuff.indexOf(']') + 1, 1); + } + else + assert(!"unhandled substitute"); + } + + if ( bCondition && !nCheckPos ) + { + if ( nIndex == 1 && NumFor[0].GetCount() == 0 && + sBuff[sBuff.getLength() - 1] != ';' ) + { + // No format code => GENERAL but not if specified empty + OUString aAdd( pSc->GetStandardName() ); + if ( !pSc->ScanFormat( aAdd ) ) + { + sal_uInt16 nCnt = pSc->GetResultStringsCnt(); + if ( nCnt ) + { + NumFor[0].Enlarge(nCnt); + pSc->CopyInfo( &(NumFor[0].Info()), nCnt ); + sBuff.append(aAdd); + } + } + } + else if ( nIndex == 1 && NumFor[nIndex].GetCount() == 0 && + sBuff[sBuff.getLength() - 1] != ';' && + (NumFor[0].GetCount() > 1 || + (NumFor[0].GetCount() == 1 && + NumFor[0].Info().nTypeArray[0] != NF_KEY_GENERAL)) ) + { + // No trailing second subformat => GENERAL but not if specified empty + // and not if first subformat is GENERAL + OUString aAdd( pSc->GetStandardName() ); + if ( !pSc->ScanFormat( aAdd ) ) + { + sal_uInt16 nCnt = pSc->GetResultStringsCnt(); + if ( nCnt ) + { + NumFor[nIndex].Enlarge(nCnt); + pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt ); + sBuff.append(";"); + sBuff.append(aAdd); + } + } + } + else if ( nIndex == 2 && NumFor[nIndex].GetCount() == 0 && + sBuff[sBuff.getLength() - 1] != ';' && + eOp2 != NUMBERFORMAT_OP_NO ) + { + // No trailing third subformat => GENERAL but not if specified empty + OUString aAdd( pSc->GetStandardName() ); + if ( !pSc->ScanFormat( aAdd ) ) + { + sal_uInt16 nCnt = pSc->GetResultStringsCnt(); + if ( nCnt ) + { + NumFor[nIndex].Enlarge(nCnt); + pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt ); + sBuff.append(";"); + sBuff.append(aAdd); + } + } + } + } + rString = sBuff.makeStringAndClear(); + sFormatstring = rString; + + if (NumFor[2].GetCount() == 0 && // No third partial string + eOp1 == NUMBERFORMAT_OP_GT && eOp2 == NUMBERFORMAT_OP_NO && + fLimit1 == 0.0 && fLimit2 == 0.0) + { + eOp1 = NUMBERFORMAT_OP_GE; // Add 0 to the first format + } + +} + +SvNumberformat::~SvNumberformat() +{ +} + +/** + * Next_Symbol + * + * Splits up the symbols for further processing (by the Turing machine) + * + * Start state = SsStart, * = Special state + * ---------------+-------------------+----------------------------+--------------- + * Old State | Symbol read | Event | New state + * ---------------+-------------------+----------------------------+--------------- + * SsStart | " | Symbol += Character | SsGetQuoted + * | ; | Pos-- | SsGetString + * | [ | Symbol += Character | SsGetBracketed + * | ] | Error | SsStop + * | BLANK | | + * | Else | Symbol += Character | SsGetString + * ---------------+-------------------+----------------------------+--------------- + * SsGetString | " | Symbol += Character | SsGetQuoted + * | ; | | SsStop + * | Else | Symbol += Character | + * ---------------+-------------------+----------------------------+--------------- + * SsGetQuoted | " | Symbol += Character | SsGetString + * | Else | Symbol += Character | + * ---------------+-------------------+----------------------------+--------------- + * SsGetBracketed | <, > = | del [ | + * | | Symbol += Character | SsGetCon + * | BLANK | | + * | h, H, m, M, s, S | Symbol += Character | SsGetTime + * | Else | del [ | + * | | Symbol += Character | SsGetPrefix + * ---------------+-------------------+----------------------------+--------------- + * SsGetTime | ] | Symbol += Character | SsGetString + * | h, H, m, M, s, S | Symbol += Character, * | SsGetString + * | Else | del [; Symbol += Character | SsGetPrefix + * ---------------+-------------------+----------------------------+--------------- + * SsGetPrefix | ] | | SsStop + * | Else | Symbol += Character | + * ---------------+-------------------+----------------------------+--------------- + * SsGetCon | >, = | Symbol += Character | + * | ] | | SsStop + * | Else | Error | SsStop + * ---------------+-------------------+----------------------------+--------------- + */ + +namespace { + +enum ScanState +{ + SsStop, + SsStart, + SsGetCon, // condition + SsGetString, // format string + SsGetPrefix, // color or NatNumN + SsGetTime, // [HH] for time + SsGetBracketed, // any [...] not decided yet + SsGetQuoted // quoted text +}; + +} + +// read a string until ']' and delete spaces in input +// static +sal_Int32 SvNumberformat::ImpGetNumber(OUStringBuffer& rString, + sal_Int32& nPos, + OUString& sSymbol) +{ + sal_Int32 nStartPos = nPos; + sal_Unicode cToken; + sal_Int32 nLen = rString.getLength(); + OUStringBuffer sBuffSymbol; + while ( nPos < nLen ) + { + cToken = rString[nPos]; + if (cToken == ']') + break; + if (cToken == ' ') + { // delete spaces + rString.remove(nPos,1); + nLen--; + } + else + { + nPos++; + sBuffSymbol.append(cToken); + } + } + sSymbol = sBuffSymbol.makeStringAndClear(); + return nPos - nStartPos; +} + +namespace { + +sal_Unicode toUniChar(sal_uInt8 n) +{ + char c; + if (n < 10) + { + c = '0' + n; + } + else + { + c = 'A' + n - 10; + } + return sal_Unicode(c); +} + +bool IsCombiningSymbol( OUStringBuffer& rStringBuffer, sal_Int32 nPos ) +{ + bool bRet = false; + while (nPos >= 0) + { + switch (rStringBuffer[nPos]) + { + case '*': + case '\\': + case '_': + bRet = !bRet; + --nPos; + break; + default: + return bRet; + } + } + return bRet; +} + +} // namespace + +OUString SvNumberformat::LocaleType::generateCode() const +{ + OUStringBuffer aBuf; +#if 0 + // TODO: We may re-enable this later. Don't remove it! --Kohei + if (mnNumeralShape) + { + sal_uInt8 nVal = mnNumeralShape; + for (sal_uInt8 i = 0; i < 2; ++i) + { + sal_uInt8 n = (nVal & 0xF0) >> 4; + if (n || aBuf.getLength()) + { + aBuf.append(toUniChar(n)); + } + nVal = nVal << 4; + } + } + + if (mnNumeralShape || mnCalendarType) + { + sal_uInt8 nVal = mnCalendarType; + for (sal_uInt8 i = 0; i < 2; ++i) + { + sal_uInt8 n = (nVal & 0xF0) >> 4; + if (n || aBuf.getLength()) + { + aBuf.append(toUniChar(n)); + } + nVal = nVal << 4; + } + } +#endif + + sal_uInt16 n16 = static_cast<sal_uInt16>( + (meLanguageWithoutLocaleData == LANGUAGE_DONTKNOW) ? meLanguage : + meLanguageWithoutLocaleData); + if (meLanguage == LANGUAGE_SYSTEM) + { + switch (meSubstitute) + { + case Substitute::NONE: + ; // nothing + break; + case Substitute::TIME: + n16 = static_cast<sal_uInt16>(LANGUAGE_NF_SYSTEM_TIME); + break; + case Substitute::LONGDATE: + n16 = static_cast<sal_uInt16>(LANGUAGE_NF_SYSTEM_DATE); + break; + } + } + for (sal_uInt8 i = 0; i < 4; ++i) + { + sal_uInt8 n = static_cast<sal_uInt8>((n16 & 0xF000) >> 12); + // Omit leading zeros for consistency. + if (n || !aBuf.isEmpty() || i == 3) + { + aBuf.append(toUniChar(n)); + } + n16 = n16 << 4; + } + + return aBuf.makeStringAndClear(); +} + +SvNumberformat::LocaleType::LocaleType() + : meLanguage(LANGUAGE_DONTKNOW) + , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW) + , meSubstitute(Substitute::NONE) + , mnNumeralShape(0) + , mnCalendarType(0) +{ +} + +SvNumberformat::LocaleType::LocaleType(sal_uInt32 nRawNum) + : meLanguage(LANGUAGE_DONTKNOW) + , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW) + , meSubstitute(Substitute::NONE) + , mnNumeralShape(0) + , mnCalendarType(0) +{ + meLanguage = static_cast<LanguageType>(nRawNum & 0x0000FFFF); + if (meLanguage == LANGUAGE_NF_SYSTEM_TIME) + { + meSubstitute = Substitute::TIME; + meLanguage = LANGUAGE_SYSTEM; + } + else if (meLanguage == LANGUAGE_NF_SYSTEM_DATE) + { + meSubstitute = Substitute::LONGDATE; + meLanguage = LANGUAGE_SYSTEM; + } + nRawNum = (nRawNum >> 16); + mnCalendarType = static_cast<sal_uInt8>(nRawNum & 0xFF); + nRawNum = (nRawNum >> 8); + mnNumeralShape = static_cast<sal_uInt8>(nRawNum & 0xFF); +} + +bool SvNumberformat::LocaleType::isPlainLocale() const +{ + return meSubstitute == Substitute::NONE && !mnCalendarType && !mnNumeralShape; +} + +// static +SvNumberformat::LocaleType SvNumberformat::ImpGetLocaleType(const OUString& rString, sal_Int32& nPos ) +{ + sal_uInt32 nNum = 0; + sal_Unicode cToken = 0; + sal_Int32 nStart = nPos; + sal_Int32 nLen = rString.getLength(); + while ( nPos < nLen && (nPos - nStart < 8) ) + { + cToken = rString[nPos]; + if (cToken == ']') + break; + if ( '0' <= cToken && cToken <= '9' ) + { + nNum *= 16; + nNum += cToken - '0'; + } + else if ( 'a' <= cToken && cToken <= 'f' ) + { + nNum *= 16; + nNum += cToken - 'a' + 10; + } + else if ( 'A' <= cToken && cToken <= 'F' ) + { + nNum *= 16; + nNum += cToken - 'A' + 10; + } + else + { + return LocaleType(); // LANGUAGE_DONTKNOW; + } + ++nPos; + } + + return (cToken == ']' || nPos == nLen) ? LocaleType(nNum) : LocaleType(); +} + +static bool lcl_matchKeywordAndGetNumber( const OUString & rString, const sal_Int32 nPos, + const OUString & rKeyword, sal_Int32 & nNumber ) +{ + if (0 <= nPos && nPos + rKeyword.getLength() < rString.getLength() && rString.matchIgnoreAsciiCase( rKeyword, nPos)) + { + nNumber = o3tl::toInt32(rString.subView( nPos + rKeyword.getLength())); + return true; + } + else + { + nNumber = 0; + return false; + } +} + +short SvNumberformat::ImpNextSymbol(OUStringBuffer& rString, + sal_Int32& nPos, + OUString& sSymbol) const +{ + short eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + sal_Unicode cToken; + sal_Unicode cLetter = ' '; // Preliminary result + sal_Int32 nLen = rString.getLength(); + ScanState eState = SsStart; + OUStringBuffer sBuffSymbol(128); + + const NfKeywordTable & rKeywords = rScan.GetKeywords(); + while (nPos < nLen && eState != SsStop) + { + cToken = rString[nPos]; + nPos++; + switch (eState) + { + case SsStart: + if (cToken == '\"') + { + eState = SsGetQuoted; + sBuffSymbol.append(cToken); + } + else if (cToken == '[') + { + eState = SsGetBracketed; + sBuffSymbol.append(cToken); + } + else if (cToken == ';') + { + eState = SsGetString; + nPos--; + eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + } + else if (cToken == ']') + { + eState = SsStop; + eSymbolType = BRACKET_SYMBOLTYPE_ERROR; + } + else if (cToken == ' ') // Skip Blanks + { + nPos--; + rString.remove(nPos, 1); + nLen--; + } + else + { + sBuffSymbol.append(cToken); + eState = SsGetString; + eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + } + break; + case SsGetBracketed: + switch (cToken) + { + case '<': + case '>': + case '=': + sBuffSymbol.stripStart('['); + sBuffSymbol.append(cToken); + cLetter = cToken; + eState = SsGetCon; + switch (cToken) + { + case '<': + eSymbolType = NUMBERFORMAT_OP_LT; + break; + case '>': + eSymbolType = NUMBERFORMAT_OP_GT; + break; + case '=': + eSymbolType = NUMBERFORMAT_OP_EQ; + break; + } + break; + case ' ': + nPos--; + rString.remove(nPos, 1); + nLen--; + break; + case '$' : + if ( nPos < nLen && rString[nPos] == '-' ) + { + // [$-xxx] locale + sBuffSymbol.stripStart('['); + eSymbolType = BRACKET_SYMBOLTYPE_LOCALE; + eState = SsGetPrefix; + } + else + { // currency + eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + eState = SsGetString; + } + sBuffSymbol.append(cToken); + break; + case '~' : + // calendarID + eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + sBuffSymbol.append(cToken); + eState = SsGetString; + break; + default: + { + static const OUStringLiteral aNatNum(u"NATNUM"); + static const OUStringLiteral aDBNum(u"DBNUM"); + const OUString aBufStr( rString.toString()); + sal_Int32 nNatNumNum; + sal_Int32 nDBNum; + if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aNatNum, nNatNumNum) && + 0 <= nNatNumNum && nNatNumNum <= 19 ) + { + sBuffSymbol.stripStart('['); + sBuffSymbol.append( aBufStr.subView(--nPos, aNatNum.getLength()+1) ); + nPos += aNatNum.getLength()+1; + //! SymbolType is negative + eSymbolType = static_cast<short>(BRACKET_SYMBOLTYPE_NATNUM0 - nNatNumNum); + eState = SsGetPrefix; + } + else if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aDBNum, nDBNum) && + 1 <= nDBNum && nDBNum <= 9 ) + { + sBuffSymbol.stripStart('['); + sBuffSymbol.append( aBufStr.subView(--nPos, aDBNum.getLength()+1) ); + nPos += aDBNum.getLength()+1; + //! SymbolType is negative + eSymbolType = sal::static_int_cast< short >( BRACKET_SYMBOLTYPE_DBNUM1 - (nDBNum - 1) ); + eState = SsGetPrefix; + } + else + { + sal_Unicode cUpper = rChrCls().uppercase( aBufStr, nPos-1, 1)[0]; + if ( cUpper == rKeywords[NF_KEY_H][0] || // H + cUpper == rKeywords[NF_KEY_MI][0] || // M + cUpper == rKeywords[NF_KEY_S][0] ) // S + { + sBuffSymbol.append(cToken); + eState = SsGetTime; + cLetter = cToken; + } + else + { + sBuffSymbol.stripStart('['); + sBuffSymbol.append(cToken); + eSymbolType = BRACKET_SYMBOLTYPE_COLOR; + eState = SsGetPrefix; + } + } + } + } + break; + case SsGetString: + if (cToken == '\"') + { + eState = SsGetQuoted; + sBuffSymbol.append(cToken); + } + else if (cToken == ';' && (nPos < 2 || !IsCombiningSymbol( rString, nPos-2))) + { + eState = SsStop; + } + else + { + sBuffSymbol.append(cToken); + } + break; + case SsGetQuoted: + if (cToken == '\"') + { + eState = SsGetString; + sBuffSymbol.append(cToken); + } + else + { + sBuffSymbol.append(cToken); + } + break; + case SsGetTime: + if (cToken == ']') + { + sBuffSymbol.append(cToken); + eState = SsGetString; + eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + } + else + { + sal_Unicode cUpper = rChrCls().uppercase(rString.toString(), nPos-1, 1)[0]; + if (cUpper == rKeywords[NF_KEY_H][0] || // H + cUpper == rKeywords[NF_KEY_MI][0] || // M + cUpper == rKeywords[NF_KEY_S][0] ) // S + { + if (cLetter == cToken) + { + sBuffSymbol.append(cToken); + cLetter = ' '; + } + else + { + sBuffSymbol.stripStart('['); + sBuffSymbol.append(cToken); + eState = SsGetPrefix; + } + } + else + { + sBuffSymbol.stripStart('['); + sBuffSymbol.append(cToken); + eSymbolType = BRACKET_SYMBOLTYPE_COLOR; + eState = SsGetPrefix; + } + } + break; + case SsGetCon: + switch (cToken) + { + case '<': + eState = SsStop; + eSymbolType = BRACKET_SYMBOLTYPE_ERROR; + break; + case '>': + if (cLetter == '<') + { + sBuffSymbol.append(cToken); + cLetter = ' '; + eState = SsStop; + eSymbolType = NUMBERFORMAT_OP_NE; + } + else + { + eState = SsStop; + eSymbolType = BRACKET_SYMBOLTYPE_ERROR; + } + break; + case '=': + if (cLetter == '<') + { + sBuffSymbol.append(cToken); + cLetter = ' '; + eSymbolType = NUMBERFORMAT_OP_LE; + } + else if (cLetter == '>') + { + sBuffSymbol.append(cToken); + cLetter = ' '; + eSymbolType = NUMBERFORMAT_OP_GE; + } + else + { + eState = SsStop; + eSymbolType = BRACKET_SYMBOLTYPE_ERROR; + } + break; + case ' ': + nPos--; + rString.remove(nPos,1); + nLen--; + break; + default: + eState = SsStop; + nPos--; + break; + } + break; + case SsGetPrefix: + if (cToken == ']') + { + eState = SsStop; + } + else + { + sBuffSymbol.append(cToken); + } + break; + default: + break; + } // of switch + } // of while + sSymbol = sBuffSymbol.makeStringAndClear(); + return eSymbolType; +} + +void SvNumberformat::ConvertLanguage( SvNumberFormatter& rConverter, + LanguageType eConvertFrom, + LanguageType eConvertTo ) +{ + sal_Int32 nCheckPos; + sal_uInt32 nKey; + SvNumFormatType nType = eType; + OUString aFormatString( sFormatstring ); + rConverter.PutandConvertEntry( aFormatString, nCheckPos, nType, + nKey, eConvertFrom, eConvertTo, false); + const SvNumberformat* pFormat = rConverter.GetEntry( nKey ); + DBG_ASSERT( pFormat, "SvNumberformat::ConvertLanguage: Conversion without format" ); + if ( pFormat ) + { + ImpCopyNumberformat( *pFormat ); + // Reset values taken over from Formatter/Scanner + // pColor still points to table in temporary Formatter/Scanner + for (ImpSvNumFor & rFormatter : NumFor) + { + OUString aColorName = rFormatter.GetColorName(); + const Color* pColor = rScan.GetColor( aColorName ); + rFormatter.SetColor( pColor, aColorName ); + } + } +} + +bool SvNumberformat::HasNewCurrency() const +{ + for (const auto & j : NumFor) + { + if ( j.HasNewCurrency() ) + { + return true; + } + } + return false; +} + +bool SvNumberformat::GetNewCurrencySymbol( OUString& rSymbol, + OUString& rExtension ) const +{ + for (const auto & j : NumFor) + { + if ( j.GetNewCurrencySymbol( rSymbol, rExtension ) ) + { + return true; + } + } + rSymbol.clear(); + rExtension.clear(); + return false; +} + +// static +OUString SvNumberformat::StripNewCurrencyDelimiters( const OUString& rStr ) +{ + OUStringBuffer aTmp(rStr.getLength()); + sal_Int32 nStartPos, nPos, nLen; + nLen = rStr.getLength(); + nStartPos = 0; + while ( (nPos = rStr.indexOf( "[$", nStartPos )) >= 0 ) + { + sal_Int32 nEnd; + if ( (nEnd = GetQuoteEnd( rStr, nPos )) >= 0 ) + { + aTmp.append(rStr.subView( nStartPos, ++nEnd - nStartPos )); + nStartPos = nEnd; + } + else + { + aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) ); + nStartPos = nPos + 2; + sal_Int32 nDash; + nEnd = nStartPos - 1; + do + { + nDash = rStr.indexOf( '-', ++nEnd ); + nEnd = GetQuoteEnd( rStr, nDash ); + } + while ( nEnd >= 0 ); + sal_Int32 nClose; + nEnd = nStartPos - 1; + do + { + nClose = rStr.indexOf( ']', ++nEnd ); + nEnd = GetQuoteEnd( rStr, nClose ); + } + while ( nEnd >= 0 ); + + if(nClose < 0) + { + /* there should always be a closing ] + * but the old String class would have hidden + * that. so be conservative too + */ + nClose = nLen; + } + + nPos = nClose; + if(nDash >= 0 && nDash < nClose) + { + nPos = nDash; + } + aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) ); + nStartPos = nClose + 1; + } + } + if ( nLen > nStartPos ) + { + aTmp.append(rStr.subView(nStartPos, nLen - nStartPos) ); + } + return aTmp.makeStringAndClear(); +} + +void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUStringBuffer& rOutString) const +{ + OUString sTemp; + ImpGetOutputStandard(fNumber, sTemp); + rOutString = sTemp; +} + +void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUString& rOutString) const +{ + sal_uInt16 nStandardPrec = rScan.GetStandardPrec(); + + if ( fabs(fNumber) > EXP_ABS_UPPER_BOUND ) + { + nStandardPrec = ::std::min(nStandardPrec, static_cast<sal_uInt16>(14)); // limits to 14 decimals + rOutString = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_E2, nStandardPrec /*2*/, + GetFormatter().GetNumDecimalSep()[0]); + } + else + { + ImpGetOutputStdToPrecision(fNumber, rOutString, nStandardPrec); + } +} + +namespace +{ + +template<typename T> +bool checkForAll0s(const T& rString, sal_Int32 nIdx=0) +{ + if (nIdx>=rString.getLength()) + return false; + + do + { + if (rString[nIdx]!='0') + return false; + } + while (++nIdx<rString.getLength()); + + return true; +} + +} + +void SvNumberformat::ImpGetOutputStdToPrecision(double& rNumber, OUString& rOutString, sal_uInt16 nPrecision) const +{ + // Make sure the precision doesn't go over the maximum allowable precision. + nPrecision = ::std::min(UPPER_PRECISION, nPrecision); + + // We decided to strip trailing zeros unconditionally, since binary + // double-precision rounding error makes it impossible to determine e.g. + // whether 844.10000000000002273737 is what the user has typed, or the + // user has typed 844.1 but IEEE 754 represents it that way internally. + + rOutString = ::rtl::math::doubleToUString( rNumber, + rtl_math_StringFormat_F, nPrecision /*2*/, + GetFormatter().GetNumDecimalSep()[0], true ); + if (rOutString[0] == '-' && checkForAll0s(rOutString, 1)) + { + rOutString = comphelper::string::stripStart(rOutString, '-'); // not -0 + } + rOutString = impTransliterate(rOutString, NumFor[0].GetNatNum()); +} + +void SvNumberformat::ImpGetOutputInputLine(double fNumber, OUString& OutString) const +{ + bool bModified = false; + if ( (eType & SvNumFormatType::PERCENT) && (fabs(fNumber) < D_MAX_D_BY_100)) + { + if (fNumber == 0.0) + { + OutString = "0%"; + return; + } + fNumber *= 100; + bModified = true; + } + + if (fNumber == 0.0) + { + OutString = "0"; + return; + } + + OutString = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, + GetFormatter().GetNumDecimalSep()[0], true ); + + if ( eType & SvNumFormatType::PERCENT && bModified) + { + OutString += "%"; + } +} + +short SvNumberformat::ImpCheckCondition(double fNumber, + double fLimit, + SvNumberformatLimitOps eOp) +{ + switch(eOp) + { + case NUMBERFORMAT_OP_NO: + return -1; + case NUMBERFORMAT_OP_EQ: + return static_cast<short>(fNumber == fLimit); + case NUMBERFORMAT_OP_NE: + return static_cast<short>(fNumber != fLimit); + case NUMBERFORMAT_OP_LT: + return static_cast<short>(fNumber < fLimit); + case NUMBERFORMAT_OP_LE: + return static_cast<short>(fNumber <= fLimit); + case NUMBERFORMAT_OP_GT: + return static_cast<short>(fNumber > fLimit); + case NUMBERFORMAT_OP_GE: + return static_cast<short>(fNumber >= fLimit); + default: + return -1; + } +} + +static bool lcl_appendStarFillChar( OUStringBuffer& rBuf, const OUString& rStr ) +{ + // Right during user input the star symbol is the very + // last character before the user enters another one. + if (rStr.getLength() > 1) + { + rBuf.append(u'\x001B'); + rBuf.append(rStr[1]); + return true; + } + return false; +} + +static bool lcl_insertStarFillChar( OUStringBuffer& rBuf, sal_Int32 nPos, const OUString& rStr ) +{ + if (rStr.getLength() > 1) + { + rBuf.insert( nPos, rStr[1]); + rBuf.insert( nPos, u'\x001B'); + return true; + } + return false; +} + +void SvNumberformat::GetOutputString(std::u16string_view sString, + OUString& OutString, + const Color** ppColor) +{ + OUStringBuffer sOutBuff; + sal_uInt16 nIx; + if (eType & SvNumFormatType::TEXT) + { + nIx = 0; + } + else if (NumFor[3].GetCount() > 0) + { + nIx = 3; + } + else + { + *ppColor = nullptr; // no change of color + return; + } + *ppColor = NumFor[nIx].GetColor(); + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + if (rInfo.eScannedType == SvNumFormatType::TEXT) + { + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + for (sal_uInt16 i = 0; i < nCnt; i++) + { + switch (rInfo.nTypeArray[i]) + { + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + lcl_appendStarFillChar( sOutBuff, rInfo.sStrArray[i]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[i].getLength() >= 2) + InsertBlanks( sOutBuff, sOutBuff.getLength(), rInfo.sStrArray[i][1] ); + break; + case NF_KEY_GENERAL : // #77026# "General" is the same as "@" + case NF_SYMBOLTYPE_DEL : + sOutBuff.append(sString); + break; + default: + sOutBuff.append(rInfo.sStrArray[i]); + } + } + } + OutString = sOutBuff.makeStringAndClear(); +} + +namespace { + +void lcl_GetOutputStringScientific(double fNumber, sal_uInt16 nCharCount, + const SvNumberFormatter& rFormatter, OUString& rOutString) +{ + bool bSign = std::signbit(fNumber); + + // 1.000E+015 (one digit and the decimal point, and the two chars + + // nExpDigit for the exponential part, totalling 6 or 7). + double fExp = log10( fabs(fNumber) ); + if( fExp < 0.0 ) + fExp = 1.0 - fExp; + sal_uInt16 nCharFormat = 6 + (fExp >= 100.0 ? 1 : 0); + sal_uInt16 nPrec = nCharCount > nCharFormat ? nCharCount - nCharFormat : 0; + if (nPrec && bSign) + { + // Make room for the negative sign. + --nPrec; + } + nPrec = ::std::min(nPrec, static_cast<sal_uInt16>(14)); // limit to 14 decimals. + + rOutString = ::rtl::math::doubleToUString(fNumber, rtl_math_StringFormat_E2, + nPrec, rFormatter.GetNumDecimalSep()[0], true ); +} + +OUString lcl_GetPercentString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) +{ + sal_Int32 i; + OUStringBuffer aPercentString; + for( i = 0; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_PERCENT ) + { + aPercentString.append( rInfo.sStrArray[i] ); + bool bStringFound = false; + for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_STRING ; i-- ) + { + if( !bStringFound ) + { + bStringFound = true; + aPercentString.insert( 0, "\"" ); + } + aPercentString.insert( 0, rInfo.sStrArray[i] ); + } + i = nCnt; + if( bStringFound ) + aPercentString.insert( 0, "\"" ); + } + } + return aPercentString.makeStringAndClear(); +} + +OUString lcl_GetDenominatorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) +{ + sal_Int32 i; + OUStringBuffer aDenominatorString; + for( i = 0; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC ) + { + while ( ( ++i < nCnt ) && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_FRAC_FDIV + && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_DIGIT ); + for( ; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC_FDIV || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT ) + aDenominatorString.append( rInfo.sStrArray[i] ); + else + i = nCnt; + } + } + } + return aDenominatorString.makeStringAndClear(); +} + +OUString lcl_GetNumeratorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) +{ + sal_Int32 i; + OUStringBuffer aNumeratorString; + for( i = 0; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC ) + { + for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT ; i-- ) + { + aNumeratorString.insert( 0, rInfo.sStrArray[i] ); + } + i = nCnt; + } + } + return aNumeratorString.makeStringAndClear(); +} + +OUString lcl_GetFractionIntegerString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) +{ + sal_Int32 i; + OUStringBuffer aIntegerString; + for( i = 0; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK ) + { + for( i--; i >= 0 && ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT + || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_THSEP ); i-- ) + { + aIntegerString.insert( 0, rInfo.sStrArray[i] ); + } + i = nCnt; + } + } + return aIntegerString.makeStringAndClear(); +} + +OUString lcl_GetIntegerFractionDelimiterString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) +{ + sal_uInt16 i; + for( i = 0; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK ) + { + return rInfo.sStrArray[i]; + } + } + return OUString(); +} + +} + +OUString SvNumberformat::GetPercentString( sal_uInt16 nNumFor ) const +{ + const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + return lcl_GetPercentString( rInfo, nCnt ); +} + +OUString SvNumberformat::GetDenominatorString( sal_uInt16 nNumFor ) const +{ + const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + return lcl_GetDenominatorString( rInfo, nCnt ); +} + +OUString SvNumberformat::GetNumeratorString( sal_uInt16 nNumFor ) const +{ + const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + return lcl_GetNumeratorString( rInfo, nCnt ); +} + +OUString SvNumberformat::GetIntegerFractionDelimiterString( sal_uInt16 nNumFor ) const +{ + const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + return lcl_GetIntegerFractionDelimiterString( rInfo, nCnt ); +} + +bool SvNumberformat::GetOutputString(double fNumber, sal_uInt16 nCharCount, OUString& rOutString) const +{ + if (eType != SvNumFormatType::NUMBER) + { + return false; + } + double fTestNum = fNumber; + bool bSign = std::signbit(fTestNum); + if (bSign) + { + fTestNum = -fTestNum; + } + if (fTestNum < EXP_LOWER_BOUND) + { + lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString); + return true; + } + + double fExp = log10(fTestNum); + // Values < 1.0 always have one digit before the decimal point. + sal_uInt16 nDigitPre = fExp >= 0.0 ? static_cast<sal_uInt16>(ceil(fExp)) : 1; + + if (nDigitPre > 15) + { + lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString); + return true; + } + + sal_uInt16 nPrec = nCharCount >= nDigitPre ? nCharCount - nDigitPre : 0; + if (nPrec && bSign) + { + // Subtract the negative sign. + --nPrec; + } + if (nPrec) + { + // Subtract the decimal point. + --nPrec; + } + ImpGetOutputStdToPrecision(fNumber, rOutString, nPrec); + if (rOutString.getLength() > nCharCount) + { + // String still wider than desired. Switch to scientific notation. + lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString); + } + return true; +} + +sal_uInt16 SvNumberformat::GetSubformatIndex (double fNumber ) const +{ + sal_uInt16 nIx; // Index of the partial format + double fLimit_1 = fLimit1; + short nCheck = ImpCheckCondition(fNumber, fLimit_1, eOp1); + if (nCheck == -1 || nCheck == 1) // Only 1 String or True + { + nIx = 0; + } + else + { + double fLimit_2 = fLimit2; + nCheck = ImpCheckCondition(fNumber, fLimit_2, eOp2); + if (nCheck == -1 || nCheck == 1) + { + nIx = 1; + } + else + { + nIx = 2; + } + } + return nIx; +} + +bool SvNumberformat::GetOutputString(double fNumber, + OUString& OutString, + const Color** ppColor) +{ + bool bRes = false; + OutString.clear(); + *ppColor = nullptr; // No color change + if (eType & SvNumFormatType::LOGICAL && sFormatstring == rScan.GetKeywords()[NF_KEY_BOOLEAN]) + { + if (fNumber) + { + OutString = rScan.GetTrueString(); + } + else + { + OutString = rScan.GetFalseString(); + } + return false; + } + OUStringBuffer sBuff(64); + if (eType & SvNumFormatType::TEXT) + { + ImpGetOutputStandard(fNumber, sBuff); + OutString = sBuff.makeStringAndClear(); + return false; + } + bool bHadStandard = false; + if (bStandard) // Individual standard formats + { + if (rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION) // All number format InputLine + { + ImpGetOutputInputLine(fNumber, OutString); + return false; + } + switch (eType) + { + case SvNumFormatType::NUMBER: // Standard number format + if (rScan.GetStandardPrec() == SvNumberFormatter::UNLIMITED_PRECISION) + { + if (std::signbit(fNumber)) + { + if (!(fNumber < 0.0)) + fNumber = -fNumber; // do not display -0.0 + } + if (fNumber == 0.0) + { + OutString = "0"; + } + else if (fNumber < 1.0 && fNumber > -1.0) + { + // Decide whether to display as 0.000000123... or 1.23...e-07 + bool bFix = (fNumber < -EXP_LOWER_BOUND || EXP_LOWER_BOUND < fNumber); + if (!bFix) + { + // Arbitrary, not too many 0s visually, start E2 at 1E-10. + constexpr sal_Int32 kMaxExp = 9; + const sal_Int32 nExp = static_cast<sal_Int32>(ceil( -log10( fabs( fNumber)))); + if (nExp <= kMaxExp && rtl::math::approxEqual( + rtl::math::round( fNumber, 16), rtl::math::round( fNumber, nExp + 16))) + { + // Not too many significant digits or accuracy + // artefacts, otherwise leave everything to E2 + // format. + bFix = true; + } + } + if (bFix) + OutString = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_F, + rtl_math_DecimalPlaces_Max, + GetFormatter().GetNumDecimalSep()[0], true); + else + OutString = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_E2, + rtl_math_DecimalPlaces_Max, + GetFormatter().GetNumDecimalSep()[0], true); + } + else + { + OutString = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, + GetFormatter().GetNumDecimalSep()[0], true); + } + return false; + } + ImpGetOutputStandard(fNumber, sBuff); + bHadStandard = true; + break; + case SvNumFormatType::DATE: + bRes |= ImpGetDateOutput(fNumber, 0, sBuff); + bHadStandard = true; + break; + case SvNumFormatType::TIME: + bRes |= ImpGetTimeOutput(fNumber, 0, sBuff); + bHadStandard = true; + break; + case SvNumFormatType::DATETIME: + bRes |= ImpGetDateTimeOutput(fNumber, 0, sBuff); + bHadStandard = true; + break; + default: break; + } + } + if ( !bHadStandard ) + { + sal_uInt16 nIx = GetSubformatIndex ( fNumber ); // Index of the partial format + if (fNumber < 0.0 && + ((nIx == 0 && IsFirstSubformatRealNegative()) || // 1st, usually positive subformat + (nIx == 1 && IsSecondSubformatRealNegative()))) // 2nd, usually negative subformat + { + fNumber = -fNumber; // eliminate sign + } + *ppColor = NumFor[nIx].GetColor(); + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + if (nCnt == 0 && rInfo.eScannedType == SvNumFormatType::EMPTY) + { + return false; // Empty => nothing + } + else if (nCnt == 0) // Else Standard Format + { + ImpGetOutputStandard(fNumber, sBuff); + OutString = sBuff.makeStringAndClear(); + return false; + } + switch (rInfo.eScannedType) + { + case SvNumFormatType::TEXT: + case SvNumFormatType::DEFINED: + for (sal_uInt16 i = 0; i < nCnt; i++) + { + switch (rInfo.nTypeArray[i]) + { + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[i].getLength() >= 2) + InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + sBuff.append(rInfo.sStrArray[i]); + break; + case NF_SYMBOLTYPE_THSEP: + if (rInfo.nThousand == 0) + { + sBuff.append(rInfo.sStrArray[i]); + } + break; + default: + break; + } + } + break; + case SvNumFormatType::DATE: + bRes |= ImpGetDateOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::TIME: + bRes |= ImpGetTimeOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::DATETIME: + bRes |= ImpGetDateTimeOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::NUMBER: + case SvNumFormatType::PERCENT: + case SvNumFormatType::CURRENCY: + bRes |= ImpGetNumberOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::LOGICAL: + bRes |= ImpGetLogicalOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::FRACTION: + bRes |= ImpGetFractionOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::SCIENTIFIC: + bRes |= ImpGetScientificOutput(fNumber, nIx, sBuff); + break; + default: break; + } + } + OutString = sBuff.makeStringAndClear(); + return bRes; +} + +bool SvNumberformat::ImpGetScientificOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sStr) +{ + bool bRes = false; + bool bSign = false; + + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + + if (fNumber < 0) + { + if (nIx == 0) // Not in the ones at the end + { + bSign = true; // Formats + } + fNumber = -fNumber; + } + + sStr = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_E, + rInfo.nCntPre + rInfo.nCntPost - 1, '.' ); + OUStringBuffer ExpStr; + short nExpSign = 1; + sal_Int32 nExPos = sStr.indexOf('E'); + sal_Int32 nDecPos = -1; + + if ( nExPos >= 0 ) + { + // split into mantissa and exponent and get rid of "E+" or "E-" + sal_Int32 nExpStart = nExPos + 1; + + switch ( sStr[ nExpStart ] ) + { + case '-' : + nExpSign = -1; + [[fallthrough]]; + case '+' : + ++nExpStart; + break; + } + ExpStr = sStr.subView( nExpStart ); // part following the "E+" + sStr.truncate( nExPos ); + + if ( rInfo.nCntPre != 1 ) // rescale Exp + { + sal_Int32 nExp = OUString::unacquired(ExpStr).toInt32() * nExpSign; + sal_Int32 nRescale = (rInfo.nCntPre != 0) ? nExp % static_cast<sal_Int32>(rInfo.nCntPre) : -1; + if( nRescale < 0 && rInfo.nCntPre != 0 ) + nRescale += static_cast<sal_Int32>(rInfo.nCntPre); + nExp -= nRescale; + if ( nExp < 0 ) + { + nExpSign = -1; + nExp = -nExp; + } + else + { + nExpSign = 1; + } + ExpStr = OUString::number( nExp ); + const sal_Unicode cFirstDigit = sStr[0]; + // rescale mantissa + sStr = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_E, + nRescale + rInfo.nCntPost, '.' ); + + // sStr now may contain a rounded-up value shifted into the next + // magnitude, for example 1.000E+02 (4 digits) for fNumber 99.995 + // (9.9995E+02 rounded to 3 decimals) but we want the final result + // to be 100.00E+00 (5 digits), so for the following fill routines + // below to work correctly append a zero decimal. + /* TODO: this is awkward, could an engineering notation mode be + * introduced to rtl_math_doubleToUString()? */ + sStr.truncate( sStr.indexOf('E') ); + if (sStr[0] == '1' && cFirstDigit != '1') + sStr.append('0'); + } + + // cut any decimal delimiter + sal_Int32 index = 0; + + while((index = sStr.indexOf('.', index)) >= 0) + { + if (nDecPos < 0) + nDecPos = index; + sStr.remove(index, 1); + } + } + + sal_uInt16 j = nCnt-1; // Last symbol + sal_Int32 k; // Position in ExpStr + sal_Int32 nZeros = 0; // Erase leading zeros + + bRes |= ImpNumberFill(ExpStr, fNumber, k, j, nIx, NF_SYMBOLTYPE_EXP); + + while (nZeros < k && ExpStr[nZeros] == '0') + { + ++nZeros; + } + if (nZeros) + { + ExpStr.remove( 0, nZeros); + } + + bool bCont = true; + + if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_EXP) + { + const OUString& rStr = rInfo.sStrArray[j]; + if (nExpSign == -1) + { + ExpStr.insert(0, '-'); + } + else if (rStr.getLength() > 1 && rStr[1] == '+') + { + ExpStr.insert(0, '+'); + } + ExpStr.insert(0, rStr[0]); + if ( j ) + { + j--; + } + else + { + bCont = false; + } + } + // Continue main number: + if ( !bCont ) + { + sStr.truncate(); + } + else + { + bRes |= ImpDecimalFill(sStr, fNumber, nDecPos, j, nIx, false); + } + + if (bSign) + { + sStr.insert(0, '-'); + } + sStr.append(ExpStr); + + return bRes; +} + +double SvNumberformat::GetRoundFractionValue ( double fNumber ) const +{ + sal_uInt16 nIx = GetSubformatIndex ( fNumber ); + double fIntPart = 0.0; // integer part of fraction + sal_Int64 nFrac = 0, nDiv = 1; // numerator and denominator + double fSign = (fNumber < 0.0) ? -1.0 : 1.0; + // fNumber is modified in ImpGetFractionElements to absolute fractional part + ImpGetFractionElements ( fNumber, nIx, fIntPart, nFrac, nDiv ); + if ( nDiv > 0 ) + return fSign * ( fIntPart + static_cast<double>(nFrac) / static_cast<double>(nDiv) ); + else + return fSign * fIntPart; +} + +void SvNumberformat::ImpGetFractionElements ( double& fNumber, sal_uInt16 nIx, + double& fIntPart, sal_Int64& nFrac, sal_Int64& nDiv ) const +{ + if ( fNumber < 0.0 ) + fNumber = -fNumber; + fIntPart = floor(fNumber); // Integral part + fNumber -= fIntPart; // Fractional part + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + sal_Int64 nForcedDiv = lcl_GetDenominatorString( rInfo, NumFor[nIx].GetCount() ).toInt32(); + if( nForcedDiv > 0 ) + { // Forced Denominator + nDiv = nForcedDiv; + nFrac = static_cast<sal_Int64>(floor ( fNumber * nDiv )); + double fFracNew = static_cast<double>(nFrac) / static_cast<double>(nDiv); + double fFracNew1 = static_cast<double>(nFrac + 1) / static_cast<double>(nDiv); + double fDiff = fNumber - fFracNew; + if( fDiff > ( fFracNew1 - fNumber ) ) + { + nFrac++; + } + } + else // Calculated Denominator + { + nDiv = 1; + sal_Int64 nBasis = static_cast<sal_Int64>(floor( pow(10.0,rInfo.nCntExp))) - 1; // 9, 99, 999 ,... + sal_Int64 nFracPrev = 1, nDivPrev = 0, nFracNext, nDivNext, nPartialDenom; + double fRemainder = fNumber; + + // Use continued fraction representation of fNumber + // See https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations + while ( fRemainder > 0.0 ) + { + double fTemp = 1.0 / fRemainder; // 64bits precision required when fRemainder is very weak + nPartialDenom = static_cast<sal_Int64>(floor(fTemp)); // due to floating point notation with double precision + fRemainder = fTemp - static_cast<double>(nPartialDenom); + nDivNext = nPartialDenom * nDiv + nDivPrev; + if ( nDivNext <= nBasis ) // continue loop + { + nFracNext = nPartialDenom * nFrac + nFracPrev; + nFracPrev = nFrac; + nFrac = nFracNext; + nDivPrev = nDiv; + nDiv = nDivNext; + } + else // calculate collateral fraction and exit + { + sal_Int64 nCollat = (nBasis - nDivPrev) / nDiv; + if ( 2 * nCollat >= nPartialDenom ) + { + sal_Int64 nFracTest = nCollat * nFrac + nFracPrev; + sal_Int64 nDivTest = nCollat * nDiv + nDivPrev; + double fSign = (static_cast<double>(nFrac) > fNumber * static_cast<double>(nDiv))?1.0:-1.0; + if ( fSign * ( double(nFrac * nDivTest + nDiv * nFracTest) - 2.0 * double(nDiv * nDivTest) * fNumber ) > 0.0 ) + { + nFrac = nFracTest; + nDiv = nDivTest; + } + } + fRemainder = 0.0; // exit while loop + } + } + } + if (nFrac >= nDiv) + { + ++fIntPart; + nFrac = 0; + nDiv = ( nForcedDiv > 0 ) ? nForcedDiv : 1; + } +} + +bool SvNumberformat::ImpGetFractionOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sBuff) +{ + bool bRes = false; + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + OUStringBuffer sStr, sFrac, sDiv; // Strings, value for Integral part Numerator and denominator + bool bSign = ( (fNumber < 0) && (nIx == 0) ); // sign Not in the ones at the end + const OUString sIntegerFormat = lcl_GetFractionIntegerString(rInfo, nCnt); + const OUString sNumeratorFormat = lcl_GetNumeratorString(rInfo, nCnt); + const OUString sDenominatorFormat = lcl_GetDenominatorString(rInfo, nCnt); + + sal_Int64 nFrac = 0, nDiv = 1; + double fNum = floor(fNumber); // Integral part + + if (fNum > D_MAX_U_INT32 || rInfo.nCntExp > 9) // Too large + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } + if (rInfo.nCntExp == 0) + { + SAL_WARN( "svl.numbers", "SvNumberformat:: Fraction, nCntExp == 0"); + sBuff.truncate(); + return false; + } + + ImpGetFractionElements( fNumber, nIx, fNum, nFrac, nDiv); + + if (rInfo.nCntPre == 0) // Improper fraction + { + double fNum1 = fNum * static_cast<double>(nDiv) + static_cast<double>(nFrac); + + if (fNum1 > D_MAX_INTEGER) + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } + nFrac = static_cast<sal_Int64>(floor(fNum1)); + } + else if (fNum == 0.0 && nFrac != 0) + { + } + else + { + char aBuf[100]; + sprintf( aBuf, "%.f", fNum ); // simple rounded integer (#100211# - checked) + sStr.appendAscii( aBuf ); + impTransliterate(sStr, NumFor[nIx].GetNatNum()); + } + bool bHideFraction = (rInfo.nCntPre > 0 && nFrac == 0 + && (sNumeratorFormat.indexOf('0') < 0) + && (sDenominatorFormat.indexOf('0') < 0 + || sDenominatorFormat.toInt32() > 0) ); + if ( bHideFraction ) + { + sDiv.truncate(); + } + else // if there are some '0' in format, force display of fraction + { + sFrac = ImpIntToString( nIx, nFrac ); + sDiv = ImpIntToString( nIx, nDiv ); + } + + sal_uInt16 j = nCnt-1; // Last symbol -> backwards + sal_Int32 k; // Denominator + + bRes |= ImpNumberFill(sDiv, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRAC, true); + + bool bCont = true; + if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRAC) + { + if ( bHideFraction ) + { // do not insert blank for fraction if there is no '?' + if ( sNumeratorFormat.indexOf('?') >= 0 + || sDenominatorFormat.indexOf('?') >= 0 ) + sDiv.insert(0, ' '); + } + else + { + sDiv.insert(0, rInfo.sStrArray[j][0]); + } + if ( j ) + { + j--; + } + else + { + bCont = false; + } + } + // Further numerators: + if ( !bCont ) + { + sFrac.truncate(); + } + else + { + bRes |= ImpNumberFill(sFrac, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRACBLANK); + bCont = false; // there is no integer part? + if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRACBLANK) + { + if ( j ) + { + if ( bHideFraction ) + { // '?' in any format force display of blank as delimiter + if ( sIntegerFormat.indexOf('?') >= 0 + || sNumeratorFormat.indexOf('?') >= 0 + || sDenominatorFormat.indexOf('?') >= 0 ) + { + for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++) + sFrac.insert(0, ' '); + } + } + else + { + if ( fNum != 0.0 || sIntegerFormat.indexOf('0') >= 0 ) + sFrac.insert(0, rInfo.sStrArray[j]); // insert Blank string only if there are both integer and fraction + else + { + if ( sIntegerFormat.indexOf('?') >= 0 + || sNumeratorFormat.indexOf('?') >= 0 ) + { + for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++) + sFrac.insert(0, ' '); + } + } + } + j--; + bCont = true; // Yes, there is an integer + } + else + sFrac.insert(0, rInfo.sStrArray[j]); + } + } + // Continue integer part + if ( !bCont ) + { + sStr.truncate(); + } + else + { + k = sStr.getLength(); // After last figure + bRes |= ImpNumberFillWithThousands(sStr, fNumber, k, j, nIx, + rInfo.nCntPre); + } + if (bSign && (nFrac != 0 || fNum != 0.0)) + { + sBuff.insert(0, '-'); // Not -0 + } + sBuff.append(sStr); + sBuff.append(sFrac); + sBuff.append(sDiv); + return bRes; +} + +sal_uInt16 SvNumberformat::ImpGetFractionOfSecondString( OUStringBuffer& rBuf, double fFractionOfSecond, + int nFractionDecimals, bool bAddOneRoundingDecimal, sal_uInt16 nIx, sal_uInt16 nMinimumInputLineDecimals ) +{ + if (!nFractionDecimals) + return 0; + + // nFractionDecimals+1 to not round up what Time::GetClock() carefully + // truncated. + rBuf.append( rtl::math::doubleToUString( fFractionOfSecond, rtl_math_StringFormat_F, + (bAddOneRoundingDecimal ? nFractionDecimals + 1 : nFractionDecimals), '.')); + rBuf.stripStart('0'); + rBuf.stripStart('.'); + if (bAddOneRoundingDecimal && rBuf.getLength() > nFractionDecimals) + rBuf.truncate( nFractionDecimals); // the digit appended because of nFractionDecimals+1 + if (nMinimumInputLineDecimals) + { + rBuf.stripEnd('0'); + for (sal_Int32 index = rBuf.getLength(); index < nMinimumInputLineDecimals; ++index) + { + rBuf.append('0'); + } + impTransliterate(rBuf, NumFor[nIx].GetNatNum()); + nFractionDecimals = rBuf.getLength(); + } + else + { + impTransliterate(rBuf, NumFor[nIx].GetNatNum()); + } + return static_cast<sal_uInt16>(nFractionDecimals); +} + +bool SvNumberformat::ImpGetTimeOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sBuff) +{ + using namespace ::com::sun::star::i18n; + bool bCalendarSet = false; + const double fNumberOrig = fNumber; + bool bRes = false; + bool bSign = false; + if (fNumber < 0.0) + { + fNumber = -fNumber; + if (nIx == 0) + { + bSign = true; + } + } + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + bool bInputLine; + sal_Int32 nCntPost; + if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION && + 0 < rInfo.nCntPost && rInfo.nCntPost < 7 ) + { // round at 7 decimals (+5 of 86400 == 12 significant digits) + bInputLine = true; + nCntPost = 7; + } + else + { + bInputLine = false; + nCntPost = rInfo.nCntPost; + } + + OUStringBuffer sSecStr; + sal_Int32 nSecPos = 0; // For figure by figure processing + sal_uInt32 nHour, nMin, nSec; + if (!rInfo.bThousand) // No [] format + { + sal_uInt16 nCHour, nCMinute, nCSecond; + double fFractionOfSecond; + tools::Time::GetClock( fNumberOrig, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost); + nHour = nCHour; + nMin = nCMinute; + nSec = nCSecond; + nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx, + (bInputLine ? rInfo.nCntPost : 0)); + } + else + { + const double fTime = rtl::math::round( fNumber * 86400.0, int(nCntPost)); + if (bSign && fTime == 0.0) + { + bSign = false; // Not -00:00:00 + } + if (fTime > D_MAX_U_INT32) + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } + sal_uInt32 nSeconds = static_cast<sal_uInt32>(fTime); + + nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx, + (bInputLine ? rInfo.nCntPost : 0)); + + if (rInfo.nThousand == 3) // [ss] + { + nHour = 0; + nMin = 0; + nSec = nSeconds; + } + else if (rInfo.nThousand == 2) // [mm]:ss + { + nHour = 0; + nMin = nSeconds / 60; + nSec = nSeconds % 60; + } + else if (rInfo.nThousand == 1) // [hh]:mm:ss + { + nHour = nSeconds / 3600; + nMin = (nSeconds%3600) / 60; + nSec = nSeconds%60; + } + else + { + // TODO What should these be set to? + nHour = 0; + nMin = 0; + nSec = 0; + } + } + + sal_Unicode cAmPm = ' '; // a or p + if (rInfo.nCntExp) // AM/PM + { + if (nHour == 0) + { + nHour = 12; + cAmPm = 'a'; + } + else if (nHour < 12) + { + cAmPm = 'a'; + } + else + { + cAmPm = 'p'; + if (nHour > 12) + { + nHour -= 12; + } + } + } + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + for (sal_uInt16 i = 0; i < nCnt; i++) + { + sal_Int32 nLen; + switch (rInfo.nTypeArray[i]) + { + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[i].getLength() >= 2) + InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + case NF_SYMBOLTYPE_DATESEP: + case NF_SYMBOLTYPE_TIMESEP: + case NF_SYMBOLTYPE_TIME100SECSEP: + sBuff.append(rInfo.sStrArray[i]); + break; + case NF_SYMBOLTYPE_DIGIT: + nLen = ( bInputLine && i > 0 && + (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING || + rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ? + nCntPost : rInfo.sStrArray[i].getLength() ); + for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j) + { + sBuff.append(sSecStr[nSecPos]); + nSecPos++; + } + break; + case NF_KEY_AMPM: // AM/PM + if ( !bCalendarSet ) + { + double fDiff = DateTime(rScan.GetNullDate()) - GetCal().getEpochStart(); + fDiff += fNumberOrig; + GetCal().setLocalDateTime( fDiff ); + bCalendarSet = true; + } + if (cAmPm == 'a') + { + sBuff.append(GetCal().getDisplayName( + CalendarDisplayIndex::AM_PM, AmPmValue::AM, 0 )); + } + else + { + sBuff.append(GetCal().getDisplayName( + CalendarDisplayIndex::AM_PM, AmPmValue::PM, 0 )); + } + break; + case NF_KEY_AP: // A/P + if (cAmPm == 'a') + { + sBuff.append('a'); + } + else + { + sBuff.append('p'); + } + break; + case NF_KEY_MI: // M + sBuff.append(ImpIntToString( nIx, nMin )); + break; + case NF_KEY_MMI: // MM + sBuff.append(ImpIntToString( nIx, nMin, 2 )); + break; + case NF_KEY_H: // H + sBuff.append(ImpIntToString( nIx, nHour )); + break; + case NF_KEY_HH: // HH + sBuff.append(ImpIntToString( nIx, nHour, 2 )); + break; + case NF_KEY_S: // S + sBuff.append(ImpIntToString( nIx, nSec )); + break; + case NF_KEY_SS: // SS + sBuff.append(ImpIntToString( nIx, nSec, 2 )); + break; + default: + break; + } + } + if (bSign && rInfo.bThousand) + { + sBuff.insert(0, '-'); + } + return bRes; +} + + +/** If a day of month occurs within the format, the month name is in possessive + genitive case if the day follows the month, and partitive case if the day + precedes the month. If there is no day of month the nominative case (noun) + is returned. Also if the month is immediately preceded or followed by a + literal string other than space and not followed by a comma, the nominative + name is used, this prevents duplicated casing for MMMM\t\a and such in + documents imported from (e.g. Finnish) Excel or older LibO/OOo releases. + */ + +// IDEA: instead of eCodeType pass the index to nTypeArray and restrict +// inspection of month name around that one, that would enable different month +// cases in one format. Though probably the most rare use case ever... + +sal_Int32 SvNumberformat::ImpUseMonthCase( int & io_nState, const ImpSvNumFor& rNumFor, NfKeywordIndex eCodeType ) +{ + using namespace ::com::sun::star::i18n; + if (!io_nState) + { + bool bMonthSeen = false; + bool bDaySeen = false; + const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); + const sal_uInt16 nCount = rNumFor.GetCount(); + for (sal_uInt16 i = 0; i < nCount && io_nState == 0; ++i) + { + sal_Int32 nLen; + switch (rInfo.nTypeArray[i]) + { + case NF_KEY_D : + case NF_KEY_DD : + if (bMonthSeen) + { + io_nState = 2; + } + else + { + bDaySeen = true; + } + break; + case NF_KEY_MMM: + case NF_KEY_MMMM: + case NF_KEY_MMMMM: + if ((i < nCount-1 && + rInfo.nTypeArray[i+1] == NF_SYMBOLTYPE_STRING && + // Literal following, not empty, space nor comma. + !rInfo.sStrArray[i+1].isEmpty() && + rInfo.sStrArray[i+1][0] != ' ' && rInfo.sStrArray[i+1][0] != ',') || + (i > 0 && rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING && + ((nLen = rInfo.sStrArray[i-1].getLength()) > 0) && + // Literal preceding, not space. + rInfo.sStrArray[i-1][nLen-1] != ' ')) + { + io_nState = 1; + } + else if (bDaySeen) + { + io_nState = 3; + } + else + { + bMonthSeen = true; + } + break; + } + } + if (io_nState == 0) + { + io_nState = 1; // No day of month + } + } + switch (io_nState) + { + case 1: + // No day of month or forced nominative + switch (eCodeType) + { + case NF_KEY_MMM: + return CalendarDisplayCode::SHORT_MONTH_NAME; + case NF_KEY_MMMM: + return CalendarDisplayCode::LONG_MONTH_NAME; + case NF_KEY_MMMMM: + return CalendarDisplayCode::NARROW_MONTH_NAME; + default: + ; // nothing + } + break; + case 2: + // Day of month follows month (the month's 17th) + switch (eCodeType) + { + case NF_KEY_MMM: + return CalendarDisplayCode::SHORT_GENITIVE_MONTH_NAME; + case NF_KEY_MMMM: + return CalendarDisplayCode::LONG_GENITIVE_MONTH_NAME; + case NF_KEY_MMMMM: + return CalendarDisplayCode::NARROW_GENITIVE_MONTH_NAME; + default: + ; // Nothing + } + break; + case 3: + // Day of month precedes month (17 of month) + switch (eCodeType) + { + case NF_KEY_MMM: + return CalendarDisplayCode::SHORT_PARTITIVE_MONTH_NAME; + case NF_KEY_MMMM: + return CalendarDisplayCode::LONG_PARTITIVE_MONTH_NAME; + case NF_KEY_MMMMM: + return CalendarDisplayCode::NARROW_PARTITIVE_MONTH_NAME; + default: + ; // nothing + } + break; + } + SAL_WARN( "svl.numbers", "ImpUseMonthCase: unhandled keyword index eCodeType"); + return CalendarDisplayCode::LONG_MONTH_NAME; +} + + +bool SvNumberformat::ImpIsOtherCalendar( const ImpSvNumFor& rNumFor ) const +{ + if ( GetCal().getUniqueID() != GREGORIAN ) + { + return false; + } + const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); + const sal_uInt16 nCnt = rNumFor.GetCount(); + sal_uInt16 i; + for ( i = 0; i < nCnt; i++ ) + { + switch ( rInfo.nTypeArray[i] ) + { + case NF_SYMBOLTYPE_CALENDAR : + return false; + case NF_KEY_EC : + case NF_KEY_EEC : + case NF_KEY_R : + case NF_KEY_RR : + case NF_KEY_AAA : + case NF_KEY_AAAA : + case NF_KEY_G : + case NF_KEY_GG : + case NF_KEY_GGG : + return true; + } + } + return false; +} + +void SvNumberformat::SwitchToOtherCalendar( OUString& rOrgCalendar, + double& fOrgDateTime ) const +{ + CalendarWrapper& rCal = GetCal(); + if ( rCal.getUniqueID() != GREGORIAN ) + return; + + using namespace ::com::sun::star::i18n; + const css::uno::Sequence< OUString > xCals = rCal.getAllCalendars( + rLoc().getLanguageTag().getLocale() ); + sal_Int32 nCnt = xCals.getLength(); + if ( nCnt <= 1 ) + return; + + auto pCal = std::find_if(xCals.begin(), xCals.end(), + [](const OUString& rCalName) { return rCalName != GREGORIAN; }); + if (pCal == xCals.end()) + return; + + if ( !rOrgCalendar.getLength() ) + { + rOrgCalendar = rCal.getUniqueID(); + fOrgDateTime = rCal.getDateTime(); + } + rCal.loadCalendar( *pCal, rLoc().getLanguageTag().getLocale() ); + rCal.setDateTime( fOrgDateTime ); +} + +void SvNumberformat::SwitchToGregorianCalendar( const OUString& rOrgCalendar, + double fOrgDateTime ) const +{ + CalendarWrapper& rCal = GetCal(); + if ( rOrgCalendar.getLength() && rCal.getUniqueID() != GREGORIAN ) + { + rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() ); + rCal.setDateTime( fOrgDateTime ); + } +} + +bool SvNumberformat::ImpFallBackToGregorianCalendar( OUString& rOrgCalendar, double& fOrgDateTime ) +{ + using namespace ::com::sun::star::i18n; + CalendarWrapper& rCal = GetCal(); + if ( rCal.getUniqueID() != GREGORIAN ) + { + sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA ); + if ( nVal == 0 && rCal.getLoadedCalendar().Eras[0].ID == "Dummy" ) + { + if ( !rOrgCalendar.getLength() ) + { + rOrgCalendar = rCal.getUniqueID(); + fOrgDateTime = rCal.getDateTime(); + } + else if ( rOrgCalendar == GREGORIAN ) + { + rOrgCalendar.clear(); + } + rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() ); + rCal.setDateTime( fOrgDateTime ); + return true; + } + } + return false; +} + + +#ifdef THE_FUTURE +/* XXX NOTE: even if the ImpSwitchToSpecifiedCalendar method is currently + * unused please don't remove it, it would be needed by + * SwitchToSpecifiedCalendar(), see comment in + * ImpSvNumberInputScan::GetDateRef() */ + +bool SvNumberformat::ImpSwitchToSpecifiedCalendar( OUString& rOrgCalendar, + double& fOrgDateTime, + const ImpSvNumFor& rNumFor ) const +{ + const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); + const sal_uInt16 nCnt = rNumFor.GetCount(); + for ( sal_uInt16 i = 0; i < nCnt; i++ ) + { + if ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_CALENDAR ) + { + CalendarWrapper& rCal = GetCal(); + if ( !rOrgCalendar.getLength() ) + { + rOrgCalendar = rCal.getUniqueID(); + fOrgDateTime = rCal.getDateTime(); + } + rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLocale() ); + rCal.setDateTime( fOrgDateTime ); + return true; + } + } + return false; +} +#endif + +// static +void SvNumberformat::ImpAppendEraG( OUStringBuffer& OutString, + const CalendarWrapper& rCal, + sal_Int16 nNatNum ) +{ + using namespace ::com::sun::star::i18n; + if ( rCal.getUniqueID() == "gengou" ) + { + sal_Unicode cEra; + sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA ); + switch ( nVal ) + { + case 1: + cEra = 'M'; + break; + case 2: + cEra = 'T'; + break; + case 3: + cEra = 'S'; + break; + case 4: + cEra = 'H'; + break; + case 5: + cEra = 'R'; + break; + default: + cEra = '?'; + break; + } + OutString.append(cEra); + } + else + { + OutString.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum )); + } +} + +bool SvNumberformat::ImpIsIso8601( const ImpSvNumFor& rNumFor ) const +{ + bool bIsIso = false; + if (eType & SvNumFormatType::DATE) + { + enum State + { + eNone, + eAtYear, + eAtSep1, + eAtMonth, + eAtSep2, + eNotIso + }; + State eState = eNone; + auto & rTypeArray = rNumFor.Info().nTypeArray; + sal_uInt16 nCnt = rNumFor.GetCount(); + for (sal_uInt16 i=0; i < nCnt && !bIsIso && eState != eNotIso; ++i) + { + switch ( rTypeArray[i] ) + { + case NF_KEY_YY: // two digits not strictly ISO 8601 + case NF_KEY_YYYY: + if (eState != eNone) + { + eState = eNotIso; + } + else + { + eState = eAtYear; + } + break; + case NF_KEY_M: // single digit not strictly ISO 8601 + case NF_KEY_MM: + if (eState != eAtSep1) + { + eState = eNotIso; + } + else + { + eState = eAtMonth; + } + break; + case NF_KEY_D: // single digit not strictly ISO 8601 + case NF_KEY_DD: + if (eState != eAtSep2) + { + eState = eNotIso; + } + else + { + bIsIso = true; + } + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_DATESEP: + if (rNumFor.Info().sStrArray[i] == "-") + { + if (eState == eAtYear) + { + eState = eAtSep1; + } + else if (eState == eAtMonth) + { + eState = eAtSep2; + } + else + { + eState = eNotIso; + } + } + else + { + eState = eNotIso; + } + break; + default: + eState = eNotIso; + } + } + } + else + { + SAL_WARN( "svl.numbers", "SvNumberformat::ImpIsIso8601: no date" ); + } + return bIsIso; +} + +static bool lcl_hasEra( const ImpSvNumFor& rNumFor ) +{ + const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); + const sal_uInt16 nCnt = rNumFor.GetCount(); + for ( sal_uInt16 i = 0; i < nCnt; i++ ) + { + switch ( rInfo.nTypeArray[i] ) + { + case NF_KEY_RR : + case NF_KEY_G : + case NF_KEY_GG : + case NF_KEY_GGG : + return true; + } + } + return false; +} + +static bool lcl_isSignedYear( const CalendarWrapper& rCal, const ImpSvNumFor& rNumFor ) +{ + return rCal.getValue( css::i18n::CalendarFieldIndex::ERA ) == 0 && + rCal.getUniqueID() == GREGORIAN && !lcl_hasEra( rNumFor ); +} + +/* XXX: if needed this could be stripped from rEpochStart and diff adding and + * moved to tools' DateTime to be reused elsewhere. */ +static bool lcl_getValidDate( const DateTime& rNullDate, const DateTime& rEpochStart, double& fNumber ) +{ + static const DateTime aCE( Date(1,1,1)); + static const DateTime aMin( Date(1,1, SAL_MIN_INT16)); + static const DateTime aMax( Date(31,12, SAL_MAX_INT16), tools::Time(23,59,59, tools::Time::nanoSecPerSec - 1)); + static const double fMin = aMin - aCE; + static const double fMax = aMax - aCE; + // Value must be representable in our tools::Date proleptic Gregorian + // calendar as well. + const double fOff = (rNullDate - aCE) + fNumber; + // Add diff between epochs to serial date number. + const double fDiff = rNullDate - rEpochStart; + fNumber += fDiff; + return fMin <= fOff && fOff <= fMax; +} + +bool SvNumberformat::ImpGetDateOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sBuff) +{ + using namespace ::com::sun::star::i18n; + bool bRes = false; + + CalendarWrapper& rCal = GetCal(); + if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber)) + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } + rCal.setLocalDateTime( fNumber ); + int nUseMonthCase = 0; // Not decided yet + OUString aOrgCalendar; // empty => not changed yet + + double fOrgDateTime(0.0); + bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx] ); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ) ) + { + bOtherCalendar = false; + } + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum(); + OUString aStr; + + // NatNum12: if the date format contains more than a date + // field, it needs to specify in NatNum12 argument + // which date element needs special formatting: + // + // '[NatNum12 ordinal-number]D' -> "1st" + // '[NatNum12 D=ordinal-number]D" of "MMMM' -> "1st of April" + // '[NatNum12 D=ordinal]D" of "MMMM' -> "first of April" + // '[NatNum12 YYYY=year,D=ordinal]D" of "MMMM", "YYYY' -> "first of April, nineteen ninety" + // + // Note: set only for YYYY, MMMM, M, DDDD, D and NNN/AAAA in date formats. + // Additionally for MMMMM, MMM, DDD and NN/AA to support at least + // capitalize, upper, lower, title. + // XXX It's possible to extend this for other keywords and date + time + // combinations, as required. + + bool bUseSpellout = NatNumTakesParameters(nNatNum) && + (nCnt == 1 || NumFor[nIx].GetNatNum().GetParams().indexOf('=') > -1); + + for (sal_uInt16 i = 0; i < nCnt; i++) + { + switch (rInfo.nTypeArray[i]) + { + case NF_SYMBOLTYPE_CALENDAR : + if ( !aOrgCalendar.getLength() ) + { + aOrgCalendar = rCal.getUniqueID(); + fOrgDateTime = rCal.getDateTime(); + } + rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() ); + rCal.setDateTime( fOrgDateTime ); + ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + break; + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[i].getLength() >= 2) + InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + case NF_SYMBOLTYPE_DATESEP: + case NF_SYMBOLTYPE_TIMESEP: + case NF_SYMBOLTYPE_TIME100SECSEP: + sBuff.append(rInfo.sStrArray[i]); + break; + case NF_KEY_M: // M + aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_MONTH, nNatNum ); + // NatNum12: support variants of preposition, suffixation or article + // for example, Catalan "de març", but "d'abril" etc. + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + break; + case NF_KEY_MM: // MM + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_MONTH, nNatNum )); + break; + case NF_KEY_MMM: // MMM + case NF_KEY_MMMM: // MMMM + case NF_KEY_MMMMM: // MMMMM + // NatNum12: support variants of preposition, suffixation or + // article, or capitalize, upper, lower, title. + // Note: result of the "spell out" conversion can depend from the optional + // PartitiveMonths or GenitiveMonths defined in the locale data, + // see description of ImpUseMonthCase(), and locale data in + // i18npool/source/localedata/data/ and libnumbertext + aStr = rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], + static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])), + nNatNum); + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + break; + case NF_KEY_Q: // Q + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum )); + break; + case NF_KEY_QQ: // QQ + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum )); + break; + case NF_KEY_D: // D + aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum ); + // NatNum12: support variants of preposition, suffixation or article + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + break; + case NF_KEY_DD: // DD + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum )); + break; + case NF_KEY_DDD: // DDD + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ); + // NatNum12: support at least capitalize, upper, lower, title + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_DDDD: // DDDD + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ); + // NatNum12: support variants of preposition, suffixation or article + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_YY: // YY + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_YYYY: // YYYY + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } + aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ); + if (aStr.getLength() < 4 && !lcl_hasEra(NumFor[nIx])) + { + using namespace comphelper::string; + // Ensure that year consists of at least 4 digits, so it + // can be distinguished from 2 digits display and edited + // without suddenly being hit by the 2-digit year magic. + OUStringBuffer aBuf; + padToLength(aBuf, 4 - aStr.getLength(), '0'); + impTransliterate(aBuf, NumFor[nIx].GetNatNum()); + aBuf.append(aStr); + aStr = aBuf.makeStringAndClear(); + } + // NatNum12: support variants of preposition, suffixation or article + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_EC: // E + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); + break; + case NF_KEY_EEC: // EE + case NF_KEY_R: // R + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum )); + break; + case NF_KEY_NN: // NN + case NF_KEY_AAA: // AAA + aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ); + // NatNum12: support at least capitalize, upper, lower, title + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + break; + case NF_KEY_NNN: // NNN + case NF_KEY_AAAA: // AAAA + aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ); + // NatNum12: support variants of preposition, suffixation or article + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + break; + case NF_KEY_NNNN: // NNNN + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); + sBuff.append(rLoc().getLongDateDayOfWeekSep()); + break; + case NF_KEY_WW : // WW + sBuff.append(ImpIntToString( nIx, + rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR ))); + break; + case NF_KEY_G: // G + ImpAppendEraG(sBuff, rCal, nNatNum ); + break; + case NF_KEY_GG: // GG + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum )); + break; + case NF_KEY_GGG: // GGG + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum )); + break; + case NF_KEY_RR: // RR => GGGEE + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum )); + break; + } + } + if ( aOrgCalendar.getLength() ) + { + rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar + } + return bRes; +} + +bool SvNumberformat::ImpGetDateTimeOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sBuff) +{ + using namespace ::com::sun::star::i18n; + bool bRes = false; + + CalendarWrapper& rCal = GetCal(); + if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber)) + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } + + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + bool bInputLine; + sal_Int32 nCntPost; + if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION && + 0 < rInfo.nCntPost && rInfo.nCntPost < 7 ) + { + // round at 7 decimals (+5 of 86400 == 12 significant digits) + bInputLine = true; + nCntPost = 7; + } + else + { + bInputLine = false; + nCntPost = rInfo.nCntPost; + } + double fTime = (fNumber - floor( fNumber )) * 86400.0; + fTime = ::rtl::math::round( fTime, int(nCntPost) ); + if (fTime >= 86400.0) + { + // result of fNumber==x.999999999... rounded up, use correct date/time + fTime -= 86400.0; + fNumber = floor( fNumber + 0.5) + fTime; + } + rCal.setLocalDateTime( fNumber ); + + int nUseMonthCase = 0; // Not decided yet + OUString aOrgCalendar; // empty => not changed yet + double fOrgDateTime(0.0); + bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx] ); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ) ) + { + bOtherCalendar = false; + } + sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum(); + + OUStringBuffer sSecStr; + sal_Int32 nSecPos = 0; // For figure by figure processing + sal_uInt32 nHour, nMin, nSec; + if (!rInfo.bThousand) // No [] format + { + sal_uInt16 nCHour, nCMinute, nCSecond; + double fFractionOfSecond; + tools::Time::GetClock( fNumber, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost); + nHour = nCHour; + nMin = nCMinute; + nSec = nCSecond; + nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx, + (bInputLine ? rInfo.nCntPost : 0)); + } + else + { + sal_uInt32 nSeconds = static_cast<sal_uInt32>(floor( fTime )); + + nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx, + (bInputLine ? rInfo.nCntPost : 0)); + + if (rInfo.nThousand == 3) // [ss] + { + nHour = 0; + nMin = 0; + nSec = nSeconds; + } + else if (rInfo.nThousand == 2) // [mm]:ss + { + nHour = 0; + nMin = nSeconds / 60; + nSec = nSeconds % 60; + } + else if (rInfo.nThousand == 1) // [hh]:mm:ss + { + nHour = nSeconds / 3600; + nMin = (nSeconds%3600) / 60; + nSec = nSeconds%60; + } + else + { + nHour = 0; // TODO What should these values be? + nMin = 0; + nSec = 0; + } + } + sal_Unicode cAmPm = ' '; // a or p + if (rInfo.nCntExp) // AM/PM + { + if (nHour == 0) + { + nHour = 12; + cAmPm = 'a'; + } + else if (nHour < 12) + { + cAmPm = 'a'; + } + else + { + cAmPm = 'p'; + if (nHour > 12) + { + nHour -= 12; + } + } + } + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + sal_Int32 nLen; + OUString aYear; + for (sal_uInt16 i = 0; i < nCnt; i++) + { + switch (rInfo.nTypeArray[i]) + { + case NF_SYMBOLTYPE_CALENDAR : + if ( !aOrgCalendar.getLength() ) + { + aOrgCalendar = rCal.getUniqueID(); + fOrgDateTime = rCal.getDateTime(); + } + rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() ); + rCal.setDateTime( fOrgDateTime ); + ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + break; + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[i].getLength() >= 2) + InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + case NF_SYMBOLTYPE_DATESEP: + case NF_SYMBOLTYPE_TIMESEP: + case NF_SYMBOLTYPE_TIME100SECSEP: + sBuff.append(rInfo.sStrArray[i]); + break; + case NF_SYMBOLTYPE_DIGIT: + nLen = ( bInputLine && i > 0 && + (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING || + rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ? + nCntPost : rInfo.sStrArray[i].getLength() ); + for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j) + { + sBuff.append(sSecStr[ nSecPos ]); + nSecPos++; + } + break; + case NF_KEY_AMPM: // AM/PM + if (cAmPm == 'a') + { + sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM, + AmPmValue::AM, 0 )); + } + else + { + sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM, + AmPmValue::PM, 0 )); + } + break; + case NF_KEY_AP: // A/P + if (cAmPm == 'a') + { + sBuff.append('a'); + } + else + { + sBuff.append('p'); + } + break; + case NF_KEY_MI: // M + sBuff.append(ImpIntToString( nIx, nMin )); + break; + case NF_KEY_MMI: // MM + sBuff.append(ImpIntToString( nIx, nMin, 2 )); + break; + case NF_KEY_H: // H + sBuff.append(ImpIntToString( nIx, nHour )); + break; + case NF_KEY_HH: // HH + sBuff.append(ImpIntToString( nIx, nHour, 2 )); + break; + case NF_KEY_S: // S + sBuff.append(ImpIntToString( nIx, nSec )); + break; + case NF_KEY_SS: // SS + sBuff.append(ImpIntToString( nIx, nSec, 2 )); + break; + case NF_KEY_M: // M + sBuff.append(rCal.getDisplayString( + CalendarDisplayCode::SHORT_MONTH, nNatNum )); + break; + case NF_KEY_MM: // MM + sBuff.append(rCal.getDisplayString( + CalendarDisplayCode::LONG_MONTH, nNatNum )); + break; + case NF_KEY_MMM: // MMM + sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], + static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])), + nNatNum)); + break; + case NF_KEY_MMMM: // MMMM + sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], + static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])), + nNatNum)); + break; + case NF_KEY_MMMMM: // MMMMM + sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], + static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])), + nNatNum)); + break; + case NF_KEY_Q: // Q + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum )); + break; + case NF_KEY_QQ: // QQ + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum )); + break; + case NF_KEY_D: // D + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum )); + break; + case NF_KEY_DD: // DD + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum )); + break; + case NF_KEY_DDD: // DDD + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum )); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_DDDD: // DDDD + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_YY: // YY + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_YYYY: // YYYY + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } + aYear = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ); + if (aYear.getLength() < 4 && !lcl_hasEra(NumFor[nIx])) + { + using namespace comphelper::string; + // Ensure that year consists of at least 4 digits, so it + // can be distinguished from 2 digits display and edited + // without suddenly being hit by the 2-digit year magic. + OUStringBuffer aBuf; + padToLength(aBuf, 4 - aYear.getLength(), '0'); + impTransliterate(aBuf, NumFor[nIx].GetNatNum()); + aBuf.append(aYear); + sBuff.append(aBuf); + } + else + { + sBuff.append(aYear); + } + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_EC: // E + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); + break; + case NF_KEY_EEC: // EE + case NF_KEY_R: // R + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum )); + break; + case NF_KEY_NN: // NN + case NF_KEY_AAA: // AAA + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum )); + break; + case NF_KEY_NNN: // NNN + case NF_KEY_AAAA: // AAAA + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); + break; + case NF_KEY_NNNN: // NNNN + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); + sBuff.append(rLoc().getLongDateDayOfWeekSep()); + break; + case NF_KEY_WW : // WW + sBuff.append(ImpIntToString( nIx, rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR ))); + break; + case NF_KEY_G: // G + ImpAppendEraG( sBuff, rCal, nNatNum ); + break; + case NF_KEY_GG: // GG + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum )); + break; + case NF_KEY_GGG: // GGG + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum )); + break; + case NF_KEY_RR: // RR => GGGEE + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum )); + break; + } + } + if ( aOrgCalendar.getLength() ) + { + rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar + } + return bRes; +} + +bool SvNumberformat::ImpGetLogicalOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sStr) +{ + bool bRes = false; + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + for (sal_uInt16 j = 0; j < nCnt; ++j) + { + switch (rInfo.nTypeArray[j]) + { + case NF_KEY_BOOLEAN: + sStr.append( fNumber ? rScan.GetTrueString() : rScan.GetFalseString()); + break; + case NF_SYMBOLTYPE_STRING: + sStr.append( rInfo.sStrArray[j]); + break; + } + } + impTransliterate(sStr, NumFor[nIx].GetNatNum()); + return bRes; +} + +bool SvNumberformat::ImpGetNumberOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sStr) +{ + bool bRes = false; + bool bSign; + if (fNumber < 0.0) + { + bSign = (nIx == 0); // Not in the ones at the back; + fNumber = -fNumber; + } + else + { + bSign = false; + if ( std::signbit( fNumber ) ) + { + fNumber = -fNumber; // yes, -0.0 is possible, eliminate '-' + } + } + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + if (rInfo.eScannedType == SvNumFormatType::PERCENT) + { + if (fNumber < D_MAX_D_BY_100) + { + fNumber *= 100.0; + } + else + { + sStr = ImpSvNumberformatScan::sErrStr; + return false; + } + } + sal_uInt16 i, j; + sal_Int32 nDecPos = -1; + bool bInteger = false; + if ( rInfo.nThousand != FLAG_STANDARD_IN_FORMAT ) + { + // Special formatting only if no GENERAL keyword in format code + const sal_uInt16 nThousand = rInfo.nThousand; + tools::Long nPrecExp; + for (i = 0; i < nThousand; i++) + { + if (fNumber > D_MIN_M_BY_1000) + { + fNumber /= 1000.0; + } + else + { + fNumber = 0.0; + } + } + if (fNumber > 0.0) + { + nPrecExp = GetPrecExp( fNumber ); + } + else + { + nPrecExp = 0; + } + if (rInfo.nCntPost) // Decimal places + { + if ((rInfo.nCntPost + nPrecExp) > 15 && nPrecExp < 15) + { + sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 15-nPrecExp, '.'); + for (tools::Long l = 15-nPrecExp; l < static_cast<tools::Long>(rInfo.nCntPost); l++) + { + sStr.append('0'); + } + } + else + { + sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, rInfo.nCntPost, '.' ); + } + sStr.stripStart('0'); // Strip leading zeros + } + else if (fNumber == 0.0) // Null + { + // Nothing to be done here, keep empty string sStr, + // ImpNumberFillWithThousands does the rest + } + else // Integer + { + sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 0, '.'); + sStr.stripStart('0'); // Strip leading zeros + } + nDecPos = sStr.indexOf('.' ); + if ( nDecPos >= 0) + { + const sal_Unicode* p = sStr.getStr() + nDecPos; + while ( *++p == '0' ) + ; + if ( !*p ) + { + bInteger = true; + } + sStr.remove( nDecPos, 1 ); // Remove . + } + if (bSign && (sStr.isEmpty() || checkForAll0s(sStr))) // Only 00000 + { + bSign = false; // Not -0.00 + } + } // End of != FLAG_STANDARD_IN_FORMAT + + // Edit backwards: + j = NumFor[nIx].GetCount()-1; // Last symbol + // Decimal places: + bRes |= ImpDecimalFill( sStr, fNumber, nDecPos, j, nIx, bInteger ); + if (bSign) + { + sStr.insert(0, '-'); + } + impTransliterate(sStr, NumFor[nIx].GetNatNum()); + return bRes; +} + +bool SvNumberformat::ImpDecimalFill( OUStringBuffer& sStr, // number string + double& rNumber, // number + sal_Int32 nDecPos, // decimals start + sal_uInt16 j, // symbol index within format code + sal_uInt16 nIx, // subformat index + bool bInteger) // is integer +{ + bool bRes = false; + bool bFilled = false; // Was filled? + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + sal_Int32 k = sStr.getLength(); // After last figure + // Decimal places: + if (rInfo.nCntPost > 0) + { + bool bTrailing = true; // Trailing zeros? + short nType; + while (j > 0 && // Backwards + (nType = rInfo.nTypeArray[j]) != NF_SYMBOLTYPE_DECSEP) + { + switch ( nType ) + { + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_insertStarFillChar( sStr, k, rInfo.sStrArray[j]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[j].getLength() >= 2) + /*k = */ InsertBlanks(sStr, k, rInfo.sStrArray[j][1] ); + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + case NF_SYMBOLTYPE_PERCENT: + sStr.insert(k, rInfo.sStrArray[j]); + break; + case NF_SYMBOLTYPE_THSEP: + if (rInfo.nThousand == 0) + { + sStr.insert(k, rInfo.sStrArray[j]); + } + break; + case NF_SYMBOLTYPE_DIGIT: + { + const OUString& rStr = rInfo.sStrArray[j]; + const sal_Unicode* p1 = rStr.getStr(); + const sal_Unicode* p = p1 + rStr.getLength(); + // In case the number of decimals passed are less than the + // "digits" given, append trailing '0' characters, which here + // means insert them because literal strings may have been + // appended already. If they weren't to be '0' characters + // they'll be changed below, as if decimals with trailing zeros + // were passed. + if (nDecPos >= 0 && nDecPos <= k) + { + sal_Int32 nAppend = rStr.getLength() - (k - nDecPos); + while (nAppend-- > 0) + { + sStr.insert( k++, '0'); + } + } + while (k && p1 < p--) + { + const sal_Unicode c = *p; + k--; + if ( sStr[k] != '0' ) + { + bTrailing = false; + bFilled = true; + } + if (bTrailing) + { + if ( c == '0' ) + { + bFilled = true; + } + else if ( c == '-' ) + { + if ( bInteger ) + { + sStr[ k ] = '-'; + } + bFilled = true; + } + else if ( c == '?' ) + { + sStr[ k ] = ' '; + bFilled = true; + } + else if ( !bFilled ) // # + { + sStr.remove(k,1); + } + } + } // of for + break; + } // of case digi + case NF_KEY_CCC: // CCC currency + sStr.insert(k, rScan.GetCurAbbrev()); + break; + case NF_KEY_GENERAL: // Standard in the String + { + OUStringBuffer sNum; + ImpGetOutputStandard(rNumber, sNum); + sNum.stripStart('-'); + sStr.insert(k, sNum.makeStringAndClear()); + break; + } + default: + break; + } // of switch + j--; + } // of while + } // of decimal places + + bRes |= ImpNumberFillWithThousands(sStr, rNumber, k, j, nIx, // Fill with . if needed + rInfo.nCntPre, bFilled ); + + return bRes; +} + +bool SvNumberformat::ImpNumberFillWithThousands( OUStringBuffer& sBuff, // number string + double& rNumber, // number + sal_Int32 k, // position within string + sal_uInt16 j, // symbol index within format code + sal_uInt16 nIx, // subformat index + sal_Int32 nDigCnt, // count of integer digits in format + bool bAddDecSep) // add decimal separator if necessary +{ + bool bRes = false; + sal_Int32 nLeadingStringChars = 0; // inserted StringChars before number + sal_Int32 nDigitCount = 0; // count of integer digits from the right + bool bStop = false; + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + // no normal thousands separators if number divided by thousands + bool bDoThousands = (rInfo.nThousand == 0); + utl::DigitGroupingIterator aGrouping( GetFormatter().GetLocaleData()->getDigitGrouping()); + + while (!bStop) // backwards + { + if (j == 0) + { + bStop = true; + } + switch (rInfo.nTypeArray[j]) + { + case NF_SYMBOLTYPE_DECSEP: + aGrouping.reset(); + [[fallthrough]]; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + case NF_SYMBOLTYPE_PERCENT: + if ( rInfo.nTypeArray[j] != NF_SYMBOLTYPE_DECSEP || bAddDecSep ) + sBuff.insert(k, rInfo.sStrArray[j]); + if ( k == 0 ) + { + nLeadingStringChars = nLeadingStringChars + rInfo.sStrArray[j].getLength(); + } + break; + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[j].getLength() >= 2) + /*k = */ InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] ); + break; + case NF_SYMBOLTYPE_THSEP: + // #i7284# #102685# Insert separator also if number is divided + // by thousands and the separator is specified somewhere in + // between and not only at the end. + // #i12596# But do not insert if it's a parenthesized negative + // format like (#,) + // In fact, do not insert if divided and regex [0#,],[^0#] and + // no other digit symbol follows (which was already detected + // during scan of format code, otherwise there would be no + // division), else do insert. Same in ImpNumberFill() below. + if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 ) + { + bDoThousands = ((j == 0) || + (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT && + rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) || + (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT)); + } + if ( bDoThousands ) + { + if (k > 0) + { + sBuff.insert(k, rInfo.sStrArray[j]); + } + else if (nDigitCount < nDigCnt) + { + // Leading '#' displays nothing (e.g. no leading + // separator for numbers <1000 with #,##0 format). + // Leading '?' displays blank. + // Everything else, including nothing, displays the + // separator. + sal_Unicode cLeader = 0; + if (j > 0 && rInfo.nTypeArray[j-1] == NF_SYMBOLTYPE_DIGIT) + { + const OUString& rStr = rInfo.sStrArray[j-1]; + sal_Int32 nLen = rStr.getLength(); + if (nLen) + { + cLeader = rStr[ nLen - 1 ]; + } + } + switch (cLeader) + { + case '#': + ; // nothing + break; + case '?': + // replace thousand separator with blank + sBuff.insert(k, ' '); + break; + default: + sBuff.insert(k, rInfo.sStrArray[j]); + } + } + aGrouping.advance(); + } + break; + case NF_SYMBOLTYPE_DIGIT: + { + const OUString& rStr = rInfo.sStrArray[j]; + const sal_Unicode* p1 = rStr.getStr(); + const sal_Unicode* p = p1 + rStr.getLength(); + while ( p1 < p-- ) + { + nDigitCount++; + if (k > 0) + { + k--; + } + else + { + switch (*p) + { + case '0': + sBuff.insert(0, '0'); + break; + case '?': + sBuff.insert(0, ' '); + break; + } + } + if (nDigitCount == nDigCnt && k > 0) + { + // more digits than specified + ImpDigitFill(sBuff, 0, k, nIx, nDigitCount, aGrouping); + } + } + break; + } + case NF_KEY_CCC: // CCC currency + sBuff.insert(k, rScan.GetCurAbbrev()); + break; + case NF_KEY_GENERAL: // "General" in string + { + OUStringBuffer sNum; + ImpGetOutputStandard(rNumber, sNum); + sNum.stripStart('-'); + sBuff.insert(k, sNum.makeStringAndClear()); + break; + } + default: + break; + } // switch + j--; // next format code string + } // while + + k = k + nLeadingStringChars; // MSC converts += to int and then warns, so ... + if (k > nLeadingStringChars) + { + ImpDigitFill(sBuff, nLeadingStringChars, k, nIx, nDigitCount, aGrouping); + } + return bRes; +} + +void SvNumberformat::ImpDigitFill(OUStringBuffer& sStr, // number string + sal_Int32 nStart, // start of digits + sal_Int32 & k, // position within string + sal_uInt16 nIx, // subformat index + sal_Int32 & nDigitCount, // count of integer digits from the right so far + utl::DigitGroupingIterator & rGrouping ) // current grouping +{ + if (NumFor[nIx].Info().bThousand) // Only if grouping fill in separators + { + const OUString& rThousandSep = GetFormatter().GetNumThousandSep(); + while (k > nStart) + { + if (nDigitCount == rGrouping.getPos()) + { + sStr.insert( k, rThousandSep ); + rGrouping.advance(); + } + nDigitCount++; + k--; + } + } + else // simply skip + { + k = nStart; + } +} + +bool SvNumberformat::ImpNumberFill( OUStringBuffer& sBuff, // number string + double& rNumber, // number for "General" format + sal_Int32& k, // position within string + sal_uInt16& j, // symbol index within format code + sal_uInt16 nIx, // subformat index + short eSymbolType, // type of stop condition + bool bInsertRightBlank)// insert blank on right for denominator (default = false) +{ + bool bRes = false; + bool bStop = false; + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + // no normal thousands separators if number divided by thousands + bool bDoThousands = (rInfo.nThousand == 0); + bool bFoundNumber = false; + short nType; + + k = sBuff.getLength(); // behind last digit + + while (!bStop && (nType = rInfo.nTypeArray[j]) != eSymbolType ) // Backwards + { + switch ( nType ) + { + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP ) + k = 0; // tdf#100842 jump to beginning of number before inserting something else + bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[j].getLength() >= 2) + { + if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP ) + k = 0; // tdf#100842 jump to beginning of number before inserting something else + k = InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] ); + } + break; + case NF_SYMBOLTYPE_THSEP: + // Same as in ImpNumberFillWithThousands() above, do not insert + // if divided and regex [0#,],[^0#] and no other digit symbol + // follows (which was already detected during scan of format + // code, otherwise there would be no division), else do insert. + if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 ) + { + bDoThousands = ((j == 0) || + (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT && + rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) || + (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT)); + } + if ( bDoThousands && k > 0 ) + { + sBuff.insert(k, rInfo.sStrArray[j]); + } + break; + case NF_SYMBOLTYPE_DIGIT: + { + bFoundNumber = true; + sal_uInt16 nPosInsertBlank = bInsertRightBlank ? k : 0; // left alignment of denominator + const OUString& rStr = rInfo.sStrArray[j]; + const sal_Unicode* p1 = rStr.getStr(); + const sal_Unicode* p = p1 + rStr.getLength(); + while ( p1 < p-- ) + { + if (k > 0) + { + k--; + } + else + { + switch (*p) + { + case '0': + sBuff.insert(0, '0'); + break; + case '?': + sBuff.insert(nPosInsertBlank, ' '); + break; + } + } + } + } + break; + case NF_KEY_CCC: // CCC currency + sBuff.insert(k, rScan.GetCurAbbrev()); + break; + case NF_KEY_GENERAL: // Standard in the String + { + OUStringBuffer sNum; + bFoundNumber = true; + ImpGetOutputStandard(rNumber, sNum); + sNum.stripStart('-'); + sBuff.insert(k, sNum.makeStringAndClear()); + } + break; + case NF_SYMBOLTYPE_FRAC_FDIV: // Do Nothing + if (k > 0) + { + k--; + } + break; + + default: + if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP ) + k = 0; // tdf#100842 jump to beginning of number before inserting something else + sBuff.insert(k, rInfo.sStrArray[j]); + break; + } // of switch + if ( j ) + j--; // Next String + else + bStop = true; + } // of while + return bRes; +} + +void SvNumberformat::GetFormatSpecialInfo(bool& bThousand, + bool& IsRed, + sal_uInt16& nPrecision, + sal_uInt16& nLeadingCnt) const +{ + // as before: take info from nNumFor=0 for whole format (for dialog etc.) + + SvNumFormatType nDummyType; + GetNumForInfo( 0, nDummyType, bThousand, nPrecision, nLeadingCnt ); + + // "negative in red" is only useful for the whole format + + const Color* pColor = NumFor[1].GetColor(); + IsRed = fLimit1 == 0.0 && fLimit2 == 0.0 && pColor + && (*pColor == ImpSvNumberformatScan::GetRedColor()); +} + +void SvNumberformat::GetNumForInfo( sal_uInt16 nNumFor, SvNumFormatType& rScannedType, + bool& bThousand, sal_uInt16& nPrecision, sal_uInt16& nLeadingCnt ) const +{ + // take info from a specified sub-format (for XML export) + + if ( nNumFor > 3 ) + { + return; // invalid + } + + const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); + rScannedType = rInfo.eScannedType; + bThousand = rInfo.bThousand; + nPrecision = (rInfo.eScannedType == SvNumFormatType::FRACTION) + ? rInfo.nCntExp // number of denominator digits for fraction + : rInfo.nCntPost; + sal_Int32 nPosHash = 1; + if ( rInfo.eScannedType == SvNumFormatType::FRACTION && + ( (nPosHash += GetDenominatorString(nNumFor).indexOf('#')) > 0 ) ) + nPrecision -= nPosHash; + if (bStandard && rInfo.eScannedType == SvNumFormatType::NUMBER) + { + // StandardFormat + nLeadingCnt = 1; + } + else + { + nLeadingCnt = 0; + bool bStop = false; + sal_uInt16 i = 0; + const sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + while (!bStop && i < nCnt) + { + short nType = rInfo.nTypeArray[i]; + if ( nType == NF_SYMBOLTYPE_DIGIT) + { + const sal_Unicode* p = rInfo.sStrArray[i].getStr(); + while ( *p == '#' ) + { + p++; + } + while ( *p++ == '0' ) + { + nLeadingCnt++; + } + } + else if (nType == NF_SYMBOLTYPE_DECSEP + || nType == NF_SYMBOLTYPE_EXP + || nType == NF_SYMBOLTYPE_FRACBLANK) // Fraction: stop after integer part, + { // do not count '0' of fraction + bStop = true; + } + i++; + } + } +} + +const OUString* SvNumberformat::GetNumForString( sal_uInt16 nNumFor, sal_uInt16 nPos, + bool bString /* = false */ ) const +{ + if ( nNumFor > 3 ) + { + return nullptr; + } + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + if ( !nCnt ) + { + return nullptr; + } + if ( nPos == 0xFFFF ) + { + nPos = nCnt - 1; + if ( bString ) + { // Backwards + short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos; + while ( nPos > 0 && (*pType != NF_SYMBOLTYPE_STRING) && + (*pType != NF_SYMBOLTYPE_CURRENCY) ) + { + pType--; + nPos--; + } + if ( (*pType != NF_SYMBOLTYPE_STRING) && (*pType != NF_SYMBOLTYPE_CURRENCY) ) + { + return nullptr; + } + } + } + else if ( nPos > nCnt - 1 ) + { + return nullptr; + } + else if ( bString ) + { + // forward + short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos; + while ( nPos < nCnt && (*pType != NF_SYMBOLTYPE_STRING) && + (*pType != NF_SYMBOLTYPE_CURRENCY) ) + { + pType++; + nPos++; + } + if ( nPos >= nCnt || ((*pType != NF_SYMBOLTYPE_STRING) && + (*pType != NF_SYMBOLTYPE_CURRENCY)) ) + { + return nullptr; + } + } + return &NumFor[nNumFor].Info().sStrArray[nPos]; +} + +short SvNumberformat::GetNumForType( sal_uInt16 nNumFor, sal_uInt16 nPos ) const +{ + if ( nNumFor > 3 ) + { + return 0; + } + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + if ( !nCnt ) + { + return 0; + } + if ( nPos == 0xFFFF ) + { + nPos = nCnt - 1; + } + else if ( nPos > nCnt - 1 ) + { + return 0; + } + return NumFor[nNumFor].Info().nTypeArray[nPos]; +} + +bool SvNumberformat::IsNegativeWithoutSign() const +{ + if ( IsSecondSubformatRealNegative() ) + { + const OUString* pStr = GetNumForString( 1, 0, true ); + if ( pStr ) + { + return !HasStringNegativeSign( *pStr ); + } + } + return false; +} + +bool SvNumberformat::IsNegativeInBracket() const +{ + sal_uInt16 nCnt = NumFor[1].GetCount(); + if (!nCnt) + { + return false; + } + auto& tmp = NumFor[1].Info().sStrArray; + return tmp[0] == "(" && tmp[nCnt-1] == ")"; +} + +bool SvNumberformat::HasPositiveBracketPlaceholder() const +{ + sal_uInt16 nCnt = NumFor[0].GetCount(); + return NumFor[0].Info().sStrArray[nCnt-1] == "_)"; +} + +DateOrder SvNumberformat::GetDateOrder() const +{ + if ( eType & SvNumFormatType::DATE ) + { + auto& rTypeArray = NumFor[0].Info().nTypeArray; + sal_uInt16 nCnt = NumFor[0].GetCount(); + for ( sal_uInt16 j=0; j<nCnt; j++ ) + { + switch ( rTypeArray[j] ) + { + case NF_KEY_D : + case NF_KEY_DD : + return DateOrder::DMY; + case NF_KEY_M : + case NF_KEY_MM : + case NF_KEY_MMM : + case NF_KEY_MMMM : + case NF_KEY_MMMMM : + return DateOrder::MDY; + case NF_KEY_YY : + case NF_KEY_YYYY : + case NF_KEY_EC : + case NF_KEY_EEC : + case NF_KEY_R : + case NF_KEY_RR : + return DateOrder::YMD; + } + } + } + else + { + SAL_WARN( "svl.numbers", "SvNumberformat::GetDateOrder: no date" ); + } + return rLoc().getDateOrder(); +} + +sal_uInt32 SvNumberformat::GetExactDateOrder() const +{ + sal_uInt32 nRet = 0; + if ( !(eType & SvNumFormatType::DATE) ) + { + SAL_WARN( "svl.numbers", "SvNumberformat::GetExactDateOrder: no date" ); + return nRet; + } + auto& rTypeArray = NumFor[0].Info().nTypeArray; + sal_uInt16 nCnt = NumFor[0].GetCount(); + int nShift = 0; + for ( sal_uInt16 j=0; j<nCnt && nShift < 3; j++ ) + { + switch ( rTypeArray[j] ) + { + case NF_KEY_D : + case NF_KEY_DD : + nRet = (nRet << 8) | 'D'; + ++nShift; + break; + case NF_KEY_M : + case NF_KEY_MM : + case NF_KEY_MMM : + case NF_KEY_MMMM : + case NF_KEY_MMMMM : + nRet = (nRet << 8) | 'M'; + ++nShift; + break; + case NF_KEY_YY : + case NF_KEY_YYYY : + case NF_KEY_EC : + case NF_KEY_EEC : + case NF_KEY_R : + case NF_KEY_RR : + nRet = (nRet << 8) | 'Y'; + ++nShift; + break; + } + } + return nRet; +} + +void SvNumberformat::GetConditions( SvNumberformatLimitOps& rOper1, double& rVal1, + SvNumberformatLimitOps& rOper2, double& rVal2 ) const +{ + rOper1 = eOp1; + rOper2 = eOp2; + rVal1 = fLimit1; + rVal2 = fLimit2; +} + +const Color* SvNumberformat::GetColor( sal_uInt16 nNumFor ) const +{ + if ( nNumFor > 3 ) + { + return nullptr; + } + return NumFor[nNumFor].GetColor(); +} + +static void lcl_SvNumberformat_AddLimitStringImpl( OUString& rStr, + SvNumberformatLimitOps eOp, + double fLimit, std::u16string_view rDecSep ) +{ + if ( eOp == NUMBERFORMAT_OP_NO ) + return; + + switch ( eOp ) + { + case NUMBERFORMAT_OP_EQ : + rStr = "[="; + break; + case NUMBERFORMAT_OP_NE : + rStr = "[<>"; + break; + case NUMBERFORMAT_OP_LT : + rStr = "[<"; + break; + case NUMBERFORMAT_OP_LE : + rStr = "[<="; + break; + case NUMBERFORMAT_OP_GT : + rStr = "[>"; + break; + case NUMBERFORMAT_OP_GE : + rStr = "[>="; + break; + default: + SAL_WARN( "svl.numbers", "unsupported number format" ); + break; + } + rStr += ::rtl::math::doubleToUString( fLimit, + rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, + rDecSep[0], true); + rStr += "]"; +} + +static void lcl_insertLCID( OUStringBuffer& rFormatStr, sal_uInt32 nLCID, sal_Int32 nPosInsertLCID, bool bDBNumInserted ) +{ + if ( nLCID == 0 ) + return; + if (nPosInsertLCID == rFormatStr.getLength() && !bDBNumInserted) + // No format code, no locale. + return; + + OUStringBuffer aLCIDString = OUString::number( nLCID , 16 ).toAsciiUpperCase(); + // Search for only last DBNum which is the last element before insertion position + if ( bDBNumInserted && nPosInsertLCID >= 8 + && aLCIDString.getLength() > 4 + && rFormatStr.indexOf( "[DBNum", nPosInsertLCID-8) == nPosInsertLCID-8 ) + { // remove DBNumX code if long LCID + nPosInsertLCID -= 8; + rFormatStr.remove( nPosInsertLCID, 8 ); + } + aLCIDString.insert( 0, "[$-" ); + aLCIDString.append( "]" ); + rFormatStr.insert( nPosInsertLCID, aLCIDString.makeStringAndClear() ); +} + +/** Increment nAlphabetID for CJK numerals + * +1 for financial numerals [NatNum2] + * +2 for Arabic fullwidth numerals [NatNum3] + * */ +static void lcl_incrementAlphabetWithNatNum ( sal_uInt32& nAlphabetID, sal_uInt32 nNatNum ) +{ + if ( nNatNum == 2) // financial + nAlphabetID += 1; + else if ( nNatNum == 3) + nAlphabetID += 2; + nAlphabetID = nAlphabetID << 24; +} + +OUString SvNumberformat::GetMappedFormatstring( const NfKeywordTable& rKeywords, + const LocaleDataWrapper& rLocWrp, + LanguageType nOriginalLang /* =LANGUAGE_DONTKNOW */, + bool bSystemLanguage /* =false */ ) const +{ + OUStringBuffer aStr; + if (maLocale.meSubstitute != LocaleType::Substitute::NONE) + { + // XXX: theoretically this could clash with the first subformat's + // lcl_insertLCID() below, in practice as long as it is used for system + // time and date modifiers it shouldn't (i.e. there is no calendar or + // numeral specified as well). + aStr.append("[$-" + maLocale.generateCode() + "]"); + } + bool bDefault[4]; + // 1 subformat matches all if no condition specified, + bDefault[0] = ( NumFor[1].GetCount() == 0 && eOp1 == NUMBERFORMAT_OP_NO ); + // with 2 subformats [>=0];[<0] is implied if no condition specified + bDefault[1] = ( !bDefault[0] && NumFor[2].GetCount() == 0 && + eOp1 == NUMBERFORMAT_OP_GE && fLimit1 == 0.0 && + eOp2 == NUMBERFORMAT_OP_NO && fLimit2 == 0.0 ); + // with 3 or more subformats [>0];[<0];[=0] is implied if no condition specified, + // note that subformats may be empty (;;;) and NumFor[2].GetCount()>0 is not checked. + bDefault[2] = ( !bDefault[0] && !bDefault[1] && + eOp1 == NUMBERFORMAT_OP_GT && fLimit1 == 0.0 && + eOp2 == NUMBERFORMAT_OP_LT && fLimit2 == 0.0 ); + bool bDefaults = bDefault[0] || bDefault[1] || bDefault[2]; + // from now on bDefault[] values are used to append empty subformats at the end + bDefault[3] = false; + if ( !bDefaults ) + { + // conditions specified + if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 == NUMBERFORMAT_OP_NO ) + { + bDefault[0] = bDefault[1] = true; // [];x + } + else if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 != NUMBERFORMAT_OP_NO && + NumFor[2].GetCount() == 0 ) + { + bDefault[0] = bDefault[1] = bDefault[2] = bDefault[3] = true; // [];[];; + } + // nothing to do if conditions specified for every subformat + } + else if ( bDefault[0] ) + { + bDefault[0] = false; // a single unconditional subformat is never delimited + } + else + { + if ( bDefault[2] && NumFor[2].GetCount() == 0 && NumFor[1].GetCount() > 0 ) + { + bDefault[3] = true; // special cases x;x;; and ;x;; + } + for ( int i=0; i<3 && !bDefault[i]; ++i ) + { + bDefault[i] = true; + } + } + int nSem = 0; // needed ';' delimiters + int nSub = 0; // subformats delimited so far + for ( int n=0; n<4; n++ ) + { + if ( n > 0 && NumFor[n].Info().eScannedType != SvNumFormatType::UNDEFINED ) + { + nSem++; + } + OUString aPrefix; + + if ( !bDefaults ) + { + switch ( n ) + { + case 0 : + lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp1, + fLimit1, rLocWrp.getNumDecimalSep() ); + break; + case 1 : + lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp2, + fLimit2, rLocWrp.getNumDecimalSep() ); + break; + } + } + + const OUString& rColorName = NumFor[n].GetColorName(); + if ( !rColorName.isEmpty() ) + { + const NfKeywordTable & rKey = rScan.GetKeywords(); + for ( int j = NF_KEY_FIRSTCOLOR; j <= NF_KEY_LASTCOLOR; j++ ) + { + if ( rKey[j] == rColorName ) + { + aPrefix += "[" + rKeywords[j] + "]"; + break; // for + } + } + } + + SvNumberNatNum aNatNum = NumFor[n].GetNatNum(); + bool bDBNumInserted = false; + if (aNatNum.IsComplete() && (aNatNum.GetDBNum() > 0 || nOriginalLang != LANGUAGE_DONTKNOW)) + { // GetFormatStringForExcel() may have changed language to en_US + if (aNatNum.GetLang() == LANGUAGE_ENGLISH_US && nOriginalLang != LANGUAGE_DONTKNOW) + aNatNum.SetLang( nOriginalLang ); + if ( aNatNum.GetDBNum() > 0 ) + { + aPrefix += "[DBNum" + OUString::number( aNatNum.GetDBNum() ) + "]"; + bDBNumInserted = true; + } + } + + sal_uInt16 nCnt = NumFor[n].GetCount(); + if ( nSem && (nCnt || !aPrefix.isEmpty()) ) + { + for ( ; nSem; --nSem ) + { + aStr.append( ';' ); + } + for ( ; nSub <= n; ++nSub ) + { + bDefault[nSub] = false; + } + } + + if ( !aPrefix.isEmpty() ) + { + aStr.append( aPrefix ); + } + sal_Int32 nPosHaveLCID = -1; + sal_Int32 nPosInsertLCID = aStr.getLength(); + sal_uInt32 nCalendarID = 0x0000000; // Excel ID of calendar used in sub-format see tdf#36038 + constexpr sal_uInt32 kCalGengou = 0x0030000; + if ( nCnt ) + { + auto& rTypeArray = NumFor[n].Info().nTypeArray; + auto& rStrArray = NumFor[n].Info().sStrArray; + for ( sal_uInt16 j=0; j<nCnt; j++ ) + { + if ( 0 <= rTypeArray[j] && rTypeArray[j] < NF_KEYWORD_ENTRIES_COUNT ) + { + aStr.append( rKeywords[rTypeArray[j]] ); + if( NF_KEY_NNNN == rTypeArray[j] ) + { + aStr.append( rLocWrp.getLongDateDayOfWeekSep() ); + } + switch (rTypeArray[j]) + { + case NF_KEY_EC: + case NF_KEY_EEC: + case NF_KEY_R: + case NF_KEY_RR: + // Implicit secondary (non-gregorian) calendar. + // Currently only for ja-JP. + /* TODO: same for all locales in + * LocaleDataWrapper::doesSecondaryCalendarUseEC() ? + * Should split the locales off that then. */ + if (!nCalendarID) + { + const LanguageType nLang = MsLangId::getRealLanguage( nOriginalLang); + if (nLang == LANGUAGE_JAPANESE) + nCalendarID = kCalGengou; + } + break; + default: + ; // nothing + } + } + else + { + switch ( rTypeArray[j] ) + { + case NF_SYMBOLTYPE_DECSEP : + aStr.append( rLocWrp.getNumDecimalSep() ); + break; + case NF_SYMBOLTYPE_THSEP : + aStr.append( rLocWrp.getNumThousandSep() ); + break; + case NF_SYMBOLTYPE_EXP : + aStr.append( rKeywords[NF_KEY_E] ); + if ( rStrArray[j].getLength() > 1 && rStrArray[j][1] == '+' ) + aStr.append( "+" ); + else + // tdf#102370: Excel code for exponent without sign + aStr.append( "-" ); + break; + case NF_SYMBOLTYPE_DATESEP : + aStr.append( rLocWrp.getDateSep() ); + break; + case NF_SYMBOLTYPE_TIMESEP : + aStr.append( rLocWrp.getTimeSep() ); + break; + case NF_SYMBOLTYPE_TIME100SECSEP : + aStr.append( rLocWrp.getTime100SecSep() ); + break; + case NF_SYMBOLTYPE_FRACBLANK : + case NF_SYMBOLTYPE_STRING : + if ( rStrArray[j].getLength() == 1 ) + { + if ( rTypeArray[j] == NF_SYMBOLTYPE_STRING ) + aStr.append( '\\' ); + aStr.append( rStrArray[j] ); + } + else + { + aStr.append( '"' ); + aStr.append( rStrArray[j] ); + aStr.append( '"' ); + } + break; + case NF_SYMBOLTYPE_CALDEL : + if (j + 1 >= nCnt) + break; + if ( rStrArray[j+1] == "gengou" ) + { + nCalendarID = kCalGengou; + } + else if ( rStrArray[j+1] == "hijri" ) + { + nCalendarID = 0x0060000; + } + else if ( rStrArray[j+1] == "buddhist" ) + { + nCalendarID = 0x0070000; + } + else if ( rStrArray[j+1] == "jewish" ) + { + nCalendarID = 0x0080000; + } + // Other calendars (see tdf#36038) not corresponding between LibO and XL. + // However, skip any calendar modifier and don't write + // as format code (if not as literal string). + j += 2; + break; + case NF_SYMBOLTYPE_CURREXT : + nPosHaveLCID = aStr.getLength(); + aStr.append( rStrArray[j] ); + break; + default: + aStr.append( rStrArray[j] ); + } + } + } + } + sal_uInt32 nAlphabetID = 0x0000000; // Excel ID of alphabet used for numerals see tdf#36038 + LanguageType nLanguageID = LANGUAGE_SYSTEM; + if ( aNatNum.IsComplete() ) + { + nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang()); + if ( aNatNum.GetNatNum() == 0 ) + { + nAlphabetID = 0x01000000; // Arabic-european numerals + } + else if ( nCalendarID > 0 || aNatNum.GetDBNum() == 0 || aNatNum.GetDBNum() == aNatNum.GetNatNum() ) + { // if no DBNum code then use long LCID + // if DBNum value != NatNum value, use DBNum and not extended LCID + // if calendar, then DBNum will be removed + LanguageType pri = primary(nLanguageID); + if ( pri == LANGUAGE_ARABIC_PRIMARY_ONLY ) + nAlphabetID = 0x02000000; // Arabic-indic numerals + else if ( pri == primary(LANGUAGE_FARSI) ) + nAlphabetID = 0x03000000; // Farsi numerals + else if ( pri.anyOf( + primary(LANGUAGE_HINDI), + primary(LANGUAGE_MARATHI), + primary(LANGUAGE_NEPALI) )) + nAlphabetID = 0x04000000; // Devanagari numerals + else if ( pri == primary(LANGUAGE_BENGALI) ) + nAlphabetID = 0x05000000; // Bengali numerals + else if ( pri == primary(LANGUAGE_PUNJABI) ) + { + if ( nLanguageID == LANGUAGE_PUNJABI_ARABIC_LSO ) + nAlphabetID = 0x02000000; // Arabic-indic numerals + else + nAlphabetID = 0x06000000; // Punjabi numerals + } + else if ( pri == primary(LANGUAGE_GUJARATI) ) + nAlphabetID = 0x07000000; // Gujarati numerals + else if ( pri == primary(LANGUAGE_ODIA)) + nAlphabetID = 0x08000000; // Odia (Oriya) numerals + else if ( pri == primary(LANGUAGE_TAMIL)) + nAlphabetID = 0x09000000; // Tamil numerals + else if ( pri == primary(LANGUAGE_TELUGU)) + nAlphabetID = 0x0A000000; // Telugu numerals + else if ( pri == primary(LANGUAGE_KANNADA)) + nAlphabetID = 0x0B000000; // Kannada numerals + else if ( pri == primary(LANGUAGE_MALAYALAM)) + nAlphabetID = 0x0C000000; // Malayalam numerals + else if ( pri == primary(LANGUAGE_THAI)) + { + // The Thai T NatNum modifier during Xcl export. + if ( rKeywords[NF_KEY_THAI_T] == "T" ) + nAlphabetID = 0x0D000000; // Thai numerals + } + else if ( pri == primary(LANGUAGE_LAO)) + nAlphabetID = 0x0E000000; // Lao numerals + else if ( pri == primary(LANGUAGE_TIBETAN)) + nAlphabetID = 0x0F000000; // Tibetan numerals + else if ( pri == primary(LANGUAGE_BURMESE)) + nAlphabetID = 0x10000000; // Burmese numerals + else if ( pri == primary(LANGUAGE_TIGRIGNA_ETHIOPIA)) + nAlphabetID = 0x11000000; // Tigrigna numerals + else if ( pri == primary(LANGUAGE_KHMER)) + nAlphabetID = 0x12000000; // Khmer numerals + else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA)) + { + if ( nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_MONGOLIA + && nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_LSO ) + nAlphabetID = 0x13000000; // Mongolian numerals + } + // CJK numerals + else if ( pri == primary(LANGUAGE_JAPANESE)) + { + nAlphabetID = 0x1B; + lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); + } + else if ( pri == primary(LANGUAGE_CHINESE)) + { + if ( nLanguageID == LANGUAGE_CHINESE_TRADITIONAL + || nLanguageID == LANGUAGE_CHINESE_HONGKONG + || nLanguageID == LANGUAGE_CHINESE_MACAU ) + { + nAlphabetID = 0x21; + lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); + } + else // LANGUAGE_CHINESE_SIMPLIFIED + { + nAlphabetID = 0x1E; + lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); + } + } + else if ( pri == primary(LANGUAGE_KOREAN)) + { + if ( aNatNum.GetNatNum() == 9 ) // Hangul + { + nAlphabetID = 0x27000000; + } + else + { + nAlphabetID = 0x24; + lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); + } + } + } + // Add LCID to DBNum + if ( aNatNum.GetDBNum() > 0 && nLanguageID == LANGUAGE_SYSTEM ) + nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang()); + } + else if (nPosHaveLCID < 0) + { + // Do not insert a duplicated LCID that was already given with a + // currency format as [$R-1C09] + if (!bSystemLanguage && nOriginalLang != LANGUAGE_DONTKNOW) + { + // Explicit locale, write only to the first subformat. + if (n == 0) + nLanguageID = MsLangId::getRealLanguage( nOriginalLang); + } + else if (bSystemLanguage && maLocale.meLanguageWithoutLocaleData != LANGUAGE_DONTKNOW) + { + // Explicit locale but no locale data thus assigned to system + // locale, preserve for roundtrip, write only to the first + // subformat. + if (n == 0) + nLanguageID = maLocale.meLanguageWithoutLocaleData; + } + } + if ( nCalendarID > 0 ) + { // Add alphabet and language to calendar + if ( nAlphabetID == 0 ) + nAlphabetID = 0x01000000; + if ( nLanguageID == LANGUAGE_SYSTEM && nOriginalLang != LANGUAGE_DONTKNOW ) + nLanguageID = nOriginalLang; + } + lcl_insertLCID( aStr, nAlphabetID + nCalendarID + static_cast<sal_uInt16>(nLanguageID), nPosInsertLCID, + bDBNumInserted); + } + for ( ; nSub<4 && bDefault[nSub]; ++nSub ) + { // append empty subformats + aStr.append( ';' ); + } + return aStr.makeStringAndClear(); +} + +OUString SvNumberformat::ImpGetNatNumString( const SvNumberNatNum& rNum, + sal_Int64 nVal, sal_uInt16 nMinDigits ) const +{ + OUString aStr; + if ( nMinDigits ) + { + if ( nMinDigits == 2 ) + { + // speed up the most common case + if ( 0 <= nVal && nVal < 10 ) + { + sal_Unicode aBuf[2]; + aBuf[0] = '0'; + aBuf[1] = '0' + nVal; + aStr = OUString(aBuf, SAL_N_ELEMENTS(aBuf)); + } + else + { + aStr = OUString::number( nVal ); + } + } + else + { + OUString aValStr( OUString::number( nVal ) ); + if ( aValStr.getLength() >= nMinDigits ) + { + aStr = aValStr; + } + else + { + OUStringBuffer aBuf; + for(sal_Int32 index = 0; index < nMinDigits - aValStr.getLength(); ++index) + { + aBuf.append('0'); + } + aBuf.append(aValStr); + aStr = aBuf.makeStringAndClear(); + } + } + } + else + { + aStr = OUString::number( nVal ); + } + return impTransliterate(aStr, rNum); +} + +OUString SvNumberformat::impTransliterateImpl(const OUString& rStr, + const SvNumberNatNum& rNum ) const +{ + css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); + return GetFormatter().GetNatNum()->getNativeNumberStringParams(rStr, aLocale, rNum.GetNatNum(), + rNum.GetParams()); +} + +void SvNumberformat::impTransliterateImpl(OUStringBuffer& rStr, + const SvNumberNatNum& rNum ) const +{ + css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); + + rStr = GetFormatter().GetNatNum()->getNativeNumberStringParams( + OUString::unacquired(rStr), aLocale, rNum.GetNatNum(), rNum.GetParams()); +} + +OUString SvNumberformat::impTransliterateImpl(const OUString& rStr, + const SvNumberNatNum& rNum, + const sal_uInt16 nDateKey) const +{ + // no KEYWORD=argument list in NatNum12 + if (rNum.GetParams().indexOf('=') == -1) + return impTransliterateImpl( rStr, rNum); + + const NfKeywordTable & rKeywords = rScan.GetKeywords(); + + // Format: KEYWORD=numbertext_prefix, ..., for example: + // [NatNum12 YYYY=title ordinal,MMMM=article, D=ordinal-number] + sal_Int32 nField = -1; + do + { + nField = rNum.GetParams().indexOf(OUStringConcatenation(rKeywords[nDateKey] + "="), ++nField); + } + while (nField != -1 && nField != 0 && + (rNum.GetParams()[nField - 1] != ',' && + rNum.GetParams()[nField - 1] != ' ')); + + // no format specified for actual keyword + if (nField == -1) + return rStr; + + sal_Int32 nKeywordLen = rKeywords[nDateKey].getLength() + 1; + sal_Int32 nFieldEnd = rNum.GetParams().indexOf(',', nField); + + if (nFieldEnd == -1) + nFieldEnd = rNum.GetParams().getLength(); + + css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); + + return GetFormatter().GetNatNum()->getNativeNumberStringParams( + rStr, aLocale, rNum.GetNatNum(), + rNum.GetParams().copy(nField + nKeywordLen, nFieldEnd - nField - nKeywordLen)); +} + +void SvNumberformat::GetNatNumXml( css::i18n::NativeNumberXmlAttributes2& rAttr, + sal_uInt16 nNumFor ) const +{ + if ( nNumFor <= 3 ) + { + const SvNumberNatNum& rNum = NumFor[nNumFor].GetNatNum(); + if ( rNum.IsSet() ) + { + css::lang::Locale aLocale( + LanguageTag( rNum.GetLang() ).getLocale() ); + + /* TODO: a new XNativeNumberSupplier2::convertToXmlAttributes() + * should rather return NativeNumberXmlAttributes2 and places + * adapted, and whether to fill Spellout or something different + * should be internal there. */ + css::i18n::NativeNumberXmlAttributes aTmp( + GetFormatter().GetNatNum()->convertToXmlAttributes( + aLocale, rNum.GetNatNum())); + rAttr.Locale = aTmp.Locale; + rAttr.Format = aTmp.Format; + rAttr.Style = aTmp.Style; + if ( NatNumTakesParameters(rNum.GetNatNum()) ) + { + // NatNum12 spell out numbers, dates and money amounts + rAttr.Spellout = rNum.GetParams(); + // Mutually exclusive. + rAttr.Format.clear(); + rAttr.Style.clear(); + } + else + { + rAttr.Spellout.clear(); + } + } + else + { + rAttr = css::i18n::NativeNumberXmlAttributes2(); + } + } + else + { + rAttr = css::i18n::NativeNumberXmlAttributes2(); + } +} + +// static +bool SvNumberformat::HasStringNegativeSign( const OUString& rStr ) +{ + // For Sign '-' needs to be at the start or at the end of the string (blanks ignored) + sal_Int32 nLen = rStr.getLength(); + if ( !nLen ) + { + return false; + } + const sal_Unicode* const pBeg = rStr.getStr(); + const sal_Unicode* const pEnd = pBeg + nLen; + const sal_Unicode* p = pBeg; + do + { // Start + if ( *p == '-' ) + { + return true; + } + } + while ( *p == ' ' && ++p < pEnd ); + + p = pEnd - 1; + + do + { // End + if ( *p == '-' ) + { + return true; + } + } + while ( *p == ' ' && pBeg < --p ); + return false; +} + +// static +bool SvNumberformat::IsInQuote( const OUString& rStr, sal_Int32 nPos, + sal_Unicode cQuote, sal_Unicode cEscIn, sal_Unicode cEscOut ) +{ + sal_Int32 nLen = rStr.getLength(); + if ( nPos >= nLen ) + { + return false; + } + const sal_Unicode* p0 = rStr.getStr(); + const sal_Unicode* p = p0; + const sal_Unicode* p1 = p0 + nPos; + bool bQuoted = false; + while ( p <= p1 ) + { + if ( *p == cQuote ) + { + if ( p == p0 ) + { + bQuoted = true; + } + else if ( bQuoted ) + { + if ( *(p-1) != cEscIn ) + { + bQuoted = false; + } + } + else + { + if ( *(p-1) != cEscOut ) + { + bQuoted = true; + } + } + } + p++; + } + return bQuoted; +} + +// static +sal_Int32 SvNumberformat::GetQuoteEnd( const OUString& rStr, sal_Int32 nPos, + sal_Unicode cQuote, sal_Unicode cEscIn ) +{ + if ( nPos < 0 ) + { + return -1; + } + sal_Int32 nLen = rStr.getLength(); + if ( nPos >= nLen ) + { + return -1; + } + if ( !IsInQuote( rStr, nPos, cQuote, cEscIn ) ) + { + if ( rStr[ nPos ] == cQuote ) + { + return nPos; // Closing cQuote + } + return -1; + } + const sal_Unicode* p0 = rStr.getStr(); + const sal_Unicode* p = p0 + nPos; + const sal_Unicode* p1 = p0 + nLen; + while ( p < p1 ) + { + if ( *p == cQuote && p > p0 && *(p-1) != cEscIn ) + { + return sal::static_int_cast< sal_Int32 >(p - p0); + } + p++; + } + return nLen; // End of String +} + +sal_uInt16 SvNumberformat::GetNumForNumberElementCount( sal_uInt16 nNumFor ) const +{ + if ( nNumFor < 4 ) + { + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + return nCnt - ImpGetNumForStringElementCount( nNumFor ); + } + return 0; +} + +sal_uInt16 SvNumberformat::ImpGetNumForStringElementCount( sal_uInt16 nNumFor ) const +{ + sal_uInt16 nCnt = 0; + sal_uInt16 nNumForCnt = NumFor[nNumFor].GetCount(); + auto& rTypeArray = NumFor[nNumFor].Info().nTypeArray; + for ( sal_uInt16 j=0; j<nNumForCnt; ++j ) + { + switch ( rTypeArray[j] ) + { + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + case NF_SYMBOLTYPE_DATESEP: + case NF_SYMBOLTYPE_TIMESEP: + case NF_SYMBOLTYPE_TIME100SECSEP: + case NF_SYMBOLTYPE_PERCENT: + ++nCnt; + break; + } + } + return nCnt; +} + +bool SvNumberformat::IsMinuteSecondFormat() const +{ + if (GetMaskedType() != SvNumFormatType::TIME) + return false; + + constexpr sal_uInt16 k00 = 0x00; // Nada, Nilch + constexpr sal_uInt16 kLB = 0x01; // '[' Left Bracket + constexpr sal_uInt16 kRB = 0x02; // ']' Right Bracket + constexpr sal_uInt16 kMM = 0x04; // M or MM + constexpr sal_uInt16 kTS = 0x08; // Time Separator + constexpr sal_uInt16 kSS = 0x10; // S or SS +#define HAS_MINUTE_SECOND(state) ((state) == (kMM|kTS|kSS) || (state) == (kLB|kMM|kRB|kTS|kSS)) + // Also (kMM|kTS|kLB|kSS|kRB) but those are the same bits. + + sal_uInt16 nState = k00; + bool bSep = false; + sal_uInt16 nNumForCnt = NumFor[0].GetCount(); + auto const & rTypeArray = NumFor[0].Info().nTypeArray; + for (sal_uInt16 j=0; j < nNumForCnt; ++j) + { + switch (rTypeArray[j]) + { + case NF_SYMBOLTYPE_DEL: + { + // '[' or ']' before/after MM or SS + const OUString& rStr = NumFor[0].Info().sStrArray[j]; + if (rStr == "[") + { + if (nState != k00 && nState != (kMM|kTS)) + return false; + nState |= kLB; + } + else if (rStr == "]") + { + if (nState != (kLB|kMM) && nState != (kMM|kTS|kLB|kSS)) + return false; + nState |= kRB; + } + else + return false; + } + break; + case NF_KEY_MI: + case NF_KEY_MMI: + if (nState != k00 && nState != kLB) + return false; + nState |= kMM; + break; + case NF_SYMBOLTYPE_TIMESEP: + if (nState != kMM && nState != (kLB|kMM|kRB)) + return false; + nState |= kTS; + break; + case NF_KEY_S: + case NF_KEY_SS: + if (nState != (kMM|kTS) && nState != (kLB|kMM|kRB|kTS) && nState != (kMM|kTS|kLB)) + return false; + nState |= kSS; + break; + case NF_SYMBOLTYPE_TIME100SECSEP: + // Trailing fraction of seconds allowed. + if (!HAS_MINUTE_SECOND(nState)) + return false; + bSep = true; + break; + case NF_SYMBOLTYPE_DIGIT: + if (!bSep) + return false; + break; + case NF_SYMBOLTYPE_STRING: + // nothing, display literal + break; + default: + return false; + } + } + return HAS_MINUTE_SECOND(nState); +#undef HAS_MINUTE_SECOND +} + +OUString SvNumberformat::GetFormatStringForTimePrecision( int nPrecision ) const +{ + OUStringBuffer sString; + using comphelper::string::padToLength; + + sal_uInt16 nNumForCnt = NumFor[0].GetCount(); + auto const & rTypeArray = NumFor[0].Info().nTypeArray; + for (sal_uInt16 j=0; j < nNumForCnt; ++j) + { + switch (rTypeArray[j]) + { + case NF_KEY_S : + case NF_KEY_SS: + sString.append( NumFor[0].Info().sStrArray[j] ); + if ( j > 0 && rTypeArray[j-1] == NF_SYMBOLTYPE_DEL && j < nNumForCnt-1 ) + { + j++; + sString.append( NumFor[0].Info().sStrArray[j] ); + } + if (nPrecision > 0) + { + sString.append( rLoc().getTime100SecSep() ); + padToLength(sString, sString.getLength() + nPrecision, '0'); + } + break; + case NF_SYMBOLTYPE_TIME100SECSEP: + case NF_SYMBOLTYPE_DIGIT: + break; + case NF_SYMBOLTYPE_STRING: + sString.append( "\"" ); + [[fallthrough]]; + default: + sString.append( NumFor[0].Info().sStrArray[j] ); + if (rTypeArray[j] == NF_SYMBOLTYPE_STRING) + { + sString.append( "\"" ); + } + } + } + + return sString.makeStringAndClear(); +} + +sal_uInt16 SvNumberformat::GetThousandDivisorPrecision( sal_uInt16 nIx ) const +{ + if (nIx >= 4) + return 0; + + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + + if (rInfo.eScannedType != SvNumFormatType::NUMBER && rInfo.eScannedType != SvNumFormatType::CURRENCY) + return 0; + + if (rInfo.nThousand == FLAG_STANDARD_IN_FORMAT) + return SvNumberFormatter::UNLIMITED_PRECISION; + + return rInfo.nThousand * 3; +} + +const CharClass& SvNumberformat::rChrCls() const +{ + return rScan.GetChrCls(); +} + +const LocaleDataWrapper& SvNumberformat::rLoc() const +{ + return rScan.GetLoc(); +} + +CalendarWrapper& SvNumberformat::GetCal() const +{ + return rScan.GetCal(); +} + +const SvNumberFormatter& SvNumberformat::GetFormatter() const +{ + return *rScan.GetNumberformatter(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |