Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  zforscan.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 <stdlib.h>
#include <comphelper/string.hxx>
#include <sal/log.hxx>
#include <tools/debug.hxx>
#include <i18nlangtag/mslangid.hxx>
#include <unotools/charclass.hxx>
#include <unotools/localedatawrapper.hxx>
#include <com/sun/star/i18n/NumberFormatCode.hpp>
#include <com/sun/star/i18n/NumberFormatMapper.hpp>

#include <svl/zforlist.hxx>
#include <svl/zformat.hxx>
#include <unotools/digitgroupingiterator.hxx>

#include "zforscan.hxx"

#include <svl/nfsymbol.hxx>
using namespace svt;

const sal_Unicode cNoBreakSpace = 0xA0;
const sal_Unicode cNarrowNoBreakSpace = 0x202F;

const int MaxCntPost = 20; //max dec places allow by rtl_math_round

const NfKeywordTable ImpSvNumberformatScan::sEnglishKeyword =
{             // Syntax keywords in English (USA)
    //! All keywords MUST be UPPERCASE! In same order as NfKeywordIndex
    u""_ustr,        // NF_KEY_NONE 0
    u"E"_ustr,       // NF_KEY_E Exponent
    u"AM/PM"_ustr,   // NF_KEY_AMPM AM/PM
    u"A/P"_ustr,     // NF_KEY_AP AM/PM short
    u"M"_ustr,       // NF_KEY_MI Minute
    u"MM"_ustr,      // NF_KEY_MMI Minute 02
    u"M"_ustr,       // NF_KEY_M month        (!)
    u"MM"_ustr,      // NF_KEY_MM month 02     (!)
    u"MMM"_ustr,     // NF_KEY_MMM month short name
    u"MMMM"_ustr,    // NF_KEY_MMMM month long name
    u"MMMMM"_ustr,   // NF_KEY_MMMMM first letter of month name
    u"H"_ustr,       // NF_KEY_H hour
    u"HH"_ustr,      // NF_KEY_HH hour 02
    u"S"_ustr,       // NF_KEY_S Second
    u"SS"_ustr,      // NF_KEY_SS Second 02
    u"Q"_ustr,       // NF_KEY_Q Quarter short 'Q'
    u"QQ"_ustr,      // NF_KEY_QQ Quarter long
    u"D"_ustr,       // NF_KEY_D day of month
    u"DD"_ustr,      // NF_KEY_DD day of month 02
    u"DDD"_ustr,     // NF_KEY_DDD day of week short
    u"DDDD"_ustr,    // NF_KEY_DDDD day of week long
    u"YY"_ustr,      // NF_KEY_YY year two digits
    u"YYYY"_ustr,    // NF_KEY_YYYY year four digits
    u"NN"_ustr,      // NF_KEY_NN Day of week short
    u"NNN"_ustr,     // NF_KEY_NNN Day of week long
    u"NNNN"_ustr,    // NF_KEY_NNNN Day of week long incl. separator
    u"AAA"_ustr,     // NF_KEY_AAA
    u"AAAA"_ustr,    // NF_KEY_AAAA
    u"E"_ustr,       // NF_KEY_EC
    u"EE"_ustr,      // NF_KEY_EEC
    u"G"_ustr,       // NF_KEY_G
    u"GG"_ustr,      // NF_KEY_GG
    u"GGG"_ustr,     // NF_KEY_GGG
    u"R"_ustr,       // NF_KEY_R
    u"RR"_ustr,      // NF_KEY_RR
    u"WW"_ustr,      // NF_KEY_WW Week of year
    u"t"_ustr,       // NF_KEY_THAI_T Thai T modifier, speciality of Thai Excel, only
                // used with Thai locale and converted to [NatNum1], only
                // exception as lowercase
    u"CCC"_ustr,     // NF_KEY_CCC Currency abbreviation
    u"BOOLEAN"_ustr, // NF_KEY_BOOLEAN boolean
    u"GENERAL"_ustr, // NF_KEY_GENERAL General / Standard

    // Reserved words translated and color names follow:
    u"TRUE"_ustr,    // NF_KEY_TRUE boolean true
    u"FALSE"_ustr,   // NF_KEY_FALSE boolean false
    u"COLOR"_ustr,   // NF_KEY_COLOR color
        // colours
    u"BLACK"_ustr,   // NF_KEY_BLACK
    u"BLUE"_ustr,    // NF_KEY_BLUE
    u"GREEN"_ustr,   // NF_KEY_GREEN
    u"CYAN"_ustr,    // NF_KEY_CYAN
    u"RED"_ustr,     // NF_KEY_RED
    u"MAGENTA"_ustr, // NF_KEY_MAGENTA
    u"BROWN"_ustr,   // NF_KEY_BROWN
    u"GREY"_ustr,    // NF_KEY_GREY
    u"YELLOW"_ustr,  // NF_KEY_YELLOW
    u"WHITE"_ustr    // NF_KEY_WHITE
};

const ::std::vector<Color> ImpSvNumberformatScan::StandardColor{
    COL_BLACK,        COL_LIGHTBLUE, COL_LIGHTGREEN, COL_LIGHTCYAN, COL_LIGHTRED,
    COL_LIGHTMAGENTA, COL_BROWN,     COL_GRAY,       COL_YELLOW,    COL_WHITE
};

// This vector will hold *only* the color names in German language.
static const std::u16string_view& GermanColorName(size_t i)
{
    static const std::u16string_view sGermanColorNames[]{ u"FARBE",   u"SCHWARZ", u"BLAU",
                                                          u"GRÜN",    u"CYAN",    u"ROT",
                                                          u"MAGENTA", u"BRAUN",   u"GRAU",
                                                          u"GELB",    u"WEISS" };
    assert(i < SAL_N_ELEMENTS(sGermanColorNames));
    return sGermanColorNames[i];
}

ImpSvNumberformatScan::ImpSvNumberformatScan(SvNFLanguageData& rCurrentLanguageData,
                                             const SvNumberFormatter& rColorCallback,
                                             const Date& aNullDate)
    : maNullDate(aNullDate)
    , mrCurrentLanguageData(rCurrentLanguageData)
    , mrColorCallback(rColorCallback)
    , eNewLnge(LANGUAGE_DONTKNOW)
    , eTmpLnge(LANGUAGE_DONTKNOW)
    , nCurrPos(-1)
    , meKeywordLocalization(KeywordLocalization::AllowEnglish)
{
    xNFC = css::i18n::NumberFormatMapper::create(rCurrentLanguageData.GetComponentContext());
    bConvertMode = false;
    mbConvertDateOrder = false;
    bConvertSystemToSystem = false;
    bKeywordsNeedInit = true;            // locale dependent and not locale dependent keywords
    bCompatCurNeedInit = true;           // locale dependent compatibility currency strings

    static_assert( NF_KEY_BLACK - NF_KEY_COLOR == 1,        "bad FARBE(COLOR), SCHWARZ(BLACK) sequence");
    static_assert( NF_KEY_FIRSTCOLOR - NF_KEY_COLOR == 1,   "bad color sequence");
    static_assert( NF_MAX_DEFAULT_COLORS + 1 == 11,         "bad color count");
    static_assert( NF_KEY_WHITE - NF_KEY_COLOR + 1 == 11,   "bad color sequence count");

    nStandardPrec = 2;

    Reset();
}

ImpSvNumberformatScan::~ImpSvNumberformatScan()
{
    Reset();
}

void ImpSvNumberformatScan::ChangeIntl( KeywordLocalization eKeywordLocalization )
{
    meKeywordLocalization = eKeywordLocalization;
    bKeywordsNeedInit = true;
    bCompatCurNeedInit = true;
    // may be initialized by InitSpecialKeyword()
    sKeyword[NF_KEY_TRUE].clear();
    sKeyword[NF_KEY_FALSE].clear();
}

void ImpSvNumberformatScan::InitSpecialKeyword( NfKeywordIndex eIdx ) const
{
    switch ( eIdx )
    {
    case NF_KEY_TRUE :
        const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_TRUE] =
            mrCurrentLanguageData.GetCharClass()->uppercase( mrCurrentLanguageData.GetLocaleData()->getTrueWord() );
        if ( sKeyword[NF_KEY_TRUE].isEmpty() )
        {
            SAL_WARN( "svl.numbers""InitSpecialKeyword: TRUE_WORD?" );
            const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_TRUE] = sEnglishKeyword[NF_KEY_TRUE];
        }
        break;
    case NF_KEY_FALSE :
        const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_FALSE] =
            mrCurrentLanguageData.GetCharClass()->uppercase( mrCurrentLanguageData.GetLocaleData()->getFalseWord() );
        if ( sKeyword[NF_KEY_FALSE].isEmpty() )
        {
            SAL_WARN( "svl.numbers""InitSpecialKeyword: FALSE_WORD?" );
            const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_FALSE] = sEnglishKeyword[NF_KEY_FALSE];
        }
        break;
    default:
        SAL_WARN( "svl.numbers""InitSpecialKeyword: unknown request" );
    }
}

void ImpSvNumberformatScan::InitCompatCur() const
{
    ImpSvNumberformatScan* pThis = const_cast<ImpSvNumberformatScan*>(this);
    // currency symbol for old style ("automatic") compatibility format codes
    mrCurrentLanguageData.GetCompatibilityCurrency( pThis->sCurSymbol, pThis->sCurAbbrev );
    // currency symbol upper case
    pThis->sCurString = mrCurrentLanguageData.GetCharClass()->uppercase( sCurSymbol );
    bCompatCurNeedInit = false;
}

void ImpSvNumberformatScan::InitKeywords() const
{
    if ( !bKeywordsNeedInit )
        return ;
    const_cast<ImpSvNumberformatScan*>(this)->SetDependentKeywords();
    bKeywordsNeedInit = false;
}

/** Extract the name of General, Standard, Whatever, ignoring leading modifiers
    such as [NatNum1]. */

static OUString lcl_extractStandardGeneralName( const OUString & rCode )
{
    OUString aStr;
    const sal_Unicode* p = rCode.getStr();
    const sal_Unicode* const pStop = p + rCode.getLength();
    const sal_Unicode* pBeg = p;    // name begins here
    bool bMod = false;
    bool bDone = false;
    while (p < pStop && !bDone)
    {
        switch (*p)
        {
        case '[':
            bMod = true;
            break;
        case ']':
            if (bMod)
            {
                bMod = false;
                pBeg = p+1;
            }
            // else: would be a locale data error, easily to be spotted in
            // UI dialog
            break;
        case ';':
            if (!bMod)
            {
                bDone = true;
                --p;    // put back, increment by one follows
            }
            break;
        }
        ++p;
        if (bMod)
        {
            pBeg = p;
        }
    }
    if (pBeg < p)
    {
        aStr = rCode.copy( pBeg - rCode.getStr(), p - pBeg);
    }
    return aStr;
}

