Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/svl/source/numbers/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 217 kB image not shown  

Quelle  zformat.cxx   Sprache: C

 
/* -*- 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 <o3tl/numeric.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;

        int nValue = o3tl::convertToHex<int>(cToken);

        if (nValue == -1)
            return LocaleType(); // LANGUAGE_DONTKNOW;

        nNum *= 16;
        nNum += nValue;

        ++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;
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=85 H=94 G=89

¤ Dauer der Verarbeitung: 0.36 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.