/* -*- 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 <string_view>
#include <o3tl/sprintf.hxx>
#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 OUString GREGORIAN = u"gregorian"_ustr;
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()
constexpr sal_Int32 kTimeSignificantRound = 7; // Round (date+)time at 7 decimals
// (+5 of 86400 == 12 significant digits).
const sal_Unicode cBlankDigit = 0x2007; // tdf#158890 use figure space for '?'
} // 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)
{
ImpCopyNumberformat( rFormat );
}
SvNumberformat::SvNumberformat( SvNumberformat const & rFormat, ImpSvNumberformatScan& rSc )
: rScan(rSc)
{
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, "[NatNum" + OUString::number(nNatNum) + "]");
return sCalendar;
}
namespace
{
bool NatNumTakesParameters(sal_Int16 nNum)
{
return (nNum == css::i18n::NativeNumberMode::NATNUM12);
}
}
// is there a 3-letter bank code in NatNum12 param (but not
// followed by an equal mark, like in the date code "NNN=")?
static bool lcl_isNatNum12Currency( std::u16string_view sParam )
{
sal_Int32 nUpper = 0;
sal_Int32 nLen = sParam.size();
for (sal_Int32 n = 0; n < nLen; ++n)
{
sal_Unicode c = sParam[n];
if ( 'A' <= c && c <= 'Z' )
{
++nUpper;
}
else if ( c == ' ' && nUpper == 3 && (n == 3 || sParam[n - 4] == ' ') )
{
return true;
}
else
{
nUpper = 0;
}
}
return nUpper == 3 && (nLen == 3 || sParam[nLen - 4] == ' ');
}
SvNumberformat::SvNumberformat(OUString& rString,
ImpSvNumberformatScan* pSc,
ImpSvNumberInputScan* pISc,
const NativeNumberWrapper& rNatNum,
sal_Int32& nCheckPos,
LanguageType& eLan,
bool bReplaceBooleanEquivalent)
: rScan(*pSc)
, bAdditionalBuiltin( 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 = GetCurrentLanguageData().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 = GetCurrentLanguageData().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.GetCurrentLanguageData().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, rNatNum, 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 = rScan.GetCurrentLanguageData().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"
else if (sParams.indexOf("CURRENCY") >= 0)
sParams = sParams.replaceAll("CURRENCY",
rLoc().getCurrBankSymbol());
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() + 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)
{
if ( NumFor[nIndex].GetNatNum().GetNatNum() == 12 &&
lcl_isNatNum12Currency(NumFor[nIndex].GetNatNum().GetParams()) )
eType = SvNumFormatType::CURRENCY;
else
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(";" + 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(";" + 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)
{
if (n < 10)
return static_cast<sal_Unicode>('0' + n);
else
return static_cast<sal_Unicode>('A' + n - 10);
}
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) & 0xFFFF;
}
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(std::u16string_view rString, sal_Int32& nPos )
{
sal_uInt32 nNum = 0;
sal_Unicode cToken = 0;
sal_Int32 nStart = nPos;
sal_Int32 nLen = rString.size();
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( std::u16string_view rString, const sal_Int32 nPos,
std::u16string_view rKeyword, sal_Int32 & nNumber )
{
if (0 <= nPos && nPos + static_cast<sal_Int32>(rKeyword.size()) < static_cast<sal_Int32>(rString.size()) && o3tl::matchIgnoreAsciiCase( rString, rKeyword, nPos))
{
nNumber = o3tl::toInt32(rString.substr( nPos + rKeyword.size()));
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 constexpr OUString aNatNum(u"NATNUM"_ustr);
static constexpr OUString aDBNum(u"DBNUM"_ustr);
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 NativeNumberWrapper& rNatNum) const
{
OUString sTemp;
ImpGetOutputStandard(fNumber, sTemp, rNatNum);
rOutString = sTemp;
}
void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUString& rOutString,
const NativeNumberWrapper& rNatNum) 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*/,
GetCurrentLanguageData().GetNumDecimalSep()[0]);
}
else
{
ImpGetOutputStdToPrecision(fNumber, rOutString, nStandardPrec, rNatNum);
}
}
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;
}
OUString impTransliterateImpl(const OUString& rStr,
const SvNumberNatNum& rNum,
const NativeNumberWrapper& rNatNum)
{
css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
return rNatNum.getNativeNumberStringParams(rStr, aLocale, rNum.GetNatNum(), rNum.GetParams());
}
void impTransliterateImpl(OUStringBuffer& rStr,
const SvNumberNatNum& rNum,
const NativeNumberWrapper& rNatNum)
{
css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
rStr = rNatNum.getNativeNumberStringParams(
OUString::unacquired(rStr), aLocale, rNum.GetNatNum(), rNum.GetParams());
}
OUString impTransliterate(const OUString& rStr, const SvNumberNatNum& rNum, const NativeNumberWrapper& rNatNum)
{
return rNum.IsComplete() ? impTransliterateImpl(rStr, rNum, rNatNum) : rStr;
}
void impTransliterate(OUStringBuffer& rStr, const SvNumberNatNum& rNum, const NativeNumberWrapper& rNatNum)
{
if(rNum.IsComplete())
{
impTransliterateImpl(rStr, rNum, rNatNum);
}
}
}
void SvNumberformat::ImpGetOutputStdToPrecision(double& rNumber, OUString& rOutString, sal_uInt16 nPrecision,
const NativeNumberWrapper& rNatNum) 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*/,
GetCurrentLanguageData().GetNumDecimalSep()[0], true );
if (rOutString[0] == '-' && checkForAll0s(rOutString, 1))
{
rOutString = comphelper::string::stripStart(rOutString, '-'); // not -0
}
rOutString = ::impTransliterate(rOutString, NumFor[0].GetNatNum(), rNatNum);
}
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,
GetCurrentLanguageData().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, std::u16string_view rStr )
{
// Right during user input the star symbol is the very
// last character before the user enters another one.
if (rStr.size() > 1)
{
rBuf.append(u'\x001B');
rBuf.append(rStr[1]);
return true;
}
return false;
}
static bool lcl_insertStarFillChar( OUStringBuffer& rBuf, sal_Int32 nPos, std::u16string_view rStr )
{
if (rStr.size() > 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,
bool bStarFlag) const
{
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 SvNFLanguageData& rLanguageData, 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, rLanguageData.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 NativeNumberWrapper& rNatNum) 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, GetCurrentLanguageData(), 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, GetCurrentLanguageData(), 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, rNatNum);
if (rOutString.getLength() > nCharCount)
{
// String still wider than desired. Switch to scientific notation.
lcl_GetOutputStringScientific(fNumber, nCharCount, GetCurrentLanguageData(), 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,
const NativeNumberWrapper& rNatNum,
const SvNFLanguageData& rCurrentLang,
bool bStarFlag) const
{
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, rNatNum);
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,
GetCurrentLanguageData().GetNumDecimalSep()[0], true);
else
OutString = ::rtl::math::doubleToUString( fNumber,
rtl_math_StringFormat_E2,
rtl_math_DecimalPlaces_Max,
GetCurrentLanguageData().GetNumDecimalSep()[0], true);
}
else
{
OutString = ::rtl::math::doubleToUString( fNumber,
rtl_math_StringFormat_Automatic,
rtl_math_DecimalPlaces_Max,
GetCurrentLanguageData().GetNumDecimalSep()[0], true);
}
return false;
}
ImpGetOutputStandard(fNumber, sBuff, rNatNum);
bHadStandard = true;
break;
case SvNumFormatType::DATE:
bRes |= ImpGetDateOutput(fNumber, 0, bStarFlag, rNatNum, rCurrentLang, sBuff);
bHadStandard = true;
break;
case SvNumFormatType::TIME:
bRes |= ImpGetTimeOutput(fNumber, 0, bStarFlag, rNatNum, rCurrentLang, sBuff);
bHadStandard = true;
break;
case SvNumFormatType::DATETIME:
bRes |= ImpGetDateTimeOutput(fNumber, 0, bStarFlag, rNatNum, rCurrentLang, 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, rNatNum);
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, bStarFlag, rNatNum, rCurrentLang, sBuff);
break;
case SvNumFormatType::TIME:
bRes |= ImpGetTimeOutput(fNumber, nIx, bStarFlag, rNatNum, rCurrentLang, sBuff);
break;
case SvNumFormatType::DATETIME:
bRes |= ImpGetDateTimeOutput(fNumber, nIx, bStarFlag, rNatNum, rCurrentLang, sBuff);
break;
case SvNumFormatType::NUMBER:
case SvNumFormatType::PERCENT:
case SvNumFormatType::CURRENCY:
bRes |= ImpGetNumberOutput(fNumber, nIx, bStarFlag, rNatNum, sBuff);
break;
case SvNumFormatType::LOGICAL:
bRes |= ImpGetLogicalOutput(fNumber, nIx, rNatNum, sBuff);
break;
case SvNumFormatType::FRACTION:
bRes |= ImpGetFractionOutput(fNumber, nIx, bStarFlag, rNatNum, sBuff);
break;
case SvNumFormatType::SCIENTIFIC:
bRes |= ImpGetScientificOutput(fNumber, nIx, bStarFlag, rNatNum, sBuff);
break;
default: break;
}
}
OutString = sBuff.makeStringAndClear();
return bRes;
}
bool SvNumberformat::ImpGetScientificOutput(double fNumber,
sal_uInt16 nIx,
bool bStarFlag,
const NativeNumberWrapper& rNatNum,
OUStringBuffer& sStr) const
{
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 = ExpStr.getLength() - 1; // Position in ExpStr
sal_Int32 nZeros = 0; // Erase leading zeros
// erase all leading zeros except last one
while (nZeros < k && ExpStr[nZeros] == '0')
{
++nZeros;
}
if (nZeros)
{
ExpStr.remove( 0, nZeros);
}
// restore leading zeros or blanks according to format '0' or '?' tdf#156449
bRes |= ImpNumberFill(rNatNum, ExpStr, fNumber, k, j, nIx, NF_SYMBOLTYPE_EXP, bStarFlag);
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(rNatNum, sStr, fNumber, nDecPos, j, nIx, false, bStarFlag);
}
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,
bool bStarFlag,
const NativeNumberWrapper& rNatNum,
OUStringBuffer& sBuff) const
{
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];
o3tl::sprintf( aBuf, "%.f", fNum ); // simple rounded integer
sStr.appendAscii( aBuf );
::impTransliterate(sStr, NumFor[nIx].GetNatNum(), rNatNum);
}
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(rNatNum, nIx, nFrac);
sDiv = ImpIntToString(rNatNum, nIx, nDiv);
}
sal_uInt16 j = nCnt-1; // Last symbol -> backwards
sal_Int32 k; // Denominator
bRes |= ImpNumberFill(rNatNum, sDiv, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRAC, bStarFlag, 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(rNatNum, sFrac, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRACBLANK, bStarFlag);
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(rNatNum, sStr, fNumber, k, j, nIx,
rInfo.nCntPre, bStarFlag);
}
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,
const NativeNumberWrapper& rNatNum) const
{
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(), rNatNum);
nFractionDecimals = rBuf.getLength();
}
else
{
::impTransliterate(rBuf, NumFor[nIx].GetNatNum(), rNatNum);
}
return static_cast<sal_uInt16>(nFractionDecimals);
}
bool SvNumberformat::ImpGetTimeOutput(double fNumber,
sal_uInt16 nIx,
bool bStarFlag,
const NativeNumberWrapper& rNatNum,
const SvNFLanguageData& rCurrentLang,
OUStringBuffer& sBuff) const
{
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 < kTimeSignificantRound )
{
bInputLine = true;
nCntPost = kTimeSignificantRound;
}
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), rNatNum );
}
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), rNatNum );
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
{
CalendarWrapper& rCal = *rCurrentLang.GetCalendar();
if ( !bCalendarSet )
{
double fDiff = DateTime::Sub( DateTime(rScan.GetNullDate()), rCal.getEpochStart());
fDiff += fNumberOrig;
rCal.setLocalDateTime( fDiff );
bCalendarSet = true;
}
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(rNatNum, nIx, nMin ));
break;
case NF_KEY_MMI: // MM
sBuff.append(ImpIntToString(rNatNum, nIx, nMin, 2 ));
break;
case NF_KEY_H: // H
sBuff.append(ImpIntToString(rNatNum, nIx, nHour ));
break;
case NF_KEY_HH: // HH
sBuff.append(ImpIntToString(rNatNum, nIx, nHour, 2 ));
break;
case NF_KEY_S: // S
sBuff.append(ImpIntToString(rNatNum, nIx, nSec ));
break;
case NF_KEY_SS: // SS
sBuff.append(ImpIntToString(rNatNum, 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;
}
namespace {
bool ImpIsOtherCalendar( const ImpSvNumFor& rNumFor, const CalendarWrapper& rCal )
{
if ( rCal.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,
CalendarWrapper& rCal ) const
{
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( std::u16string_view rOrgCalendar,
double fOrgDateTime,
CalendarWrapper& rCal ) const
{
if ( rOrgCalendar.size() && rCal.getUniqueID() != GREGORIAN )
{
rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() );
rCal.setDateTime( fOrgDateTime );
}
}
bool SvNumberformat::ImpFallBackToGregorianCalendar( OUString& rOrgCalendar,
double& fOrgDateTime,
CalendarWrapper& rCal ) const
{
using namespace ::com::sun::star::i18n;
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 = DateTime::Sub( aMin, aCE);
static const double fMax = DateTime::Sub( aMax, aCE);
// Value must be representable in our tools::Date proleptic Gregorian
// calendar as well.
const double fOff = DateTime::Sub( rNullDate, aCE) + fNumber;
// Add diff between epochs to serial date number.
const double fDiff = DateTime::Sub( rNullDate, rEpochStart);
fNumber += fDiff;
return fMin <= fOff && fOff <= fMax;
}
bool SvNumberformat::ImpGetDateOutput(double fNumber,
sal_uInt16 nIx,
bool bStarFlag,
const NativeNumberWrapper& rNatNum,
const SvNFLanguageData& rCurrentLang,
OUStringBuffer& sBuff) const
{
using namespace ::com::sun::star::i18n;
bool bRes = false;
CalendarWrapper& rCal = *rCurrentLang.GetCalendar();
if (!lcl_getValidDate( DateTime( 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], *rCurrentLang.GetCalendar() );
if ( bOtherCalendar )
{
SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() ) )
{
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, *rCurrentLang.GetCalendar() );
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], rNatNum);
}
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], rNatNum);
}
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], rNatNum);
}
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, *rCurrentLang.GetCalendar() );
}
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], rNatNum);
}
sBuff.append(aStr);
if ( bOtherCalendar )
{
SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
break;
case NF_KEY_DDDD: // DDDD
if ( bOtherCalendar )
{
SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
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], rNatNum);
}
sBuff.append(aStr);
if ( bOtherCalendar )
{
SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
break;
case NF_KEY_YY: // YY
if ( bOtherCalendar )
{
SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
// 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, *rCurrentLang.GetCalendar() );
}
break;
case NF_KEY_YYYY: // YYYY
if ( bOtherCalendar )
{
SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
// 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(), rNatNum);
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], rNatNum);
}
sBuff.append(aStr);
if ( bOtherCalendar )
{
SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
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], rNatNum);
}
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], rNatNum);
}
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(rNatNum, 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,
bool bStarFlag,
const NativeNumberWrapper& rNatNum,
const SvNFLanguageData& rCurrentLang,
OUStringBuffer& sBuff) const
{
using namespace ::com::sun::star::i18n;
bool bRes = false;
CalendarWrapper& rCal = *rCurrentLang.GetCalendar();
if (!lcl_getValidDate( DateTime( rScan.GetNullDate() ), rCal.getEpochStart(), fNumber))
{
sBuff = ImpSvNumberformatScan::sErrStr;
return false;
}
const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
bool bInputLine;
sal_Int32 nCntPost, nFirstRounding;
if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION &&
0 < rInfo.nCntPost && rInfo.nCntPost < kTimeSignificantRound )
{
bInputLine = true;
nCntPost = nFirstRounding = kTimeSignificantRound;
}
else
{
bInputLine = false;
nCntPost = rInfo.nCntPost;
// For clock format (not []) do not round up to seconds and thus days.
nFirstRounding = (rInfo.bThousand ? nCntPost : kTimeSignificantRound);
}
double fTime = (fNumber - floor( fNumber )) * 86400.0;
fTime = ::rtl::math::round( fTime, int(nFirstRounding) );
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], *rCurrentLang.GetCalendar() );
if ( bOtherCalendar )
{
SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() ) )
{
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), rNatNum );
}
else
{
sal_uInt32 nSeconds = static_cast<sal_uInt32>(floor( fTime ));
nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx,
(bInputLine ? rInfo.nCntPost : 0), rNatNum );
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, *rCurrentLang.GetCalendar() );
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(rNatNum, nIx, nMin ));
break;
case NF_KEY_MMI: // MM
sBuff.append(ImpIntToString(rNatNum, nIx, nMin, 2 ));
break;
case NF_KEY_H: // H
sBuff.append(ImpIntToString(rNatNum, nIx, nHour ));
break;
case NF_KEY_HH: // HH
sBuff.append(ImpIntToString(rNatNum, nIx, nHour, 2 ));
break;
case NF_KEY_S: // S
sBuff.append(ImpIntToString(rNatNum, nIx, nSec ));
break;
case NF_KEY_SS: // SS
sBuff.append(ImpIntToString(rNatNum, 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, *rCurrentLang.GetCalendar() );
}
sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ));
if ( bOtherCalendar )
{
SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
break;
case NF_KEY_DDDD: // DDDD
if ( bOtherCalendar )
{
SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
if ( bOtherCalendar )
{
SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
break;
case NF_KEY_YY: // YY
if ( bOtherCalendar )
{
SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
// 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, *rCurrentLang.GetCalendar() );
}
break;
case NF_KEY_YYYY: // YYYY
if ( bOtherCalendar )
{
SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
// 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(), rNatNum);
aBuf.append(aYear);
sBuff.append(aBuf);
}
else
{
sBuff.append(aYear);
}
if ( bOtherCalendar )
{
SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
}
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(rNatNum, 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,
const NativeNumberWrapper& rNatNum,
OUStringBuffer& sStr) const
{
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(), rNatNum);
return bRes;
}
bool SvNumberformat::ImpGetNumberOutput(double fNumber,
sal_uInt16 nIx,
bool bStarFlag,
const NativeNumberWrapper& rNatNum,
OUStringBuffer& sStr) const
{
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;
}
// Make sure that Calc's ROUND and formatted output agree
fNumber = rtl_math_round(fNumber, rInfo.nCntPost, rtl_math_RoundingMode_Corrected);
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(rNatNum, sStr, fNumber, nDecPos, j, nIx, bInteger, bStarFlag);
if (bSign)
{
sStr.insert(0, '-');
}
::impTransliterate(sStr, NumFor[nIx].GetNatNum(), rNatNum);
return bRes;
}
bool SvNumberformat::ImpDecimalFill(const NativeNumberWrapper& rNatNum,
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 bStarFlag) const
{
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 ] = cBlankDigit;
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, rNatNum);
sNum.stripStart('-');
sStr.insert(k, sNum);
break;
}
default:
break;
} // of switch
j--;
} // of while
} // of decimal places
bRes |= ImpNumberFillWithThousands(rNatNum, sStr, rNumber, k, j, nIx, // Fill with . if needed
rInfo.nCntPre, bStarFlag, bFilled );
return bRes;
}
bool SvNumberformat::ImpNumberFillWithThousands( const NativeNumberWrapper& rNatNum,
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 bStarFlag,
bool bAddDecSep) const // 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( GetCurrentLanguageData().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:
if ( rInfo.nTypeArray[j] == NF_SYMBOLTYPE_STRING && nDigCnt == 0 )
{
// tdf#159930 no integer in format ".###"
k = 0; // insert string at the beginning
}
[[fallthrough]];
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, cBlankDigit);
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, rNatNum);
sNum.stripStart('-');
sBuff.insert(k, sNum);
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 ) const // current grouping
{
if (NumFor[nIx].Info().bThousand) // Only if grouping fill in separators
{
const OUString& rThousandSep = GetCurrentLanguageData().GetNumThousandSep();
while (k > nStart)
{
if (nDigitCount == rGrouping.getPos())
{
sStr.insert( k, rThousandSep );
rGrouping.advance();
}
nDigitCount++;
k--;
}
}
else // simply skip
{
k = nStart;
}
}
bool SvNumberformat::ImpNumberFill( const NativeNumberWrapper& rNatNum,
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 bStarFlag,
bool bInsertRightBlank) const // 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, cBlankDigit);
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, rNatNum);
sNum.stripStart('-');
sBuff.insert(k, sNum);
}
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' || *p == '?' )
{
nLeadingCnt++;
p++;
}
}
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;
auto aLCIDString = OUString::number( nLCID , 16 ).toAsciiUpperCase();
// Search for only last DBNum which is the last element before insertion position
if ( bDBNumInserted && nPosInsertLCID >= 8
&& aLCIDString.length > 4
&& OUString::unacquired(rFormatStr).match( "[DBNum", nPosInsertLCID-8) )
{ // remove DBNumX code if long LCID
nPosInsertLCID -= 8;
rFormatStr.remove( nPosInsertLCID, 8 );
}
rFormatStr.insert( nPosInsertLCID, "[$-" + aLCIDString + "]" );
}
/** 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( "\"" + rStrArray[j] + "\"" );
}
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();
}
// static
OUString SvNumberformat::ImpGetNatNumString(const SvNumberNatNum& rNum,
sal_Int64 nVal, sal_uInt16 nMinDigits,
const NativeNumberWrapper& rNatNum)
{
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, rNatNum);
}
OUString SvNumberformat::impTransliterateImpl(const OUString& rStr,
const SvNumberNatNum& rNum,
const sal_uInt16 nDateKey,
const NativeNumberWrapper& rNatNum) const
{
// no KEYWORD=argument list in NatNum12
if (rNum.GetParams().indexOf('=') == -1)
return ::impTransliterateImpl( rStr, rNum, rNatNum);
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(Concat2View(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 rNatNum.getNativeNumberStringParams(
rStr, aLocale, rNum.GetNatNum(),
rNum.GetParams().copy(nField + nKeywordLen, nFieldEnd - nField - nKeywordLen));
}
void SvNumberformat::GetNatNumXml( css::i18n::NativeNumberXmlAttributes2& rAttr,
sal_uInt16 nNumFor, const NativeNumberWrapper& rNatNum ) 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(
rNatNum.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();
}
}
OUString SvNumberformat::GetNatNumModifierString( sal_uInt16 nNumFor ) const
{
if ( nNumFor > 3 )
return u""_ustr;
const SvNumberNatNum& rNum = NumFor[nNumFor].GetNatNum();
if ( !rNum.IsSet() )
return u""_ustr;
const sal_Int32 nNum = rNum.GetNatNum();
OUStringBuffer sNatNumModifier = "[NatNum" + OUString::number( nNum );
if ( NatNumTakesParameters( nNum ) )
{
sNatNumModifier.append( " " + rNum.GetParams() );
}
sNatNumModifier.append( "]" );
return sNatNumModifier.makeStringAndClear();
}
// 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();
}
const SvNFLanguageData& SvNumberformat::GetCurrentLanguageData() const
{
return rScan.GetCurrentLanguageData();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'replace' is required to be utilized.
↑ V530 The return value of function 'replace' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'truncate' is required to be utilized.
↑ V530 The return value of function 'truncate' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'truncate' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'truncate' is required to be utilized.
↑ V530 The return value of function 'appendAscii' is required to be utilized.
↑ V530 The return value of function 'truncate' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'truncate' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'truncate' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'truncate' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'padToLength' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'padToLength' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'padToLength' is required to be utilized.
↑ V547 Expression '!"unhandled substitute"' is always false.
↑ V1019 Compound assignment expression is used inside condition.
↑ V1037 Two or more case-branches perform the same actions. Check lines: 4243, 4248, 4253