void ImpSvNumberformatScan::SetDependentKeywords()
{
    using namespace ::com::sun::star;
    using namespace ::com::sun::star::uno;

    const CharClass* pCharClass = mrCurrentLanguageData.GetCharClass();
    const LocaleDataWrapper* pLocaleData = mrCurrentLanguageData.GetLocaleData();
    // #80023# be sure to generate keywords for the loaded Locale, not for the
    // requested Locale, otherwise number format codes might not match
    const LanguageTag aLoadedLocale = pLocaleData->getLoadedLanguageTag();
    LanguageType eLang = aLoadedLocale.getLanguageType( false);

    bool bL10n = (meKeywordLocalization != KeywordLocalization::EnglishOnly);
    if (bL10n)
    {
        // Check if this actually is a locale that uses any localized keywords,
        // if not then disable localized keywords completely.
        if ( !eLang.anyOf( LANGUAGE_GERMAN,
                    LANGUAGE_GERMAN_SWISS,
                    LANGUAGE_GERMAN_AUSTRIAN,
                    LANGUAGE_GERMAN_LUXEMBOURG,
                    LANGUAGE_GERMAN_LIECHTENSTEIN,
                    LANGUAGE_DUTCH,
                    LANGUAGE_DUTCH_BELGIAN,
                    LANGUAGE_FRENCH,
                    LANGUAGE_FRENCH_BELGIAN,
                    LANGUAGE_FRENCH_CANADIAN,
                    LANGUAGE_FRENCH_SWISS,
                    LANGUAGE_FRENCH_LUXEMBOURG,
                    LANGUAGE_FRENCH_MONACO,
                    LANGUAGE_FINNISH,
                    LANGUAGE_ITALIAN,
                    LANGUAGE_ITALIAN_SWISS,
                    LANGUAGE_DANISH,
                    LANGUAGE_NORWEGIAN,
                    LANGUAGE_NORWEGIAN_BOKMAL,
                    LANGUAGE_NORWEGIAN_NYNORSK,
                    LANGUAGE_SWEDISH,
                    LANGUAGE_SWEDISH_FINLAND,
                    LANGUAGE_PORTUGUESE,
                    LANGUAGE_PORTUGUESE_BRAZILIAN,
                    LANGUAGE_SPANISH_MODERN,
                    LANGUAGE_SPANISH_DATED,
                    LANGUAGE_SPANISH_MEXICAN,
                    LANGUAGE_SPANISH_GUATEMALA,
                    LANGUAGE_SPANISH_COSTARICA,
                    LANGUAGE_SPANISH_PANAMA,
                    LANGUAGE_SPANISH_DOMINICAN_REPUBLIC,
                    LANGUAGE_SPANISH_VENEZUELA,
                    LANGUAGE_SPANISH_COLOMBIA,
                    LANGUAGE_SPANISH_PERU,
                    LANGUAGE_SPANISH_ARGENTINA,
                    LANGUAGE_SPANISH_ECUADOR,
                    LANGUAGE_SPANISH_CHILE,
                    LANGUAGE_SPANISH_URUGUAY,
                    LANGUAGE_SPANISH_PARAGUAY,
                    LANGUAGE_SPANISH_BOLIVIA,
                    LANGUAGE_SPANISH_EL_SALVADOR,
                    LANGUAGE_SPANISH_HONDURAS,
                    LANGUAGE_SPANISH_NICARAGUA,
                    LANGUAGE_SPANISH_PUERTO_RICO ))
        {
            bL10n = false;
            meKeywordLocalization = KeywordLocalization::EnglishOnly;
        }
    }

    // Init the current NfKeywordTable with English keywords.
    sKeyword = sEnglishKeyword;

    // Set the uppercase localized General name, e.g. Standard -> STANDARD
    i18n::NumberFormatCode aFormat = xNFC->getFormatCode( NF_NUMBER_STANDARD, aLoadedLocale.getLocale() );
    sNameStandardFormat = lcl_extractStandardGeneralName( aFormat.Code );
    sKeyword[NF_KEY_GENERAL] = pCharClass->uppercase( sNameStandardFormat );

    // Thai T NatNum special. Other locale's small letter 't' results in upper
    // case comparison not matching but length does in conversion mode. Ugly.
    if (eLang == LANGUAGE_THAI)
    {
        sKeyword[NF_KEY_THAI_T] = "T";
    }
    else
    {
        sKeyword[NF_KEY_THAI_T] = sEnglishKeyword[NF_KEY_THAI_T];
    }

    // boolean keywords
    InitSpecialKeyword( NF_KEY_TRUE );
    InitSpecialKeyword( NF_KEY_FALSE );

    // Boolean equivalent format codes that are written to Excel files, may
    // have been written to ODF as well, specifically if such loaded Excel file
    // was saved as ODF, and shall result in proper Boolean again.
    // "TRUE";"TRUE";"FALSE"
    sBooleanEquivalent1 = "\"" + sKeyword[NF_KEY_TRUE] + "\";\"" +
        sKeyword[NF_KEY_TRUE] + "\";\"" + sKeyword[NF_KEY_FALSE] + "\"";
    // [>0]"TRUE";[<0]"TRUE";"FALSE"
    sBooleanEquivalent2 = "[>0]\"" + sKeyword[NF_KEY_TRUE] + "\";[<0]\"" +
        sKeyword[NF_KEY_TRUE] + "\";\"" + sKeyword[NF_KEY_FALSE] + "\"";

    // compatibility currency strings
    InitCompatCur();

    if (!bL10n)
        return;

    // All locale dependent keywords overrides follow.

    if ( eLang.anyOf(
            LANGUAGE_GERMAN,
            LANGUAGE_GERMAN_SWISS,
            LANGUAGE_GERMAN_AUSTRIAN,
            LANGUAGE_GERMAN_LUXEMBOURG,
            LANGUAGE_GERMAN_LIECHTENSTEIN))
    {
        //! all capital letters
        sKeyword[NF_KEY_M] =         "M";     // month 1
        sKeyword[NF_KEY_MM] =        "MM";    // month 01
        sKeyword[NF_KEY_MMM] =       "MMM";   // month Jan
        sKeyword[NF_KEY_MMMM] =      "MMMM";  // month Januar
        sKeyword[NF_KEY_MMMMM] =     "MMMMM"// month J
        sKeyword[NF_KEY_H] =         "H";     // hour 2
        sKeyword[NF_KEY_HH] =        "HH";    // hour 02
        sKeyword[NF_KEY_D] =         "T";
        sKeyword[NF_KEY_DD] =        "TT";
        sKeyword[NF_KEY_DDD] =       "TTT";
        sKeyword[NF_KEY_DDDD] =      "TTTT";
        sKeyword[NF_KEY_YY] =        "JJ";
        sKeyword[NF_KEY_YYYY] =      "JJJJ";
        sKeyword[NF_KEY_BOOLEAN] =   "LOGISCH";
        sKeyword[NF_KEY_COLOR] =     GermanColorName(NF_KEY_COLOR - NF_KEY_COLOR);
        sKeyword[NF_KEY_BLACK] =     GermanColorName(NF_KEY_BLACK - NF_KEY_COLOR);
        sKeyword[NF_KEY_BLUE] =      GermanColorName(NF_KEY_BLUE - NF_KEY_COLOR);
        sKeyword[NF_KEY_GREEN] =     GermanColorName(NF_KEY_GREEN - NF_KEY_COLOR);
        sKeyword[NF_KEY_CYAN] =      GermanColorName(NF_KEY_CYAN - NF_KEY_COLOR);
        sKeyword[NF_KEY_RED] =       GermanColorName(NF_KEY_RED - NF_KEY_COLOR);
        sKeyword[NF_KEY_MAGENTA] =   GermanColorName(NF_KEY_MAGENTA - NF_KEY_COLOR);
        sKeyword[NF_KEY_BROWN] =     GermanColorName(NF_KEY_BROWN - NF_KEY_COLOR);
        sKeyword[NF_KEY_GREY] =      GermanColorName(NF_KEY_GREY - NF_KEY_COLOR);
        sKeyword[NF_KEY_YELLOW] =    GermanColorName(NF_KEY_YELLOW - NF_KEY_COLOR);
        sKeyword[NF_KEY_WHITE] =     GermanColorName(NF_KEY_WHITE - NF_KEY_COLOR);
    }
    else
    {
        // day
        if ( eLang.anyOf(
                LANGUAGE_ITALIAN,
                LANGUAGE_ITALIAN_SWISS))
        {
            sKeyword[NF_KEY_D] = "G";
            sKeyword[NF_KEY_DD] = "GG";
            sKeyword[NF_KEY_DDD] = "GGG";
            sKeyword[NF_KEY_DDDD] = "GGGG";
            // must exchange the era code, same as Xcl
            sKeyword[NF_KEY_G] = "X";
            sKeyword[NF_KEY_GG] = "XX";
            sKeyword[NF_KEY_GGG] = "XXX";
        }
        else if ( eLang.anyOf(
                 LANGUAGE_FRENCH,
                 LANGUAGE_FRENCH_BELGIAN,
                 LANGUAGE_FRENCH_CANADIAN,
                 LANGUAGE_FRENCH_SWISS,
                 LANGUAGE_FRENCH_LUXEMBOURG,
                 LANGUAGE_FRENCH_MONACO))
        {
            sKeyword[NF_KEY_D] = "J";
            sKeyword[NF_KEY_DD] = "JJ";
            sKeyword[NF_KEY_DDD] = "JJJ";
            sKeyword[NF_KEY_DDDD] = "JJJJ";
        }
        else if ( eLang == LANGUAGE_FINNISH )
        {
            sKeyword[NF_KEY_D] = "P";
            sKeyword[NF_KEY_DD] = "PP";
            sKeyword[NF_KEY_DDD] = "PPP";
            sKeyword[NF_KEY_DDDD] = "PPPP";
        }

        // month
        if ( eLang == LANGUAGE_FINNISH )
        {
            sKeyword[NF_KEY_M] = "K";
            sKeyword[NF_KEY_MM] = "KK";
            sKeyword[NF_KEY_MMM] = "KKK";
            sKeyword[NF_KEY_MMMM] = "KKKK";
            sKeyword[NF_KEY_MMMMM] = "KKKKK";
        }

        // year
        if ( eLang.anyOf(
            LANGUAGE_ITALIAN,
            LANGUAGE_ITALIAN_SWISS,
            LANGUAGE_FRENCH,
            LANGUAGE_FRENCH_BELGIAN,
            LANGUAGE_FRENCH_CANADIAN,
            LANGUAGE_FRENCH_SWISS,
            LANGUAGE_FRENCH_LUXEMBOURG,
            LANGUAGE_FRENCH_MONACO,
            LANGUAGE_PORTUGUESE,
            LANGUAGE_PORTUGUESE_BRAZILIAN,
            LANGUAGE_SPANISH_MODERN,
            LANGUAGE_SPANISH_DATED,
            LANGUAGE_SPANISH_MEXICAN,
            LANGUAGE_SPANISH_GUATEMALA,
            LANGUAGE_SPANISH_COSTARICA,
            LANGUAGE_SPANISH_PANAMA,
            LANGUAGE_SPANISH_DOMINICAN_REPUBLIC,
            LANGUAGE_SPANISH_VENEZUELA,
            LANGUAGE_SPANISH_COLOMBIA,
            LANGUAGE_SPANISH_PERU,
            LANGUAGE_SPANISH_ARGENTINA,
            LANGUAGE_SPANISH_ECUADOR,
            LANGUAGE_SPANISH_CHILE,
            LANGUAGE_SPANISH_URUGUAY,
            LANGUAGE_SPANISH_PARAGUAY,
            LANGUAGE_SPANISH_BOLIVIA,
            LANGUAGE_SPANISH_EL_SALVADOR,
            LANGUAGE_SPANISH_HONDURAS,
            LANGUAGE_SPANISH_NICARAGUA,
            LANGUAGE_SPANISH_PUERTO_RICO))
        {
            sKeyword[NF_KEY_YY] = "AA";
            sKeyword[NF_KEY_YYYY] = "AAAA";
            // must exchange the day of week name code, same as Xcl
            sKeyword[NF_KEY_AAA] =   "OOO";
            sKeyword[NF_KEY_AAAA] =  "OOOO";
        }
        else if ( eLang.anyOf(
             LANGUAGE_DUTCH,
             LANGUAGE_DUTCH_BELGIAN))
        {
            sKeyword[NF_KEY_YY] = "JJ";
            sKeyword[NF_KEY_YYYY] = "JJJJ";
        }
        else if ( eLang == LANGUAGE_FINNISH )
        {
            sKeyword[NF_KEY_YY] = "VV";
            sKeyword[NF_KEY_YYYY] = "VVVV";
        }

        // hour
        if ( eLang.anyOf(
             LANGUAGE_DUTCH,
             LANGUAGE_DUTCH_BELGIAN))
        {
            sKeyword[NF_KEY_H] = "U";
            sKeyword[NF_KEY_HH] = "UU";
        }
        else if ( eLang.anyOf(
            LANGUAGE_FINNISH,
            LANGUAGE_SWEDISH,
            LANGUAGE_SWEDISH_FINLAND,
            LANGUAGE_DANISH,
            LANGUAGE_NORWEGIAN,
            LANGUAGE_NORWEGIAN_BOKMAL,
            LANGUAGE_NORWEGIAN_NYNORSK))
        {
            sKeyword[NF_KEY_H] = "T";
            sKeyword[NF_KEY_HH] = "TT";
        }
    }
}

void ImpSvNumberformatScan::ChangeNullDate(sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear)
{
    Date aDate(nDay, nMonth, nYear);
    if (!aDate.IsValidDate())
    {
        aDate.Normalize();
        SAL_WARN("svl.numbers","ImpSvNumberformatScan::ChangeNullDate - not valid"
                " d: " << nDay << " m: " << nMonth << " y: " << nYear << " normalized to"
                " d: " << aDate.GetDay() << " m: " << aDate.GetMonth() << " y: " << aDate.GetYear());
        // Slap the caller if really bad, like year 0.
        assert(aDate.IsValidDate());
    }
    if (aDate.IsValidDate())
        maNullDate = aDate;
}

void ImpSvNumberformatScan::ChangeStandardPrec(sal_uInt16 nPrec)
{
    nStandardPrec = nPrec;
}

const Color* ImpSvNumberformatScan::GetColor(OUString& sStr) const
{
    OUString sString = mrCurrentLanguageData.GetCharClass()->uppercase(sStr);
    const NfKeywordTable & rKeyword = GetKeywords();
    size_t i = 0;
    while (i < NF_MAX_DEFAULT_COLORS && sString != rKeyword[NF_KEY_FIRSTCOLOR+i] )
    {
        i++;
    }
    if (i >= NF_MAX_DEFAULT_COLORS && meKeywordLocalization == KeywordLocalization::AllowEnglish)
    {
        LanguageType eLang = mrCurrentLanguageData.GetLocaleData()->getLoadedLanguageTag().getLanguageType( false);
        if ( eLang.anyOf(
                    LANGUAGE_GERMAN,
                    LANGUAGE_GERMAN_SWISS,
                    LANGUAGE_GERMAN_AUSTRIAN,
                    LANGUAGE_GERMAN_LUXEMBOURG,
                    LANGUAGE_GERMAN_LIECHTENSTEIN )) // only German uses localized color names
        {
            size_t j = 0;
            while ( j < NF_MAX_DEFAULT_COLORS && sString != sEnglishKeyword[NF_KEY_FIRSTCOLOR + j] )
            {
                ++j;
            }
            if ( j < NF_MAX_DEFAULT_COLORS )
            {
                i = j;
            }
        }
    }

    enum ColorKeywordConversion
    {
        None,
        GermanToEnglish,
        EnglishToGerman
    } eColorKeywordConversion(None);

    if (bConvertMode)
    {
        const bool bFromGerman = eTmpLnge.anyOf(
                LANGUAGE_GERMAN,
                LANGUAGE_GERMAN_SWISS,
                LANGUAGE_GERMAN_AUSTRIAN,
                LANGUAGE_GERMAN_LUXEMBOURG,
                LANGUAGE_GERMAN_LIECHTENSTEIN);
        const bool bToGerman = eNewLnge.anyOf(
                LANGUAGE_GERMAN,
                LANGUAGE_GERMAN_SWISS,
                LANGUAGE_GERMAN_AUSTRIAN,
                LANGUAGE_GERMAN_LUXEMBOURG,
                LANGUAGE_GERMAN_LIECHTENSTEIN);
        if (bFromGerman && !bToGerman)
            eColorKeywordConversion = ColorKeywordConversion::GermanToEnglish;
        else if (!bFromGerman && bToGerman)
            eColorKeywordConversion = ColorKeywordConversion::EnglishToGerman;
    }

    const Color* pResult = nullptr;
    if (i >= NF_MAX_DEFAULT_COLORS)
    {
        const OUString& rColorWord = rKeyword[NF_KEY_COLOR];
        bool bL10n = true;
        if ((bL10n = sString.startsWith(rColorWord)) ||
                ((meKeywordLocalization == KeywordLocalization::AllowEnglish) &&
                 sString.startsWith(sEnglishKeyword[NF_KEY_COLOR])))
        {
            sal_Int32 nPos = (bL10n ? rColorWord.getLength() : sEnglishKeyword[NF_KEY_COLOR].getLength());
            sStr = sStr.copy(nPos);
            sStr = comphelper::string::strip(sStr, ' ');
            switch (eColorKeywordConversion)
            {
                case ColorKeywordConversion::None:
                    sStr = rColorWord + sStr;
                break;
                case ColorKeywordConversion::GermanToEnglish:
                    sStr = sEnglishKeyword[NF_KEY_COLOR] + sStr;                    // Farbe -> COLOR
                break;
                case ColorKeywordConversion::EnglishToGerman:
                    sStr = GermanColorName(NF_KEY_COLOR - NF_KEY_COLOR) + sStr;   // Color -> FARBE
                break;
            }
            sString = sString.copy(nPos);
            sString = comphelper::string::strip(sString, ' ');

            if ( CharClass::isAsciiNumeric( sString ) )
            {
                sal_Int32 nIndex = sString.toInt32();
                if (nIndex > 0 && nIndex <= 64)
                {
                    pResult = GetUserDefColor(static_cast<sal_uInt16>(nIndex)-1);
                }
            }
        }
    }
    else
    {
        sStr.clear();
        switch (eColorKeywordConversion)
        {
            case ColorKeywordConversion::None:
                sStr = rKeyword[NF_KEY_FIRSTCOLOR+i];
            break;
            case ColorKeywordConversion::GermanToEnglish:
                sStr = sEnglishKeyword[NF_KEY_FIRSTCOLOR + i];                  // Rot -> RED
            break;
            case ColorKeywordConversion::EnglishToGerman:
                sStr = GermanColorName(NF_KEY_FIRSTCOLOR - NF_KEY_COLOR + i); // Red -> ROT
            break;
        }
        pResult = &(StandardColor[i]);
    }
    return pResult;
}

