/* -*- 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