/* -*- 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)
    : maNullDate( 30, 12, 1899)
    , 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: */

V501 There are identical sub-expressions to the left and to the right of the '-' operator: NF_KEY_COLOR - NF_KEY_COLOR

V501 There are identical sub-expressions to the left and to the right of the '-' operator: NF_KEY_COLOR - NF_KEY_COLOR

V1051 Consider checking for misprints. It's possible that the 'nStringPos' should be checked here.