short ImpSvNumberformatScan::GetKeyWord( const OUString& sSymbol, sal_Int32 nPos, bool& rbFoundEnglish ) const
{
    OUString sString = mrCurrentLanguageData.GetCharClass()->uppercase( sSymbol, nPos, sSymbol.getLength() - nPos );
    const NfKeywordTable & rKeyword = GetKeywords();
    // #77026# for the Xcl perverts: the GENERAL keyword is recognized anywhere
    if (sString.startsWith( rKeyword[NF_KEY_GENERAL] ))
    {
        return NF_KEY_GENERAL;
    }
    if ((meKeywordLocalization == KeywordLocalization::AllowEnglish) &&
            sString.startsWith( sEnglishKeyword[NF_KEY_GENERAL]))
    {
        rbFoundEnglish = true;
        return NF_KEY_GENERAL;
    }

    // MUST be a reverse search to find longer strings first,
    // new keywords take precedence over old keywords,
    // skip colors et al after keywords.
    short i = NF_KEY_LASTKEYWORD;
    while (i > 0 && !sString.startsWith( rKeyword[i]))
    {
        i--;
    }
    if (i == 0 && meKeywordLocalization == KeywordLocalization::AllowEnglish)
    {
        // No localized (if so) keyword, try English keywords if keywords
        // are localized. That was already checked in SetDependentKeywords().
        i = NF_KEY_LASTKEYWORD;
        while (i > 0 && !sString.startsWith( sEnglishKeyword[i]))
        {
            i--;
        }
    }

    // The Thai T NatNum modifier during Xcl import.
    if (i == 0 && bConvertMode &&
        sString[0] == 'T' &&
        eTmpLnge == LANGUAGE_ENGLISH_US &&
        MsLangId::getRealLanguage( eNewLnge) == LANGUAGE_THAI)
    {
        i = NF_KEY_THAI_T;
    }
    return i; // 0 => not found
}

/**
 * Next_Symbol
 *
 * Splits up the input for further processing (by the Turing machine).
 *
 * Starting state = SsStar
 *
 * ---------------+-------------------+---------------------------+---------------
 * Old state      | Character read    | Event                     | New state
 * ---------------+-------------------+---------------------------+---------------
 * SsStart        | Character         | Symbol = Character        | SsGetWord
 *                |    "              | Type = String             | SsGetString
 *                |    \              | Type = String             | SsGetChar
 *                |    *              | Type = Star               | SsGetStar
 *                |    _              | Type = Blank              | SsGetBlank
 *                | @ # 0 ? / . , % [ | Symbol = Character;       |
 *                | ] ' Blank         | Type = Control character  | SsStop
 *                | $ - + ( ) :       | Type  = String;           |
 *                | Else              | Symbol = Character        | SsStop
 * ---------------|-------------------+---------------------------+---------------
 * SsGetChar      | Else              | Symbol = Character        | SsStop
 * ---------------+-------------------+---------------------------+---------------
 * GetString      | "                 |                           | SsStop
 *                | Else              | Symbol += Character       | GetString
 * ---------------+-------------------+---------------------------+---------------
 * SsGetWord      | Character         | Symbol += Character       |
 *                | + -        (E+ E-)| Symbol += Character       | SsStop
 *                | /          (AM/PM)| Symbol += Character       |
 *                | Else              | Pos--, if Key Type = Word | SsStop
 * ---------------+-------------------+---------------------------+---------------
 * SsGetStar      | Else              | Symbol += Character       | SsStop
 *                |                   | Mark special case *       |
 * ---------------+-------------------+---------------------------+---------------
 * SsGetBlank     | Else              | Symbol + =Character       | SsStop
 *                |                   | Mark special case  _      |
 * ---------------------------------------------------------------+--------------
 *
 * If we recognize a keyword in the state SsGetWord (even as the symbol's start text)
 * we write back the rest of the characters!
 */


namespace {

enum ScanState
{
    SsStop      = 0,
    SsStart     = 1,
    SsGetChar   = 2,
    SsGetString = 3,
    SsGetWord   = 4,
    SsGetStar   = 5,
    SsGetBlank  = 6
};

}

short ImpSvNumberformatScan::Next_Symbol( const OUString& rStr,
                                          sal_Int32& nPos,
                                          OUString& sSymbol ) const
{
    InitKeywords();
    const CharClass* pChrCls = mrCurrentLanguageData.GetCharClass();
    const LocaleDataWrapper* pLoc = mrCurrentLanguageData.GetLocaleData();
    short eType = 0;
    ScanState eState = SsStart;
    OUStringBuffer sSymbolBuffer;
    while ( nPos < rStr.getLength() && eState != SsStop )
    {
        sal_Unicode cToken = rStr[nPos++];
        switch (eState)
        {
        case SsStart:
            // Fetch any currency longer than one character and don't get
            // confused later on by "E/" or other combinations of letters
            // and meaningful symbols. Necessary for old automatic currency.
            // #96158# But don't do it if we're starting a "[...]" section,
            // for example a "[$...]" new currency symbol to not parse away
            // "$U" (symbol) of "[$UYU]" (abbreviation).
            if ( nCurrPos >= 0 && sCurString.getLength() > 1 &&
                 nPos-1 + sCurString.getLength() <= rStr.getLength() &&
                 (nPos <= 1 || rStr[nPos-2] != '[') )
            {
                OUString aTest = pChrCls->uppercase( rStr.copy( nPos-1, sCurString.getLength() ) );
                if ( aTest == sCurString )
                {
                    sSymbol = rStr.copy( --nPos, sCurString.getLength() );
                    nPos = nPos + sSymbol.getLength();
                    eType = NF_SYMBOLTYPE_STRING;
                    return eType;
                }
            }
            switch (cToken)
            {
            case '#':
            case '0':
            case '?':
            case '%':
            case '@':
            case '[':
            case ']':
            case ',':
            case '.':
            case '/':
            case '\'':
            case ' ':
            case ':':
            case '-':
                eType = NF_SYMBOLTYPE_DEL;
                sSymbolBuffer.append(OUStringChar(cToken));
                eState = SsStop;
                break;
            case '*':
                eType = NF_SYMBOLTYPE_STAR;
                sSymbolBuffer.append(OUStringChar(cToken));
                eState = SsGetStar;
                break;
            case '_':
                eType = NF_SYMBOLTYPE_BLANK;
                sSymbolBuffer.append(OUStringChar(cToken));
                eState = SsGetBlank;
                break;
            case '"':
                eType = NF_SYMBOLTYPE_STRING;
                eState = SsGetString;
                sSymbolBuffer.append(OUStringChar(cToken));
                break;
            case '\\':
                eType = NF_SYMBOLTYPE_STRING;
                eState = SsGetChar;
                sSymbolBuffer.append(OUStringChar(cToken));
                break;
            case '$':
            case '+':
            case '(':
            case ')':
                eType = NF_SYMBOLTYPE_STRING;
                eState = SsStop;
                sSymbolBuffer.append(OUStringChar(cToken));
                break;
            default :
                if (StringEqualsChar( mrCurrentLanguageData.GetNumDecimalSep(), cToken) ||
                    StringEqualsChar( mrCurrentLanguageData.GetNumThousandSep(), cToken) ||
                    StringEqualsChar( mrCurrentLanguageData.GetDateSep(), cToken) ||
                    StringEqualsChar( pLoc->getTimeSep(), cToken) ||
                    StringEqualsChar( pLoc->getTime100SecSep(), cToken))
                {
                    // Another separator than pre-known ASCII
                    eType = NF_SYMBOLTYPE_DEL;
                    sSymbolBuffer.append(OUStringChar(cToken));
                    eState = SsStop;
                }
                else if ( pChrCls->isLetter( rStr, nPos-1 ) )
                {
                    bool bFoundEnglish = false;
                    short nTmpType = GetKeyWord( rStr, nPos-1, bFoundEnglish);
                    if ( nTmpType )
                    {
                        bool bCurrency = false;
                        // "Automatic" currency may start with keyword,
                        // like "R" (Rand) and 'R' (era)
                        if ( nCurrPos >= 0 &&
                             nPos-1 + sCurString.getLength() <= rStr.getLength() &&
                             sCurString.startsWith( bFoundEnglish ? sEnglishKeyword[nTmpType] : sKeyword[nTmpType]))
                        {
                            OUString aTest = pChrCls->uppercase( rStr.copy( nPos-1, sCurString.getLength() ) );
                            if ( aTest == sCurString )
                            {
                                bCurrency = true;
                            }
                        }
                        if ( bCurrency )
                        {
                            eState = SsGetWord;
                            sSymbolBuffer.append(OUStringChar(cToken));
                        }
                        else
                        {
                            eType = nTmpType;
                            // The code to be advanced is the detected keyword,
                            // not necessarily the locale's keyword, but the
                            // symbol is to be the locale's keyword.
                            sal_Int32 nLen;
                            if (bFoundEnglish)
                            {
                                nLen = sEnglishKeyword[eType].getLength();
                                // Use the locale's General keyword name, not uppercase.
                                sSymbolBuffer = (eType == NF_KEY_GENERAL ? sNameStandardFormat : sKeyword[eType]);
                            }
                            else
                            {
                                nLen = sKeyword[eType].getLength();
                                // Preserve a locale's keyword's case as entered.
                                sSymbolBuffer = rStr.subView( nPos-1, nLen);
                            }
                            if ((eType == NF_KEY_E || IsAmbiguousE(eType)) && nPos < rStr.getLength())
                            {
                                sal_Unicode cNext = rStr[nPos];
                                switch ( cNext )
                                {
                                case '+' :
                                case '-' :  // E+ E- combine to one symbol
                                    sSymbolBuffer.append(OUStringChar(cNext));
                                    eType = NF_KEY_E;
                                    nPos++;
                                    break;
                                case '0' :
                                case '#' :  // scientific E without sign
                                    eType = NF_KEY_E;
                                    break;
                                }
                            }
                            nPos--;
                            nPos = nPos + nLen;
                            eState = SsStop;
                        }
                    }
                    else
                    {
                        eState = SsGetWord;
                        sSymbolBuffer.append(OUStringChar(cToken));
                    }
                }
                else
                {
                    eType = NF_SYMBOLTYPE_STRING;
                    eState = SsStop;
                    sSymbolBuffer.append(OUStringChar(cToken));
                }
                break;
            }
            break;
        case SsGetChar:
            sSymbolBuffer.append(OUStringChar(cToken));
            eState = SsStop;
            break;
        case SsGetString:
            if (cToken == '"')
            {
                eState = SsStop;
            }
            sSymbolBuffer.append(OUStringChar(cToken));
            break;
        case SsGetWord:
            if ( pChrCls->isLetter( rStr, nPos-1 ) )
            {
                bool bFoundEnglish = false;
                short nTmpType = GetKeyWord( rStr, nPos-1, bFoundEnglish);
                if ( nTmpType )
                {
                    // beginning of keyword, stop scan and put back
                    eType = NF_SYMBOLTYPE_STRING;
                    eState = SsStop;
                    nPos--;
                }
                else
                {
                    sSymbolBuffer.append(OUStringChar(cToken));
                }
            }
            else
            {
                bool bDontStop = false;
                sal_Unicode cNext;
                switch (cToken)
                {
                case '/'// AM/PM, A/P
                    if (nPos < rStr.getLength())
                    {
                        cNext = rStr[nPos];
                        if ( cNext == 'P' || cNext == 'p' )
                        {
                            sal_Int32 nLen = sSymbolBuffer.getLength();
                            if ( 1 <= nLen &&
                                    (sSymbolBuffer[0] == 'A' || sSymbolBuffer[0] == 'a') &&
                                    (nLen == 1 ||
                                     (nLen == 2 && (sSymbolBuffer[1] == 'M' || sSymbolBuffer[1] == 'm')
                                      && (rStr[nPos + 1] == 'M' || rStr[nPos + 1] == 'm'))))
                            {
                                sSymbolBuffer.append(OUStringChar(cToken));
                                bDontStop = true;
                            }
                        }
                    }
                    break;
                }
                // anything not recognized will stop the scan
                if (!bDontStop)
                {
                    eState = SsStop;
                    nPos--;
                    eType = NF_SYMBOLTYPE_STRING;
                }
            }
            break;
        case SsGetStar:
        case SsGetBlank:
            eState = SsStop;
            sSymbolBuffer.append(OUStringChar(cToken));
            break;
        default:
            break;
        } // of switch
    } // of while
    if (eState == SsGetWord)
    {
        eType = NF_SYMBOLTYPE_STRING;
    }
    sSymbol = sSymbolBuffer.makeStringAndClear();
    return eType;
}

sal_Int32 ImpSvNumberformatScan::Symbol_Division(const OUString& rString)
{
    nCurrPos = -1;
    // Do we have some sort of currency?
    OUString sString = mrCurrentLanguageData.GetCharClass()->uppercase(rString);
    sal_Int32 nCPos = 0;
    while (nCPos >= 0 && nCPos < sString.getLength())
    {
        nCPos = sString.indexOf(GetCurString(),nCPos);
        if (nCPos >= 0)
        {
            // In Quotes?
            sal_Int32 nQ = SvNumberformat::GetQuoteEnd( sString, nCPos );
            if ( nQ < 0 )
            {
                sal_Unicode c;
                if ( nCPos == 0 ||
                    ((c = sString[nCPos-1]) != '"'
                            && c != '\\') ) // dm can be protected by "dm \d
                {
                    nCurrPos = nCPos;
                    nCPos = -1;
                }
                else
                {
                    nCPos++; // Continue search
                }
            }
            else
            {
                nCPos = nQ + 1; // Continue search
            }
        }
    }
    nStringsCnt = 0;
    bool bStar = false// Is set on detecting '*'
    Reset();

    sal_Int32 nPos = 0;
    const sal_Int32 nLen = rString.getLength();
    while (nPos < nLen && nStringsCnt < NF_MAX_FORMAT_SYMBOLS)
    {
        nTypeArray[nStringsCnt] = Next_Symbol(rString, nPos, sStrArray[nStringsCnt]);
        if (nTypeArray[nStringsCnt] == NF_SYMBOLTYPE_STAR)
        { // Monitoring the '*'
            if (bStar)
            {
                return nPos; // Error: double '*'
            }
            else
            {
                // Valid only if there is a character following, else we are
                // at the end of a code that does not have a fill character
                // (yet?).
                if (sStrArray[nStringsCnt].getLength() < 2)
                    return nPos;
                bStar = true;
            }
        }
        nStringsCnt++;
    }

    return 0; // 0 => ok
}

void ImpSvNumberformatScan::SkipStrings(sal_uInt16& i, sal_Int32& nPos) const
{
    while (i < nStringsCnt && (   nTypeArray[i] == NF_SYMBOLTYPE_STRING
                               || nTypeArray[i] == NF_SYMBOLTYPE_BLANK
                               || nTypeArray[i] == NF_SYMBOLTYPE_STAR) )
    {
        nPos = nPos + sStrArray[i].getLength();
        i++;
    }
}

sal_uInt16 ImpSvNumberformatScan::PreviousKeyword(sal_uInt16 i) const
{
    short res = 0;
    if (i > 0 && i < nStringsCnt)
    {
        i--;
        while (i > 0 && nTypeArray[i] <= 0)
        {
            i--;
        }
        if (nTypeArray[i] > 0)
        {
            res = nTypeArray[i];
        }
    }
    return res;
}

sal_uInt16 ImpSvNumberformatScan::NextKeyword(sal_uInt16 i) const
{
    short res = 0;
    if (i < nStringsCnt-1)
    {
        i++;
        while (i < nStringsCnt-1 && nTypeArray[i] <= 0)
        {
            i++;
        }
        if (nTypeArray[i] > 0)
        {
            res = nTypeArray[i];
        }
    }
    return res;
}

short ImpSvNumberformatScan::PreviousType( sal_uInt16 i ) const
{
    if ( i > 0 && i < nStringsCnt )
    {
        do
        {
            i--;
        }
        while ( i > 0 && nTypeArray[i] == NF_SYMBOLTYPE_EMPTY );
        return nTypeArray[i];
    }
    return 0;
}

sal_Unicode ImpSvNumberformatScan::PreviousChar(sal_uInt16 i) const
{
    sal_Unicode res = ' ';
    if (i > 0 && i < nStringsCnt)
    {
        i--;
        while (i > 0 &&
               ( nTypeArray[i] == NF_SYMBOLTYPE_EMPTY ||
                 nTypeArray[i] == NF_SYMBOLTYPE_STRING ||
                 nTypeArray[i] == NF_SYMBOLTYPE_STAR ||
                 nTypeArray[i] == NF_SYMBOLTYPE_BLANK ))
        {
            i--;
        }
        if (sStrArray[i].getLength() > 0)
        {
            res = sStrArray[i][sStrArray[i].getLength()-1];
        }
    }
    return res;
}

sal_Unicode ImpSvNumberformatScan::NextChar(sal_uInt16 i) const
{
    sal_Unicode res = ' ';
    if (i < nStringsCnt-1)
    {
        i++;
        while (i < nStringsCnt-1 &&
               ( nTypeArray[i] == NF_SYMBOLTYPE_EMPTY ||
                 nTypeArray[i] == NF_SYMBOLTYPE_STRING ||
                 nTypeArray[i] == NF_SYMBOLTYPE_STAR ||
                 nTypeArray[i] == NF_SYMBOLTYPE_BLANK))
        {
            i++;
        }
        if (sStrArray[i].getLength() > 0)
        {
            res = sStrArray[i][0];
        }
    }
    return res;
}

bool ImpSvNumberformatScan::IsLastBlankBeforeFrac(sal_uInt16 i) const
{
    bool res = true;
    if (i < nStringsCnt-1)
    {
        bool bStop = false;
        i++;
        while (i < nStringsCnt-1 && !bStop)
        {
            i++;
            if ( nTypeArray[i] == NF_SYMBOLTYPE_DEL &&
                 sStrArray[i][0] == '/')
            {
                bStop = true;
            }
            else if ( ( nTypeArray[i] == NF_SYMBOLTYPE_DEL  &&
                        sStrArray[i][0] == ' ')             ||
                        nTypeArray[i] == NF_SYMBOLTYPE_STRING ) // integer/fraction delimiter can also be a string
            {
                res = false;
            }
        }
        if (!bStop) // no '/'{
        {
            res = false;
        }
    }
    else
    {
        res = false// no '/' any more
    }
    return res;
}

void ImpSvNumberformatScan::Reset()
{
    nStringsCnt = 0;
    nResultStringsCnt = 0;
    eScannedType = SvNumFormatType::UNDEFINED;
    bExp = false;
    bThousand = false;
    nThousand = 0;
    bDecSep = false;
    nDecPos = sal_uInt16(-1);
    nExpPos = sal_uInt16(-1);
    nBlankPos = sal_uInt16(-1);
    nCntPre = 0;
    nCntPost = 0;
    nCntExp = 0;
    bFrac = false;
    bBlank = false;
    nNatNumModifier = 0;
}

bool ImpSvNumberformatScan::Is100SecZero( sal_uInt16 i, bool bHadDecSep ) const
{
    sal_uInt16 nIndexPre = PreviousKeyword( i );
    return (nIndexPre == NF_KEY_S || nIndexPre == NF_KEY_SS) &&
            (bHadDecSep ||
             ( i > 0 && nTypeArray[i-1] == NF_SYMBOLTYPE_STRING));
              // SS"any"00  take "any" as a valid decimal separator
}

sal_Int32 ImpSvNumberformatScan::ScanType()
{
    const LocaleDataWrapper* pLoc = mrCurrentLanguageData.GetLocaleData();

    sal_Int32 nPos = 0;
    sal_uInt16 i = 0;
    SvNumFormatType eNewType;
    bool bMatchBracket = false;
    bool bHaveGeneral = false// if General/Standard encountered
    bool bIsTimeDetected =false;   // hour or second found in format
    bool bHaveMinute = false;

    SkipStrings(i, nPos);
    while (i < nStringsCnt)
    {
        if (nTypeArray[i] > 0)
        {   // keyword
            sal_uInt16 nIndexPre;
            sal_uInt16 nIndexNex;

            switch (nTypeArray[i])
            {
            case NF_KEY_E:                          // E
                eNewType = SvNumFormatType::SCIENTIFIC;
                break;
            case NF_KEY_H:                          // H
            case NF_KEY_HH:                         // HH
                bIsTimeDetected = true;
                [[fallthrough]];
            case NF_KEY_S:                          // S
            case NF_KEY_SS:                         // SS
                if ( !bHaveMinute )
                    bIsTimeDetected = true;
                [[fallthrough]];
            case NF_KEY_AMPM:                       // AM,A,PM,P
            case NF_KEY_AP:
                eNewType = SvNumFormatType::TIME;
                break;
            case NF_KEY_M:                          // M
            case NF_KEY_MM:                         // MM
            case NF_KEY_MI:                         // M  minute detected in Finnish
            case NF_KEY_MMI:                        // MM
                /* Minute or month.
                   Minute if one of:
                   * preceded by time keyword H (ignoring separators)
                   * followed by time keyword S (ignoring separators)
                   * H or S was detected and this is the first M following
                   * preceded by '[' amount bracket
                   Else month.
                   That are the Excel rules. BUT, we break it because certainly
                   in something like {HH YYYY-MM-DD} the MM is NOT meant to be
                   minute, so not if MM is between YY and DD or DD and YY.
                   Actually not if any date specific keyword followed a time
                   setting keyword.
                */

                nIndexPre = PreviousKeyword(i);
                nIndexNex = NextKeyword(i);
                if (nIndexPre == NF_KEY_H   ||      // H
                    nIndexPre == NF_KEY_HH  ||      // HH
                    nIndexNex == NF_KEY_S   ||      // S
                    nIndexNex == NF_KEY_SS  ||      // SS
                    bIsTimeDetected         ||      // tdf#101147
                    PreviousChar(i) == '['  )       // [M
                {
                    eNewType = SvNumFormatType::TIME;
                    if ( nTypeArray[i] == NF_KEY_M || nTypeArray[i] == NF_KEY_MM )
                    {
                        nTypeArray[i] -= 2;             // 6 -> 4, 7 -> 5
                    }
                    bIsTimeDetected = false;        // next M should be month
                    bHaveMinute = true;
                }
                else
                {
                    eNewType = SvNumFormatType::DATE;
                    if ( nTypeArray[i] == NF_KEY_MI || nTypeArray[i] == NF_KEY_MMI )
                    {   // follow resolution of tdf#33689 for Finnish
                        nTypeArray[i] += 2;             // 4 -> 6, 5 -> 7
                    }
                }
                break;
            case NF_KEY_MMM:                        // MMM
            case NF_KEY_MMMM:                       // MMMM
            case NF_KEY_MMMMM:                      // MMMMM
            case NF_KEY_Q:                          // Q
            case NF_KEY_QQ:                         // QQ
            case NF_KEY_D:                          // D
            case NF_KEY_DD:                         // DD
            case NF_KEY_DDD:                        // DDD
            case NF_KEY_DDDD:                       // DDDD
            case NF_KEY_YY:                         // YY
            case NF_KEY_YYYY:                       // YYYY
            case NF_KEY_NN:                         // NN
            case NF_KEY_NNN:                        // NNN
            case NF_KEY_NNNN:                       // NNNN
            case NF_KEY_WW :                        // WW
            case NF_KEY_AAA :                       // AAA
            case NF_KEY_AAAA :                      // AAAA
            case NF_KEY_EC :                        // E
            case NF_KEY_EEC :                       // EE
            case NF_KEY_G :                         // G
            case NF_KEY_GG :                        // GG
            case NF_KEY_GGG :                       // GGG
            case NF_KEY_R :                         // R
            case NF_KEY_RR :                        // RR
                eNewType = SvNumFormatType::DATE;
                bIsTimeDetected = false;
                break;
            case NF_KEY_CCC:                        // CCC
                eNewType = SvNumFormatType::CURRENCY;
                break;
            case NF_KEY_BOOLEAN:                    // BOOLEAN
                eNewType = SvNumFormatType::LOGICAL;
                break;
            case NF_KEY_GENERAL:                    // General
                eNewType = SvNumFormatType::NUMBER;
                bHaveGeneral = true;
                break;
            default:
                eNewType = SvNumFormatType::UNDEFINED;
                break;
            }
        }
        else
        {                                           // control character
            switch ( sStrArray[i][0] )
            {
            case '#':
            case '?':
                eNewType = SvNumFormatType::NUMBER;
                break;
            case '0':
                if ( eScannedType & SvNumFormatType::TIME )
                {
                    if ( Is100SecZero( i, bDecSep ) )
                    {
                        bDecSep = true;                 // subsequent 0's
                        eNewType = SvNumFormatType::TIME;
                    }
                    else
                    {
                        return nPos;                    // Error
                    }
                }
                else
                {
                    eNewType = SvNumFormatType::NUMBER;
                }
                break;
            case '%':
                eNewType = SvNumFormatType::PERCENT;
                break;
            case '/':
                eNewType = SvNumFormatType::FRACTION;
                break;
            case '[':
                if ( i < nStringsCnt-1 &&
                     nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
                     sStrArray[i+1][0] == '$' )
                {
                    eNewType = SvNumFormatType::CURRENCY;
                    bMatchBracket = true;
                }
                else if ( i < nStringsCnt-1 &&
                          nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
                          sStrArray[i+1][0] == '~' )
                {
                    eNewType = SvNumFormatType::DATE;
                    bMatchBracket = true;
                }
                else
                {
                    sal_uInt16 nIndexNex = NextKeyword(i);
                    if (nIndexNex == NF_KEY_H   ||  // H
                        nIndexNex == NF_KEY_HH  ||  // HH
                        nIndexNex == NF_KEY_M   ||  // M
                        nIndexNex == NF_KEY_MM  ||  // MM
                        nIndexNex == NF_KEY_S   ||  // S
                        nIndexNex == NF_KEY_SS   )  // SS
                        eNewType = SvNumFormatType::TIME;
                    else
                    {
                        return nPos;                // Error
                    }
                }
                break;
            case '@':
                eNewType = SvNumFormatType::TEXT;
                break;
            default:
                // Separator for SS,0
                if ((eScannedType & SvNumFormatType::TIME)
                        && 0 < i && (nTypeArray[i-1] == NF_KEY_S || nTypeArray[i-1] == NF_KEY_SS))
                {
                    // For ISO 8601 only YYYY-MM-DD"T"HH:MM:SS,0  accept both
                    // ',' and '.' regardless of locale's separator, and only
                    // those.
                    // XXX NOTE: this catches only known separators of
                    // NF_SYMBOLTYPE_DEL as all NF_SYMBOLTYPE_STRING are
                    // skipped during the loop. Meant to error out if the
                    // Time100SecSep or decimal separator differ and were used.
                    if ((eScannedType & SvNumFormatType::DATE)
                            && 11 <= i && i < nStringsCnt-1
                            && (nTypeArray[i-6] == NF_SYMBOLTYPE_STRING
                                && (sStrArray[i-6] == "\"T\"" || sStrArray[i-6] == "\\T"
                                    || sStrArray[i-6] == "T"))
                            && (nTypeArray[i-11] == NF_KEY_YYYY)
                            && (nTypeArray[i-9] == NF_KEY_M || nTypeArray[i-9] == NF_KEY_MM)
                            && (nTypeArray[i-7] == NF_KEY_D || nTypeArray[i-7] == NF_KEY_DD)
                            && (nTypeArray[i-5] == NF_KEY_H || nTypeArray[i-5] == NF_KEY_HH)
                            && (nTypeArray[i-3] == NF_KEY_MI || nTypeArray[i-3] == NF_KEY_MMI)
                            && (nTypeArray[i+1] == NF_SYMBOLTYPE_DEL && sStrArray[i+1][0] == '0'))

                    {
                        if (sStrArray[i].getLength() == 1 && (sStrArray[i][0] == ',' || sStrArray[i][0] == '.'))
                            bDecSep = true;
                        else
                            return nPos;            // Error
                    }
                    else if (pLoc->getTime100SecSep() == sStrArray[i])
                        bDecSep = true;
                    else if ( sStrArray[i][0] == ']' && i < nStringsCnt - 1 && pLoc->getTime100SecSep() == sStrArray[i+1] )
                    {
                        bDecSep = true;
                        i++;
                    }
                }
                eNewType = SvNumFormatType::UNDEFINED;
                break;
            }
        }
        if (eScannedType == SvNumFormatType::UNDEFINED)
        {
            eScannedType = eNewType;
        }
        else if (eScannedType == SvNumFormatType::TEXT || eNewType == SvNumFormatType::TEXT)
        {
            eScannedType = SvNumFormatType::TEXT; // Text always remains text
        }
        else if (eNewType == SvNumFormatType::UNDEFINED)
        { // Remains as is
        }
        else if (eScannedType != eNewType)
        {
            switch (eScannedType)
            {
            case SvNumFormatType::DATE:
                switch (eNewType)
                {
                case SvNumFormatType::TIME:
                    eScannedType = SvNumFormatType::DATETIME;
                    break;
                case SvNumFormatType::FRACTION:         // DD/MM
                    break;
                default:
                    if (nCurrPos >= 0)
                    {
                        eScannedType = SvNumFormatType::UNDEFINED;
                    }
                    else if ( sStrArray[i] != mrCurrentLanguageData.GetDateSep() )
                    {
                        return nPos;
                    }
                }
                break;
            case SvNumFormatType::TIME:
                switch (eNewType)
                {
                case SvNumFormatType::DATE:
                    eScannedType = SvNumFormatType::DATETIME;
                    break;
                case SvNumFormatType::FRACTION:         // MM/SS
                    break;
                default:
                    if (nCurrPos >= 0)
                    {
                        eScannedType = SvNumFormatType::UNDEFINED;
                    }
                    else if (pLoc->getTimeSep() != sStrArray[i])
                    {
                        return nPos;
                    }
                    break;
                }
                break;
            case SvNumFormatType::DATETIME:
                switch (eNewType)
                {
                case SvNumFormatType::TIME:
                case SvNumFormatType::DATE:
                    break;
                case SvNumFormatType::FRACTION:         // DD/MM
                    break;
                default:
                    if (nCurrPos >= 0)
                    {
                        eScannedType = SvNumFormatType::UNDEFINED;
                    }
                    else if ( mrCurrentLanguageData.GetDateSep() != sStrArray[i] &&
                              pLoc->getTimeSep() != sStrArray[i] )
                    {
                        return nPos;
                    }
                }
                break;
            case SvNumFormatType::PERCENT:
            case SvNumFormatType::SCIENTIFIC:
            case SvNumFormatType::FRACTION:
                switch (eNewType)
                {
                case SvNumFormatType::NUMBER:
                    break;
                default:
                    return nPos;
                }
                break;
            case SvNumFormatType::NUMBER:
                switch (eNewType)
                {
                case SvNumFormatType::SCIENTIFIC:
                case SvNumFormatType::PERCENT:
                case SvNumFormatType::FRACTION:
                case SvNumFormatType::CURRENCY:
                    eScannedType = eNewType;
                    break;
                default:
                    if (nCurrPos >= 0)
                    {
                        eScannedType = SvNumFormatType::UNDEFINED;
                    }
                    else
                    {
                        return nPos;
                    }
                }
                break;
            default:
                break;
            }
        }
        nPos = nPos + sStrArray[i].getLength(); // Position of correction
        i++;
        if ( bMatchBracket )
        {   // no type detection inside of matching brackets if [$...], [~...]
            while ( bMatchBracket && i < nStringsCnt )
            {
                if ( nTypeArray[i] == NF_SYMBOLTYPE_DEL
                     && sStrArray[i][0] == ']' )
                {
                    bMatchBracket = false;
                }
                else
                {
                    nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                }
                nPos = nPos + sStrArray[i].getLength();
                i++;
            }
            if ( bMatchBracket )
            {
                return nPos; // missing closing bracket at end of code
            }
        }
        SkipStrings(i, nPos);
    }

    if ((eScannedType == SvNumFormatType::NUMBER ||
         eScannedType == SvNumFormatType::UNDEFINED) &&
        nCurrPos >= 0 && !bHaveGeneral)
    {
        eScannedType = SvNumFormatType::CURRENCY; // old "automatic" currency
    }
    if (eScannedType == SvNumFormatType::UNDEFINED)
    {
        eScannedType = SvNumFormatType::DEFINED;
    }
    return 0; // All is fine
}

bool ImpSvNumberformatScan::InsertSymbol( sal_uInt16 & nPos, svt::NfSymbolType eType, const OUString& rStr )
{
    if (nStringsCnt >= NF_MAX_FORMAT_SYMBOLS || nPos > nStringsCnt)
    {
        return false;
    }
    if (nPos > 0 && nTypeArray[nPos-1] == NF_SYMBOLTYPE_EMPTY)
    {
        --nPos; // reuse position
    }
    else
    {
        if (nStringsCnt >= NF_MAX_FORMAT_SYMBOLS - 1)
        {
            return false;
        }
        ++nStringsCnt;
        sal_uInt16 i = nStringsCnt;
        while (i > nPos)
        {
            sal_uInt16 nexti = o3tl::sanitizing_dec(i);
            nTypeArray[i] = nTypeArray[nexti];
            sStrArray[i] = sStrArray[nexti];
            i = nexti;
        }
    }
    ++nResultStringsCnt;
    nTypeArray[nPos] = static_cast<short>(eType);
    sStrArray[nPos] = rStr;
    return true;
}

int ImpSvNumberformatScan::FinalScanGetCalendar( sal_Int32& nPos, sal_uInt16& ;i,
                                                 sal_uInt16& rResultStringsCnt )
{
    if ( i < nStringsCnt-1 &&
         sStrArray[i][0] == '[' &&
         nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
         sStrArray[i+1][0] == '~' )
    {
        // [~calendarID]
        nPos = nPos + sStrArray[i].getLength();           // [
        nTypeArray[i] = NF_SYMBOLTYPE_CALDEL;
        nPos = nPos + sStrArray[++i].getLength();         // ~
        sStrArray[i-1] += sStrArray[i];                   // [~
        nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
        rResultStringsCnt--;
        if ( ++i >= nStringsCnt )
        {
            return -1; // error
        }
        nPos = nPos + sStrArray[i].getLength();           // calendarID
        OUString& rStr = sStrArray[i];
        nTypeArray[i] = NF_SYMBOLTYPE_CALENDAR;          // convert
        i++;
        while ( i < nStringsCnt && sStrArray[i][0] != ']' )
        {
            nPos = nPos + sStrArray[i].getLength();
            rStr += sStrArray[i];
            nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
            rResultStringsCnt--;
            i++;
        }
        if ( rStr.getLength() && i < nStringsCnt &&
             sStrArray[i][0] == ']' )
        {
            nTypeArray[i] = NF_SYMBOLTYPE_CALDEL;
            nPos = nPos + sStrArray[i].getLength();
            i++;
        }
        else
        {
            return -1; // error
        }
        return 1;
    }
    return 0;
}

bool ImpSvNumberformatScan::IsDateFragment( size_t nPos1, size_t nPos2 ) const
{
    return nPos2 - nPos1 == 2 && nTypeArray[nPos1+1] == NF_SYMBOLTYPE_DATESEP;
}

void ImpSvNumberformatScan::SwapArrayElements( size_t nPos1, size_t nPos2 )
{
    std::swap( nTypeArray[nPos1], nTypeArray[nPos2]);
    std::swap( sStrArray[nPos1], sStrArray[nPos2]);
}

sal_Int32 ImpSvNumberformatScan::FinalScan( OUString& rString )
{
    const LocaleDataWrapper* pLoc = mrCurrentLanguageData.GetLocaleData();

    // save values for convert mode
    OUString sOldDecSep       = mrCurrentLanguageData.GetNumDecimalSep();
    OUString sOldThousandSep  = mrCurrentLanguageData.GetNumThousandSep();
    OUString sOldDateSep      = mrCurrentLanguageData.GetDateSep();
    OUString sOldTimeSep      = pLoc->getTimeSep();
    OUString sOldTime100SecSep= pLoc->getTime100SecSep();
    OUString sOldCurSymbol    = GetCurSymbol();
    OUString sOldCurString = GetCurString();
    sal_Unicode cOldKeyH    = sKeyword[NF_KEY_H][0];
    sal_Unicode cOldKeyMI   = sKeyword[NF_KEY_MI][0];
    sal_Unicode cOldKeyS    = sKeyword[NF_KEY_S][0];
    DateOrder eOldDateOrder = pLoc->getDateOrder();
    sal_uInt16 nDayPos, nMonthPos, nYearPos;
    nDayPos = nMonthPos = nYearPos = SAL_MAX_UINT16;

    // If the group separator is a No-Break Space (French) continue with a
    // normal space instead so queries on space work correctly.
    // The same for Narrow No-Break Space just in case some locale uses it.
    // The format string is adjusted to allow both.
    // For output of the format code string the LocaleData characters are used.
    if ( (sOldThousandSep[0] == cNoBreakSpace || sOldThousandSep[0] == cNarrowNoBreakSpace) &&
            sOldThousandSep.getLength() == 1 )
    {
        sOldThousandSep = " ";
    }
    bool bNewDateOrder = false;
    // change locale data et al
    if (bConvertMode)
    {
        mrCurrentLanguageData.ChangeIntl(eNewLnge);
        //! pointer may have changed
        pLoc = mrCurrentLanguageData.GetLocaleData();
        //! init new keywords
        InitKeywords();
        // Adapt date order to target locale, but Excel does not handle date
        // particle re-ordering for the target locale when loading documents,
        // though it does exchange separators, tdf#113889
        bNewDateOrder = (mbConvertDateOrder && eOldDateOrder != pLoc->getDateOrder());
    }
    const CharClass* pChrCls = mrCurrentLanguageData.GetCharClass();

    sal_Int32 nPos = 0;                    // error correction position
    sal_uInt16 i = 0;                      // symbol loop counter
    sal_uInt16 nCounter = 0;               // counts digits
    nResultStringsCnt = nStringsCnt;       // counts remaining symbols
    bDecSep = false;                       // reset in case already used in TypeCheck
    bool bThaiT = false;                   // Thai T NatNum modifier present
    bool bTimePart = false;
    bool bDenomin = false;                 // Set when reading end of denominator

    switch (eScannedType)
    {
    case SvNumFormatType::TEXT:
    case SvNumFormatType::DEFINED:
        while (i < nStringsCnt)
        {
            switch (nTypeArray[i])
            {
            case NF_SYMBOLTYPE_BLANK:
            case NF_SYMBOLTYPE_STAR:
                break;
            case NF_KEY_GENERAL : // #77026# "General" is the same as "@"
                break;
            default:
                if ( nTypeArray[i] != NF_SYMBOLTYPE_DEL ||
                     sStrArray[i][0] != '@' )
                {
                    nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                }
                break;
            }
            nPos = nPos + sStrArray[i].getLength();
            i++;
        } // of while
        break;

    case SvNumFormatType::NUMBER:
    case SvNumFormatType::PERCENT:
    case SvNumFormatType::CURRENCY:
    case SvNumFormatType::SCIENTIFIC:
    case SvNumFormatType::FRACTION:
        while (i < nStringsCnt)
        {
            // TODO: rechecking eScannedType is unnecessary.
            // This switch-case is for eScannedType == SvNumFormatType::FRACTION anyway
            if (eScannedType == SvNumFormatType::FRACTION &&        // special case
                nTypeArray[i] == NF_SYMBOLTYPE_DEL &&           // # ### #/#
                StringEqualsChar( sOldThousandSep, ' ' ) &&     // e.g. France or Sweden
                StringEqualsChar( sStrArray[i], ' ' ) &&
                !bFrac                          &&
                IsLastBlankBeforeFrac(i) )
            {
                nTypeArray[i] = NF_SYMBOLTYPE_STRING;           // del->string
            }                                                   // No thousands marker

            if (nTypeArray[i] == NF_SYMBOLTYPE_BLANK    ||
                nTypeArray[i] == NF_SYMBOLTYPE_STAR ||
                nTypeArray[i] == NF_KEY_CCC         ||          // CCC
                nTypeArray[i] == NF_KEY_GENERAL )               // Standard
            {
                if (nTypeArray[i] == NF_KEY_GENERAL)
                {
                    nThousand = FLAG_STANDARD_IN_FORMAT;
                    if ( bConvertMode )
                    {
                        sStrArray[i] = sNameStandardFormat;
                    }
                }
                nPos = nPos + sStrArray[i].getLength();
                i++;
            }
            else if (nTypeArray[i] == NF_SYMBOLTYPE_STRING ||   // No Strings or
                     nTypeArray[i] > 0)                         // Keywords
            {
                if (eScannedType == SvNumFormatType::SCIENTIFIC &&
                    nTypeArray[i] == NF_KEY_E)                  // E+
                {
                    if (bExp)                                   // Double
                    {
                        return nPos;
                    }
                    bExp = true;
                    nExpPos = i;
                    if (bDecSep)
                    {
                        nCntPost = nCounter;
                    }
                    else
                    {
                        nCntPre = nCounter;
                    }
                    nCounter = 0;
                    nTypeArray[i] = NF_SYMBOLTYPE_EXP;
                }
                else if (eScannedType == SvNumFormatType::FRACTION &&
                    (sStrArray[i][0] == ' ' || ( nTypeArray[i] == NF_SYMBOLTYPE_STRING && (sStrArray[i][0] < '0' || sStrArray[i][0] > '9') ) ) )
                {
                    if (!bBlank && !bFrac) // Not double or after a /
                    {
                        if (bDecSep && nCounter > 0) // Decimal places
                        {
                            return nPos; // Error
                        }
                        if (sStrArray[i][0] == ' ' ||  nCounter > 0 )   // treat string as integer/fraction delimiter only if there is integer
                        {
                            bBlank = true;
                            nBlankPos = i;
                            nCntPre = nCounter;
                            nCounter = 0;
                            nTypeArray[i] = NF_SYMBOLTYPE_FRACBLANK;
                        }
                    }
                    else if ( sStrArray[i][0] == ' ' )
                        nTypeArray[i] = NF_SYMBOLTYPE_FRACBLANK;
                    else if ( bFrac && ( nCounter > 0 ) )
                        bDenomin = true// following elements are no more part of denominator
                }
                else if (nTypeArray[i] == NF_KEY_THAI_T)
                {
                    bThaiT = true;
                    sStrArray[i] = sKeyword[nTypeArray[i]];
                }
                else if (sStrArray[i][0] >= '0' &&
                         sStrArray[i][0] <= '9' && !bDenomin) // denominator was not yet found
                {
                    OUString sDiv;
                    sal_uInt16 j = i;
                    while(j < nStringsCnt && sStrArray[j][0] >= '0' && sStrArray[j][0] <= '9')
                    {
                        sDiv += sStrArray[j++];
                    }
                    assert(j > 0 && "if i is 0, first iteration through loop is guaranteed by surrounding if condition");
                    if (std::u16string_view(OUString::number(sDiv.toInt32())) == sDiv)
                    {
                        // Found a Divisor
                        while (i < j)
                        {
                            nTypeArray[i++] = NF_SYMBOLTYPE_FRAC_FDIV;
                        }
                        i = j - 1; // Stop the loop
                        if (nCntPost)
                        {
                            nCounter = nCntPost;
                        }
                        else if (nCntPre)
                        {
                            nCounter = nCntPre;
                        }
                        // don't artificially increment nCntPre for forced denominator
                        if ( ( eScannedType != SvNumFormatType::FRACTION ) && (!nCntPre) )
                        {
                            nCntPre++;
                        }
                        if ( bFrac )
                            bDenomin = true// next content should be treated as outside denominator
                    }
                }
                else
                {
                    if ( bFrac && ( nCounter > 0 ) )
                        bDenomin = true;    // next content should be treated as outside denominator
                    nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                }
                nPos = nPos + sStrArray[i].getLength();
                i++;
            }
            else if (nTypeArray[i] == NF_SYMBOLTYPE_DEL)
            {
                sal_Unicode cHere = sStrArray[i][0];
                sal_Unicode cSaved = cHere;
                // Handle not pre-known separators in switch.
                sal_Unicode cSimplified;
                if (StringEqualsChar( mrCurrentLanguageData.GetNumThousandSep(), cHere))
                {
                    cSimplified = ',';
                }
                else if (StringEqualsChar( mrCurrentLanguageData.GetNumDecimalSep(), cHere))
                {
                    cSimplified = '.';
                }
                else
                {
                    cSimplified = cHere;
                }

                OUString& rStr = sStrArray[i];

                switch ( cSimplified )
                {
                case '#':
                case '0':
                case '?':
                    if (nThousand > 0)                  // #... #
                    {
                        return nPos;                    // Error
                    }
                    if ( !bDenomin )
                    {
                        nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
                        nPos = nPos + rStr.getLength();
                        i++;
                        nCounter++;
                        while (i < nStringsCnt &&
                              (sStrArray[i][0] == '#' ||
                               sStrArray[i][0] == '0' ||
                               sStrArray[i][0] == '?'))
                        {
                            nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
                            nPos = nPos + sStrArray[i].getLength();
                            nCounter++;
                            i++;
                        }
                    }
                    else // after denominator, treat any character as text
                    {
                        nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                        nPos = nPos + sStrArray[i].getLength();
                    }
                    break;
                case '-':
                    if ( bDecSep && nDecPos+1 == i &&
                         nTypeArray[nDecPos] == NF_SYMBOLTYPE_DECSEP )
                    {
                        // "0.--"
                        nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
                        nPos = nPos + rStr.getLength();
                        i++;
                        nCounter++;
                        while (i < nStringsCnt &&
                               (sStrArray[i][0] == '-') )
                        {
                            // If more than two dashes are present in
                            // currency formats the last dash will be
                            // interpreted literally as a minus sign.
                            // Has to be this ugly. Period.
                            if ( eScannedType == SvNumFormatType::CURRENCY
                                 && rStr.getLength() >= 2 &&
                                 (i == nStringsCnt-1 ||
                                  sStrArray[i+1][0] != '-') )
                            {
                                break;
                            }
                            rStr += sStrArray[i];
                            nPos = nPos + sStrArray[i].getLength();
                            nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                            nResultStringsCnt--;
                            nCounter++;
                            i++;
                        }
                    }
                    else
                    {
                        nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                        nPos = nPos + sStrArray[i].getLength();
                        i++;
                    }
                    break;
                case '.':
                case ',':
                case '\'':
                case ' ':
                    if ( StringEqualsChar( sOldThousandSep, cSaved ) )
                    {
                        // previous char with skip empty
                        sal_Unicode cPre = PreviousChar(i);
                        sal_Unicode cNext;
                        if (bExp || bBlank || bFrac)
                        {
                            // after E, / or ' '
                            if ( !StringEqualsChar( sOldThousandSep, ' ' ) )
                            {
                                nPos = nPos + sStrArray[i].getLength();
                                nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                                nResultStringsCnt--;
                                i++; // eat it
                            }
                            else
                            {
                                nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                                if ( bFrac && (nCounter > 0) )
                                    bDenomin = true// end of denominator
                            }
                        }
                        else if (i > 0 && i < nStringsCnt-1   &&
                                 (cPre == '#' || cPre == '0' || cPre == '?')      &&
                                 ((cNext = NextChar(i)) == '#' || cNext == '0' || cNext == '?')) // #,#
                        {
                            nPos = nPos + sStrArray[i].getLength();
                            if (!bThousand) // only once
                            {
                                bThousand = true;
                            }
                            // Eat it, will be reinserted at proper grouping positions further down.
                            nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                            nResultStringsCnt--;
                            i++;
                        }
                        else if (i > 0 && (cPre == '#' || cPre == '0' || cPre == '?')
                                 && PreviousType(i) == NF_SYMBOLTYPE_DIGIT
                                 && nThousand < FLAG_STANDARD_IN_FORMAT )
                        {   // #,,,,
                            if ( StringEqualsChar( sOldThousandSep, ' ' ) )
                            {
                                // strange, those French...
                                bool bFirst = true;
                                //  set a hard No-Break Space or ConvertMode
                                const OUString& rSepF = mrCurrentLanguageData.GetNumThousandSep();
                                while ( i < nStringsCnt &&
                                        sStrArray[i] == sOldThousandSep &&
                                        StringEqualsChar( sOldThousandSep, NextChar(i) ) )
                                {   // last was a space or another space
                                    // is following => separator
                                    nPos = nPos + sStrArray[i].getLength();
                                    if ( bFirst )
                                    {
                                        bFirst = false;
                                        rStr = rSepF;
                                        nTypeArray[i] = NF_SYMBOLTYPE_THSEP;
                                    }
                                    else
                                    {
                                        rStr += rSepF;
                                        nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                                        nResultStringsCnt--;
                                    }
                                    nThousand++;
                                    i++;
                                }
                                if ( i < nStringsCnt-1 &&
                                     sStrArray[i] == sOldThousandSep )
                                {
                                    // something following last space
                                    // => space if currency contained,
                                    // else separator
                                    nPos = nPos + sStrArray[i].getLength();
                                    if ( (nPos <= nCurrPos &&
                                          nCurrPos < nPos + sStrArray[i+1].getLength()) ||
                                         nTypeArray[i+1] == NF_KEY_CCC ||
                                         (i < nStringsCnt-2 &&
                                          sStrArray[i+1][0] == '[' &&
                                          sStrArray[i+2][0] == '$') )
                                    {
                                        nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                                    }
                                    else
                                    {
                                        if ( bFirst )
                                        {
                                            rStr = rSepF;
                                            nTypeArray[i] = NF_SYMBOLTYPE_THSEP;
                                        }
                                        else
                                        {
                                            rStr += rSepF;
                                            nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                                            nResultStringsCnt--;
                                        }
                                        nThousand++;
                                    }
                                    i++;
                                }
                            }
                            else
                            {
                                do
                                {
                                    nThousand++;
                                    nTypeArray[i] = NF_SYMBOLTYPE_THSEP;
                                    nPos = nPos + sStrArray[i].getLength();
                                    sStrArray[i] = mrCurrentLanguageData.GetNumThousandSep();
                                    i++;
                                }
                                while (i < nStringsCnt && sStrArray[i] == sOldThousandSep);
                            }
                        }
                        else // any grsep
                        {
                            nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                            nPos = nPos + rStr.getLength();
                            i++;
                            while ( i < nStringsCnt && sStrArray[i] == sOldThousandSep )
                            {
                                rStr += sStrArray[i];
                                nPos = nPos + sStrArray[i].getLength();
                                nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                                nResultStringsCnt--;
                                i++;
                            }
                        }
                    }
                    else if ( StringEqualsChar( sOldDecSep, cSaved ) )
                    {
                        if (bBlank || bFrac)    // . behind / or ' '
                        {
                            return nPos;        // error
                        }
                        else if (bExp)          // behind E
                        {
                            nPos = nPos + sStrArray[i].getLength();
                            nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                            nResultStringsCnt--;
                            i++;                // eat it
                        }
                        else if (bDecSep)       // any .
                        {
                            nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                            nPos = nPos + rStr.getLength();
                            i++;
                            while ( i < nStringsCnt && sStrArray[i] == sOldDecSep )
                            {
                                rStr += sStrArray[i];
                                nPos = nPos + sStrArray[i].getLength();
                                nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                                nResultStringsCnt--;
                                i++;
                            }
                        }
                        else
                        {
                            nPos = nPos + sStrArray[i].getLength();
                            nTypeArray[i] = NF_SYMBOLTYPE_DECSEP;
                            sStrArray[i] = mrCurrentLanguageData.GetNumDecimalSep();
                            bDecSep = true;
                            nDecPos = i;
                            nCntPre = nCounter;
                            nCounter = 0;

                            i++;
                        }
                    } // of else = DecSep
                    else // . without meaning
                    {
                        if (cSaved == ' ' &&
                            eScannedType == SvNumFormatType::FRACTION &&
                            StringEqualsChar( sStrArray[i], ' ' ) )
                        {
                            if (!bBlank && !bFrac)  // no dups
                            {                       // or behind /
                                if (bDecSep && nCounter > 0) // dec.
                                {
                                    return nPos; // error
                                }
                                bBlank = true;
                                nBlankPos = i;
                                nCntPre = nCounter;
                                nCounter = 0;
                            }
                            if ( bFrac && (nCounter > 0) )
                                bDenomin = true// next content is not part of denominator
                            nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                            nPos = nPos + sStrArray[i].getLength();
                        }
                        else
                        {
                            nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                            if ( bFrac && (nCounter > 0) )
                                bDenomin = true// next content is not part of denominator
                            nPos = nPos + rStr.getLength();
                            i++;
                            while (i < nStringsCnt && StringEqualsChar( sStrArray[i], cSaved ) )
                            {
                                rStr += sStrArray[i];
                                nPos = nPos + sStrArray[i].getLength();
                                nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                                nResultStringsCnt--;
                                i++;
                            }
                        }
                    }
                    break;
                case '/':
                    if (eScannedType == SvNumFormatType::FRACTION)
                    {
                        if ( i == 0 ||
                             (nTypeArray[i-1] != NF_SYMBOLTYPE_DIGIT &&
                              nTypeArray[i-1] != NF_SYMBOLTYPE_EMPTY) )
                        {
                            return nPos ? nPos : 1; // /? not allowed
                        }
                        else if (!bFrac || (bDecSep && nCounter > 0))
                        {
                            bFrac = true;
                            nCntPost = nCounter;
                            nCounter = 0;
                            nTypeArray[i] = NF_SYMBOLTYPE_FRAC;
                            nPos = nPos + sStrArray[i].getLength();
                            i++;
                        }
                        else // / double or in , in the denominator
                        {
                            return nPos; // Error
                        }
                    }
                    else
                    {
                        nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                        nPos = nPos + sStrArray[i].getLength();
                        i++;
                    }
                    break;
                case '[' :
                    if ( eScannedType == SvNumFormatType::CURRENCY &&
                         i < nStringsCnt-1 &&
                         nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
                         sStrArray[i+1][0] == '$' )
                    {
                        // [$DM-xxx]
                        nPos = nPos + sStrArray[i].getLength();     // [
                        nTypeArray[i] = NF_SYMBOLTYPE_CURRDEL;
                        nPos = nPos + sStrArray[++i].getLength();   // $
                        sStrArray[i-1] += sStrArray[i];             // [$
                        nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                        nResultStringsCnt--;
                        if ( ++i >= nStringsCnt )
                        {
                            return nPos; // Error
                        }
                        nPos = nPos + sStrArray[i].getLength();     // DM
                        OUString* pStr = &sStrArray[i];
                        nTypeArray[i] = NF_SYMBOLTYPE_CURRENCY; // convert
                        bool bHadDash = false;
                        i++;
                        while ( i < nStringsCnt && sStrArray[i][0] != ']' )
                        {
                            nPos = nPos + sStrArray[i].getLength();
                            if ( bHadDash )
                            {
                                *pStr += sStrArray[i];
                                nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                                nResultStringsCnt--;
                            }
                            else
                            {
                                if ( sStrArray[i][0] == '-' )
                                {
                                    bHadDash = true;
                                    pStr = &sStrArray[i];
                                    nTypeArray[i] = NF_SYMBOLTYPE_CURREXT;
                                }
                                else
                                {
                                    *pStr += sStrArray[i];
                                    nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                                    nResultStringsCnt--;
                                }
                            }
                            i++;
                        }
                        if ( rStr.getLength() && i < nStringsCnt && sStrArray[i][0] == ']' )
                        {
                            nTypeArray[i] = NF_SYMBOLTYPE_CURRDEL;
                            nPos = nPos + sStrArray[i].getLength();
                            i++;
                        }
                        else
                        {
                            return nPos; // Error
                        }
                    }
                    else
                    {
                        nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                        nPos = nPos + sStrArray[i].getLength();
                        i++;
                    }
                    break;
                default// Other Dels
                    if (eScannedType == SvNumFormatType::PERCENT && cHere == '%')
                    {
                        nTypeArray[i] = NF_SYMBOLTYPE_PERCENT;
                    }
                    else
                    {
                        nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                    }
                    nPos = nPos + sStrArray[i].getLength();
                    i++;
                    break;
                } // of switch (Del)
            } // of else Del
            else
            {
                SAL_WARN( "svl.numbers""unknown NF_SYMBOLTYPE_..." );
                nPos = nPos + sStrArray[i].getLength();
                i++;
            }
        } // of while
        if (eScannedType == SvNumFormatType::FRACTION)
        {
            if (bFrac)
            {
                nCntExp = nCounter;
            }
            else if (bBlank)
            {
                nCntPost = nCounter;
            }
            else
            {
                nCntPre = nCounter;
            }
        }
        else
        {
            if (bExp)
            {
                nCntExp = nCounter;
            }
            else if (bDecSep)
            {
                nCntPost = nCounter;
            }
            else
            {
                nCntPre = nCounter;
            }
        }
        if (bThousand) // Expansion of grouping separators
        {
            sal_uInt16 nMaxPos;
            if (bFrac)
            {
                if (bBlank)
                {
                    nMaxPos = nBlankPos;
                }
                else
                {
                    nMaxPos = 0;                // no grouping
                }
            }
            else if (bDecSep)                   // decimal separator present
            {
                nMaxPos = nDecPos;
            }
            else if (bExp)                      // 'E' exponent present
            {
                nMaxPos = nExpPos;
            }
            else                                // up to end
            {
                nMaxPos = i;
            }
            // Insert separators at proper positions.
            sal_Int32 nCount = 0;
            utl::DigitGroupingIterator aGrouping( pLoc->getDigitGrouping());
            size_t nFirstDigitSymbol = nMaxPos;
            size_t nFirstGroupingSymbol = nMaxPos;
            i = nMaxPos;
            while (i-- > 0)
            {
                if (nTypeArray[i] == NF_SYMBOLTYPE_DIGIT)
                {
                    nFirstDigitSymbol = i;
                    nCount = nCount + sStrArray[i].getLength(); // MSC converts += to int and then warns, so ...
                    // Insert separator only if not leftmost symbol.
                    if (i > 0 && nCount >= aGrouping.getPos())
                    {
                        DBG_ASSERT( sStrArray[i].getLength() == 1,
                                    "ImpSvNumberformatScan::FinalScan: combined digits in group separator insertion");
                        if (!InsertSymbol( i, NF_SYMBOLTYPE_THSEP, mrCurrentLanguageData.GetNumThousandSep()))
                        {
                            // nPos isn't correct here, but signals error
                            return nPos;
                        }
                        // i may have been decremented by 1
                        nFirstDigitSymbol = i + 1;
                        nFirstGroupingSymbol = i;
                        aGrouping.advance();
                    }
                }
            }
            // Generated something like "string",000; remove separator again.
            if (nFirstGroupingSymbol < nFirstDigitSymbol)
            {
                nTypeArray[nFirstGroupingSymbol] = NF_SYMBOLTYPE_EMPTY;
                nResultStringsCnt--;
            }
        }
        // Combine digits into groups to save memory (Info will be copied
        // later, taking only non-empty symbols).
        for (i = 0; i < nStringsCnt; ++i)
        {
            if (nTypeArray[i] == NF_SYMBOLTYPE_DIGIT)
            {
                OUString& rStr = sStrArray[i];
                while (++i < nStringsCnt && nTypeArray[i] == NF_SYMBOLTYPE_DIGIT)
                {
                    rStr += sStrArray[i];
                    nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                    nResultStringsCnt--;
                }
            }
        }
        break// of SvNumFormatType::NUMBER
    case SvNumFormatType::DATE:
        while (i < nStringsCnt)
        {
            switch (nTypeArray[i])
            {
            case NF_SYMBOLTYPE_BLANK:
            case NF_SYMBOLTYPE_STAR:
            case NF_SYMBOLTYPE_STRING:
                nPos = nPos + sStrArray[i].getLength();
                i++;
                break;
            case NF_SYMBOLTYPE_DEL:
                int nCalRet;
                if (sStrArray[i] == sOldDateSep)
                {
                    nTypeArray[i] = NF_SYMBOLTYPE_DATESEP;
                    nPos = nPos + sStrArray[i].getLength();
                    if (bConvertMode)
                    {
                        sStrArray[i] = mrCurrentLanguageData.GetDateSep();
                    }
                    i++;
                }
                else if ( (nCalRet = FinalScanGetCalendar( nPos, i, nResultStringsCnt )) != 0 )
                {
                    if ( nCalRet < 0  )
                    {
                        return nPos; // error
                    }
                }
                else
                {
                    nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                    nPos = nPos + sStrArray[i].getLength();
                    i++;
                }
                break;
            case NF_KEY_THAI_T :
                bThaiT = true;
                [[fallthrough]];
            case NF_KEY_M:                          // M
            case NF_KEY_MM:                         // MM
            case NF_KEY_MMM:                        // MMM
            case NF_KEY_MMMM:                       // MMMM
            case NF_KEY_MMMMM:                      // MMMMM
            case NF_KEY_Q:                          // Q
            case NF_KEY_QQ:                         // QQ
            case NF_KEY_D:                          // D
            case NF_KEY_DD:                         // DD
            case NF_KEY_DDD:                        // DDD
            case NF_KEY_DDDD:                       // DDDD
            case NF_KEY_YY:                         // YY
            case NF_KEY_YYYY:                       // YYYY
            case NF_KEY_NN:                         // NN
            case NF_KEY_NNN:                        // NNN
            case NF_KEY_NNNN:                       // NNNN
            case NF_KEY_WW :                        // WW
            case NF_KEY_AAA :                       // AAA
            case NF_KEY_AAAA :                      // AAAA
            case NF_KEY_EC :                        // E
            case NF_KEY_EEC :                       // EE
            case NF_KEY_G :                         // G
            case NF_KEY_GG :                        // GG
            case NF_KEY_GGG :                       // GGG
            case NF_KEY_R :                         // R
            case NF_KEY_RR :                        // RR
                sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
                nPos = nPos + sStrArray[i].getLength();
                if (bNewDateOrder)
                {
                    // For simple numeric date formats record date order and
                    // later rearrange.
                    switch (nTypeArray[i])
                    {
                        case NF_KEY_M:
                        case NF_KEY_MM:
                            if (nMonthPos == SAL_MAX_UINT16)
                                nMonthPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        case NF_KEY_D:
                        case NF_KEY_DD:
                            if (nDayPos == SAL_MAX_UINT16)
                                nDayPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        case NF_KEY_YY:
                        case NF_KEY_YYYY:
                            if (nYearPos == SAL_MAX_UINT16)
                                nYearPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        default:
                            ;   // nothing
                    }
                }
                i++;
                break;
            default// Other keywords
                nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                nPos = nPos + sStrArray[i].getLength();
                i++;
                break;
            }
        } // of while
        break// of SvNumFormatType::DATE
    case SvNumFormatType::TIME:
        while (i < nStringsCnt)
        {
            sal_Unicode cChar;

            switch (nTypeArray[i])
            {
            case NF_SYMBOLTYPE_BLANK:
            case NF_SYMBOLTYPE_STAR:
            case NF_SYMBOLTYPE_STRING:
                nPos = nPos + sStrArray[i].getLength();
                i++;
                break;
            case NF_SYMBOLTYPE_DEL:
                switch( sStrArray[i][0] )
                {
                case '0':
                    if ( Is100SecZero( i, bDecSep ) )
                    {
                        bDecSep = true;
                        nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
                        OUString& rStr = sStrArray[i];

                        nCounter++;
                        i++;
                        while (i < nStringsCnt &&
                               sStrArray[i][0] == '0')
                        {
                            rStr += sStrArray[i];
                            nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                            nResultStringsCnt--;
                            nCounter++;
                            i++;
                        }
                        nPos += rStr.getLength();
                    }
                    else
                    {
                        return nPos;
                    }
                    break;
                case '#':
                case '?':
                    return nPos;
                case '[':
                    if (bThousand) // Double
                    {
                        return nPos;
                    }
                    bThousand = true// Empty for Time
                    cChar = pChrCls->uppercase(OUString(NextChar(i)))[0];
                    if ( cChar == cOldKeyH )
                    {
                        nThousand = 1;      // H
                    }
                    else if ( cChar == cOldKeyMI )
                    {
                        nThousand = 2;      // M
                    }
                    else if ( cChar == cOldKeyS )
                    {
                        nThousand = 3;      // S
                    }
                    else
                    {
                        return nPos;
                    }
                    nPos = nPos + sStrArray[i].getLength();
                    i++;
                    break;
                case ']':
                    if (!bThousand) // No preceding [
                    {
                        return nPos;
                    }
                    nPos = nPos + sStrArray[i].getLength();
                    i++;
                    break;
                default:
                    nPos = nPos + sStrArray[i].getLength();
                    if ( sStrArray[i] == sOldTimeSep )
                    {
                        nTypeArray[i] = NF_SYMBOLTYPE_TIMESEP;
                        if ( bConvertMode )
                        {
                            sStrArray[i] = pLoc->getTimeSep();
                        }
                    }
                    else if ( sStrArray[i] == sOldTime100SecSep )
                    {
                        bDecSep = true;
                        nTypeArray[i] = NF_SYMBOLTYPE_TIME100SECSEP;
                        if ( bConvertMode )
                        {
                            sStrArray[i] = pLoc->getTime100SecSep();
                        }
                    }
                    else
                    {
                        nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                    }
                    i++;
                    break;
                }
                break;
            case NF_KEY_AMPM:                       // AM/PM
            case NF_KEY_AP:                         // A/P
                bExp = true;                        // Abuse for A/P
                sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
                nPos = nPos + sStrArray[i].getLength();
                i++;
                break;
            case NF_KEY_THAI_T :
                bThaiT = true;
                [[fallthrough]];
            case NF_KEY_MI:                         // M
            case NF_KEY_MMI:                        // MM
            case NF_KEY_H:                          // H
            case NF_KEY_HH:                         // HH
            case NF_KEY_S:                          // S
            case NF_KEY_SS:                         // SS
                sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
                nPos = nPos + sStrArray[i].getLength();
                i++;
                break;
            default// Other keywords
                nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                nPos = nPos + sStrArray[i].getLength();
                i++;
                break;
            }
        }                                       // of while
        nCntPost = nCounter;                    // Zero counter
        if (bExp)
        {
            nCntExp = 1;                        // Remembers AM/PM
        }
        break;                                 // of SvNumFormatType::TIME
    case SvNumFormatType::DATETIME:
        while (i < nStringsCnt)
        {
            int nCalRet;
            switch (nTypeArray[i])
            {
            case NF_SYMBOLTYPE_BLANK:
            case NF_SYMBOLTYPE_STAR:
            case NF_SYMBOLTYPE_STRING:
                nPos = nPos + sStrArray[i].getLength();
                i++;
                break;
            case NF_SYMBOLTYPE_DEL:
                if ( (nCalRet = FinalScanGetCalendar( nPos, i, nResultStringsCnt )) != 0 )
                {
                    if ( nCalRet < 0  )
                    {
                        return nPos; // Error
                    }
                }
                else
                {
                    switch( sStrArray[i][0] )
                    {
                    case '0':
                        if (bTimePart && Is100SecZero(i, bDecSep) && nCounter < MaxCntPost)
                        {
                            bDecSep = true;
                            nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
                            OUString& rStr = sStrArray[i];
                            nCounter++;
                            i++;
                            while (i < nStringsCnt &&
                                   sStrArray[i][0] == '0' && nCounter < MaxCntPost)
                            {
                                rStr += sStrArray[i];
                                nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                                nResultStringsCnt--;
                                nCounter++;
                                i++;
                            }
                            nPos += rStr.getLength();
                        }
                        else
                        {
                            return nPos;
                        }
                        break;
                    case '#':
                    case '?':
                        return nPos;
                    default:
                        nPos = nPos + sStrArray[i].getLength();
                        if (bTimePart)
                        {
                            if ( sStrArray[i] == sOldTimeSep )
                            {
                                nTypeArray[i] = NF_SYMBOLTYPE_TIMESEP;
                                if ( bConvertMode )
                                {
                                    sStrArray[i] = pLoc->getTimeSep();
                                }
                            }
                            else if ( sStrArray[i] == sOldTime100SecSep )
                            {
                                bDecSep = true;
                                nTypeArray[i] = NF_SYMBOLTYPE_TIME100SECSEP;
                                if ( bConvertMode )
                                {
                                    sStrArray[i] = pLoc->getTime100SecSep();
                                }
                            }
                            else
                            {
                                nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                            }
                        }
                        else
                        {
                            if ( sStrArray[i] == sOldDateSep )
                            {
                                nTypeArray[i] = NF_SYMBOLTYPE_DATESEP;
                                if (bConvertMode)
                                    sStrArray[i] = mrCurrentLanguageData.GetDateSep();
                            }
                            else
                            {
                                nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                            }
                        }
                        i++;
                        break;
                    }
                }
                break;
            case NF_KEY_AMPM:                       // AM/PM
            case NF_KEY_AP:                         // A/P
                bTimePart = true;
                bExp = true;                        // Abuse for A/P
                sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
                nPos = nPos + sStrArray[i].getLength();
                i++;
                break;
            case NF_KEY_MI:                         // M
            case NF_KEY_MMI:                        // MM
            case NF_KEY_H:                          // H
            case NF_KEY_HH:                         // HH
            case NF_KEY_S:                          // S
            case NF_KEY_SS:                         // SS
                bTimePart = true;
                sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
                nPos = nPos + sStrArray[i].getLength();
                i++;
                break;
            case NF_KEY_M:                          // M
            case NF_KEY_MM:                         // MM
            case NF_KEY_MMM:                        // MMM
            case NF_KEY_MMMM:                       // MMMM
            case NF_KEY_MMMMM:                      // MMMMM
            case NF_KEY_Q:                          // Q
            case NF_KEY_QQ:                         // QQ
            case NF_KEY_D:                          // D
            case NF_KEY_DD:                         // DD
            case NF_KEY_DDD:                        // DDD
            case NF_KEY_DDDD:                       // DDDD
            case NF_KEY_YY:                         // YY
            case NF_KEY_YYYY:                       // YYYY
            case NF_KEY_NN:                         // NN
            case NF_KEY_NNN:                        // NNN
            case NF_KEY_NNNN:                       // NNNN
            case NF_KEY_WW :                        // WW
            case NF_KEY_AAA :                       // AAA
            case NF_KEY_AAAA :                      // AAAA
            case NF_KEY_EC :                        // E
            case NF_KEY_EEC :                       // EE
            case NF_KEY_G :                         // G
            case NF_KEY_GG :                        // GG
            case NF_KEY_GGG :                       // GGG
            case NF_KEY_R :                         // R
            case NF_KEY_RR :                        // RR
                bTimePart = false;
                sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
                nPos = nPos + sStrArray[i].getLength();
                if (bNewDateOrder)
                {
                    // For simple numeric date formats record date order and
                    // later rearrange.
                    switch (nTypeArray[i])
                    {
                        case NF_KEY_M:
                        case NF_KEY_MM:
                            if (nMonthPos == SAL_MAX_UINT16)
                                nMonthPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        case NF_KEY_D:
                        case NF_KEY_DD:
                            if (nDayPos == SAL_MAX_UINT16)
                                nDayPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        case NF_KEY_YY:
                        case NF_KEY_YYYY:
                            if (nYearPos == SAL_MAX_UINT16)
                                nYearPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        default:
                            ;   // nothing
                    }
                }
                i++;
                break;
            case NF_KEY_THAI_T :
                bThaiT = true;
                sStrArray[i] = sKeyword[nTypeArray[i]];
                nPos = nPos + sStrArray[i].getLength();
                i++;
                break;
            default// Other keywords
                nTypeArray[i] = NF_SYMBOLTYPE_STRING;
                nPos = nPos + sStrArray[i].getLength();
                i++;
                break;
            }
        } // of while
        nCntPost = nCounter; // decimals (100th seconds)
        if (bExp)
        {
            nCntExp = 1; // Remembers AM/PM
        }
        break// of SvNumFormatType::DATETIME
    default:
        break;
    }
    if (eScannedType == SvNumFormatType::SCIENTIFIC &&
        (nCntPre + nCntPost == 0 || nCntExp == 0))
    {
        return nPos;
    }
    else if (eScannedType == SvNumFormatType::FRACTION && (nCntExp > 8 || nCntExp == 0))
    {
        return nPos;
    }
    if (bThaiT && !GetNatNumModifier())
    {
        SetNatNumModifier(1);
    }
    if ( bConvertMode )
    {
        if (bNewDateOrder && sOldDateSep == "-")
        {
            // Keep ISO formats Y-M-D, Y-M and M-D
            if (IsDateFragment( nYearPos, nMonthPos))
            {
                nTypeArray[nYearPos+1] = NF_SYMBOLTYPE_STRING;
                sStrArray[nYearPos+1] = sOldDateSep;
                bNewDateOrder = false;
            }
            if (IsDateFragment( nMonthPos, nDayPos))
            {
                nTypeArray[nMonthPos+1] = NF_SYMBOLTYPE_STRING;
                sStrArray[nMonthPos+1] = sOldDateSep;
                bNewDateOrder = false;
            }
        }
        if (bNewDateOrder)
        {
            // Rearrange date order to the target locale if the original order
            // includes date separators and is adjacent.
            /* TODO: for incomplete dates trailing separators need to be
             * handled according to the locale's usage, e.g. en-US M/D should
             * be converted to de-DE D.M. and vice versa. As is, it's
             * M/D -> D.M and D.M. -> M/D/ where specifically the latter looks
             * odd. Check accepted date patterns and append/remove? */

            switch (eOldDateOrder)
            {
                case DateOrder::DMY:
                    switch (pLoc->getDateOrder())
                    {
                        case DateOrder::MDY:
                            // Convert only if the actual format is not of YDM
                            // order (which would be a completely unusual order
                            // anyway, but..), e.g. YYYY.DD.MM not to
                            // YYYY/MM/DD
                            if (IsDateFragment( nDayPos, nMonthPos) && !IsDateFragment( nYearPos, nDayPos))
                                SwapArrayElements( nDayPos, nMonthPos);
                        break;
                        case DateOrder::YMD:
                            if (nYearPos != SAL_MAX_UINT16)
                            {
                                if (IsDateFragment( nDayPos, nMonthPos) && IsDateFragment( nMonthPos, nYearPos))
                                    SwapArrayElements( nDayPos, nYearPos);
                            }
                            else
                            {
                                if (IsDateFragment( nDayPos, nMonthPos))
                                    SwapArrayElements( nDayPos, nMonthPos);
                            }
                        break;
                        default:
                            ;   // nothing
                    }
                break;
                case DateOrder::MDY:
                    switch (pLoc->getDateOrder())
                    {
                        case DateOrder::DMY:
                            // Convert only if the actual format is not of YMD
                            // order, e.g. YYYY/MM/DD not to YYYY.DD.MM
                            /* TODO: convert such to DD.MM.YYYY instead? */
                            if (IsDateFragment( nMonthPos, nDayPos) && !IsDateFragment( nYearPos, nMonthPos))
                                SwapArrayElements( nMonthPos, nDayPos);
                        break;
                        case DateOrder::YMD:
                            if (nYearPos != SAL_MAX_UINT16)
                            {
                                if (IsDateFragment( nMonthPos, nDayPos) && IsDateFragment( nDayPos, nYearPos))
                                {
                                    SwapArrayElements( nYearPos, nMonthPos);    // YDM
                                    SwapArrayElements( nYearPos, nDayPos);      // YMD
                                }
                            }
                        break;
                        default:
                            ;   // nothing
                    }
                break;
                case DateOrder::YMD:
                    switch (pLoc->getDateOrder())
                    {
                        case DateOrder::DMY:
                            if (nYearPos != SAL_MAX_UINT16)
                            {
                                if (IsDateFragment( nYearPos, nMonthPos) && IsDateFragment( nMonthPos, nDayPos))
                                    SwapArrayElements( nYearPos, nDayPos);
                            }
                            else
                            {
                                if (IsDateFragment( nMonthPos, nDayPos))
                                    SwapArrayElements( nMonthPos, nDayPos);
                            }
                        break;
                        case DateOrder::MDY:
                            if (nYearPos != SAL_MAX_UINT16)
                            {
                                if (IsDateFragment( nYearPos, nMonthPos) && IsDateFragment( nMonthPos, nDayPos))
                                {
                                    SwapArrayElements( nYearPos, nDayPos);      // DMY
                                    SwapArrayElements( nYearPos, nMonthPos);    // MDY
                                }
                            }
                        break;
                        default:
                            ;   // nothing
                    }
                break;
                default:
                    ;   // nothing
            }
        }
        // strings containing keywords of the target locale must be quoted, so
        // the user sees the difference and is able to edit the format string
        for ( i=0; i < nStringsCnt; i++ )
        {
            if ( nTypeArray[i] == NF_SYMBOLTYPE_STRING &&
                 sStrArray[i][0] != '\"' )
            {
                if ( bConvertSystemToSystem && eScannedType == SvNumFormatType::CURRENCY )
                {
                    // don't stringize automatic currency, will be converted
                    if ( sStrArray[i] == sOldCurSymbol )
                    {
                        continue// for
                    }
                    // DM might be split into D and M
                    if ( sStrArray[i].getLength() < sOldCurSymbol.getLength() &&
                         pChrCls->uppercase( sStrArray[i], 0, 1 )[0] ==
                         sOldCurString[0] )
                    {
                        OUString aTmp( sStrArray[i] );
                        sal_uInt16 j = i + 1;
                        while ( aTmp.getLength() < sOldCurSymbol.getLength() &&
                                j < nStringsCnt &&
                                nTypeArray[j] == NF_SYMBOLTYPE_STRING )
                        {
                            aTmp += sStrArray[j++];
                        }
                        if ( pChrCls->uppercase( aTmp ) == sOldCurString )
                        {
                            sStrArray[i++] = aTmp;
                            for ( ; i<j; i++ )
                            {
                                nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                                nResultStringsCnt--;
                            }
                            i = j - 1;
                            continue// for
                        }
                    }
                }
                OUString& rStr = sStrArray[i];
                sal_Int32 nLen = rStr.getLength();
                for ( sal_Int32 j = 0; j < nLen; j++ )
                {
                    bool bFoundEnglish = false;
                    if ( (j == 0 || rStr[j - 1] != '\\') && GetKeyWord( rStr, j, bFoundEnglish) )
                    {
                        rStr = "\"" + rStr + "\"";
                        break// for
                    }
                }
            }
        }
    }
    // Concatenate strings, remove quotes for output, and rebuild the format string
    rString.clear();
    i = 0;
    while (i < nStringsCnt)
    {
        sal_Int32 nStringPos;
        sal_Int32 nArrPos = 0;
        sal_uInt16 iPos = i;
        switch ( nTypeArray[i] )
        {
        case NF_SYMBOLTYPE_STRING :
        case NF_SYMBOLTYPE_FRACBLANK :
            nStringPos = rString.getLength();
            do
            {
                if (sStrArray[i].getLength() == 2 &&
                    sStrArray[i][0] == '\\')
                {
                    // Unescape some simple forms of symbols even in the UI
                    // visible string to prevent duplicates that differ
                    // only in notation, originating from import.
                    // e.g. YYYY-MM-DD and YYYY\-MM\-DD are identical,
                    // but 0\ 000 0 and 0 000 0 in a French locale are not.

                    sal_Unicode c = sStrArray[i][1];

                    switch (c)
                    {
                    case '+':
                    case '-':
                        rString += OUStringChar(c);
                        break;
                    case ' ':
                    case '.':
                    case '/':
                        if (!(eScannedType & SvNumFormatType::DATE) &&
                            (StringEqualsChar( mrCurrentLanguageData.GetNumThousandSep(), c) ||
                             StringEqualsChar( mrCurrentLanguageData.GetNumDecimalSep(), c) ||
                             (c == ' ' &&
                              (StringEqualsChar( mrCurrentLanguageData.GetNumThousandSep(), cNoBreakSpace) ||
                               StringEqualsChar( mrCurrentLanguageData.GetNumThousandSep(), cNarrowNoBreakSpace)))))
                        {
                            rString += sStrArray[i];
                        }
                        else if ((eScannedType & SvNumFormatType::DATE) &&
                                 StringEqualsChar( mrCurrentLanguageData.GetDateSep(), c))
                        {
                            rString += sStrArray[i];
                        }
                        else if ((eScannedType & SvNumFormatType::TIME) &&
                                 (StringEqualsChar( pLoc->getTimeSep(), c) ||
                                  StringEqualsChar( pLoc->getTime100SecSep(), c)))
                        {
                            rString += sStrArray[i];
                        }
                        else if (eScannedType & SvNumFormatType::FRACTION)
                        {
                            rString += sStrArray[i];
                        }
                        else
                        {
                            rString += OUStringChar(c);
                        }
                        break;
                    default:
                        rString += sStrArray[i];
                    }
                }
                else
                {
                    rString += sStrArray[i];
                }
                if ( RemoveQuotes( sStrArray[i] ) > 0 )
                {
                    // update currency up to quoted string
                    if ( eScannedType == SvNumFormatType::CURRENCY )
                    {
                        // dM -> DM  or  DM -> $  in old automatic
                        // currency formats, oh my ..., why did we ever introduce them?
                        OUString aTmp( pChrCls->uppercase( sStrArray[iPos], nArrPos,
                                                           sStrArray[iPos].getLength()-nArrPos ) );
                        sal_Int32 nCPos = aTmp.indexOf( sOldCurString );
                        if ( nCPos >= 0 )
                        {
                            const OUString& rCur = bConvertMode && bConvertSystemToSystem ?
                                GetCurSymbol() : sOldCurSymbol;
                            sStrArray[iPos] = sStrArray[iPos].replaceAt( nArrPos + nCPos,
                                                                         sOldCurString.getLength(),
                                                                         rCur );
                            rString = rString.replaceAt( nStringPos + nCPos,
                                                         sOldCurString.getLength(),
                                                         rCur );
                        }
                        nStringPos = rString.getLength();
                        if ( iPos == i )
                        {
                            nArrPos = sStrArray[iPos].getLength();
                        }
                        else
                        {
                            nArrPos = sStrArray[iPos].getLength() + sStrArray[i].getLength();
                        }
                    }
                }
                if ( iPos != i )
                {
                    sStrArray[iPos] += sStrArray[i];
                    nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                    nResultStringsCnt--;
                }
                i++;
            }
            while ( i < nStringsCnt && nTypeArray[i] == NF_SYMBOLTYPE_STRING );

            if ( i < nStringsCnt )
            {
                i--; // enter switch on next symbol again
            }
            if ( eScannedType == SvNumFormatType::CURRENCY && nStringPos < rString.getLength() )
            {
                // same as above, since last RemoveQuotes
                OUString aTmp( pChrCls->uppercase( sStrArray[iPos], nArrPos,
                                                   sStrArray[iPos].getLength()-nArrPos ) );
                sal_Int32 nCPos = aTmp.indexOf( sOldCurString );
                if ( nCPos >= 0 )
                {
                    const OUString& rCur = bConvertMode && bConvertSystemToSystem ?
                        GetCurSymbol() : sOldCurSymbol;
                    sStrArray[iPos] = sStrArray[iPos].replaceAt( nArrPos + nCPos,
                                                                 sOldCurString.getLength(),
                                                                 rCur );
                    rString = rString.replaceAt( nStringPos + nCPos,
                                                 sOldCurString.getLength(), rCur );
                }
            }
            break;
        case NF_SYMBOLTYPE_CURRENCY :
            rString += sStrArray[i];
            RemoveQuotes( sStrArray[i] );
            break;
        case NF_KEY_THAI_T:
            if (bThaiT && GetNatNumModifier() == 1)
            {
                // Remove T from format code, will be replaced with a [NatNum1] prefix.
                nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
                nResultStringsCnt--;
            }
            else
            {
                rString += sStrArray[i];
            }
            break;
        case NF_SYMBOLTYPE_EMPTY :
            // nothing
            break;
        default:
            rString += sStrArray[i];
        }
        i++;
    }
    return 0;
}

sal_Int32 ImpSvNumberformatScan::RemoveQuotes( OUString& rStr )
{
    if ( rStr.getLength() > 1 )
    {
        sal_Unicode c = rStr[0];
        sal_Int32 n = rStr.getLength() - 1;
        if ( c == '"' && rStr[n] == '"' )
        {
            rStr = rStr.copy( 1, n-1);
            return 2;
        }
        else if ( c == '\\' )
        {
            rStr = rStr.copy(1);
            return 1;
        }
    }
    return 0;
}

sal_Int32 ImpSvNumberformatScan::ScanFormat( OUString& rString )
{
    sal_Int32 res = Symbol_Division(rString); // Lexical analysis
    if (!res)
    {
        res = ScanType(); // Recognizing the Format type
    }
    if (!res)
    {
        res = FinalScan( rString ); // Type dependent final analysis
    }
    return res; // res = control position; res = 0 => Format ok
}

void ImpSvNumberformatScan::CopyInfo(ImpSvNumberformatInfo* pInfo, sal_uInt16 nCnt)
{
    size_t i,j;
    j = 0;
    i = 0;
    while (i < nCnt && j < NF_MAX_FORMAT_SYMBOLS)
    {
        if (nTypeArray[j] != NF_SYMBOLTYPE_EMPTY)
        {
            pInfo->sStrArray[i]  = sStrArray[j];
            pInfo->nTypeArray[i] = nTypeArray[j];
            i++;
        }
        j++;
    }
    pInfo->eScannedType = eScannedType;
    pInfo->bThousand    = bThousand;
    pInfo->nThousand    = nThousand;
    pInfo->nCntPre      = nCntPre;
    pInfo->nCntPost     = nCntPost;
    pInfo->nCntExp      = nCntExp;
}

bool ImpSvNumberformatScan::ReplaceBooleanEquivalent( OUString& rString )
{
    InitKeywords();
    /* TODO: compare case insensitive? Or rather leave as is and case not
     * matching indicates user supplied on purpose? Written to file / generated
     * was always uppercase. */

    if (rString == sBooleanEquivalent1 || rString == sBooleanEquivalent2)
    {
        rString = GetKeywords()[NF_KEY_BOOLEAN];
        return true;
    }
    return false;
}

Color* ImpSvNumberformatScan::GetUserDefColor(sal_uInt16 nIndex) const
{
    return mrColorCallback.GetUserDefColor(nIndex);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Messung V0.5 in Prozent
C=85 H=92 G=88

¤ Dauer der Verarbeitung: 0.85 Sekunden  (vorverarbeitet am  2026-04-26) ¤

*© 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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge