diff options
Diffstat (limited to 'svl/source/numbers/zforscan.cxx')
-rw-r--r-- | svl/source/numbers/zforscan.cxx | 3322 |
1 files changed, 3322 insertions, 0 deletions
diff --git a/svl/source/numbers/zforscan.cxx b/svl/source/numbers/zforscan.cxx new file mode 100644 index 000000000..a5f256376 --- /dev/null +++ b/svl/source/numbers/zforscan.cxx @@ -0,0 +1,3322 @@ +/* -*- 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 + "", // NF_KEY_NONE 0 + "E", // NF_KEY_E Exponent + "AM/PM", // NF_KEY_AMPM AM/PM + "A/P", // NF_KEY_AP AM/PM short + "M", // NF_KEY_MI Minute + "MM", // NF_KEY_MMI Minute 02 + "M", // NF_KEY_M month (!) + "MM", // NF_KEY_MM month 02 (!) + "MMM", // NF_KEY_MMM month short name + "MMMM", // NF_KEY_MMMM month long name + "MMMMM", // NF_KEY_MMMMM first letter of month name + "H", // NF_KEY_H hour + "HH", // NF_KEY_HH hour 02 + "S", // NF_KEY_S Second + "SS", // NF_KEY_SS Second 02 + "Q", // NF_KEY_Q Quarter short 'Q' + "QQ", // NF_KEY_QQ Quarter long + "D", // NF_KEY_D day of month + "DD", // NF_KEY_DD day of month 02 + "DDD", // NF_KEY_DDD day of week short + "DDDD", // NF_KEY_DDDD day of week long + "YY", // NF_KEY_YY year two digits + "YYYY", // NF_KEY_YYYY year four digits + "NN", // NF_KEY_NN Day of week short + "NNN", // NF_KEY_NNN Day of week long + "NNNN", // NF_KEY_NNNN Day of week long incl. separator + "AAA", // NF_KEY_AAA + "AAAA", // NF_KEY_AAAA + "E", // NF_KEY_EC + "EE", // NF_KEY_EEC + "G", // NF_KEY_G + "GG", // NF_KEY_GG + "GGG", // NF_KEY_GGG + "R", // NF_KEY_R + "RR", // NF_KEY_RR + "WW", // NF_KEY_WW Week of year + "t", // NF_KEY_THAI_T Thai T modifier, speciality of Thai Excel, only + // used with Thai locale and converted to [NatNum1], only + // exception as lowercase + "CCC", // NF_KEY_CCC Currency abbreviation + "BOOLEAN", // NF_KEY_BOOLEAN boolean + "GENERAL", // NF_KEY_GENERAL General / Standard + + // Reserved words translated and color names follow: + "TRUE", // NF_KEY_TRUE boolean true + "FALSE", // NF_KEY_FALSE boolean false + "COLOR", // NF_KEY_COLOR color + // colours + "BLACK", // NF_KEY_BLACK + "BLUE", // NF_KEY_BLUE + "GREEN", // NF_KEY_GREEN + "CYAN", // NF_KEY_CYAN + "RED", // NF_KEY_RED + "MAGENTA", // NF_KEY_MAGENTA + "BROWN", // NF_KEY_BROWN + "GREY", // NF_KEY_GREY + "YELLOW", // NF_KEY_YELLOW + "WHITE" // 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( SvNumberFormatter* pFormatterP ) + : maNullDate( 30, 12, 1899) + , eNewLnge(LANGUAGE_DONTKNOW) + , eTmpLnge(LANGUAGE_DONTKNOW) + , nCurrPos(-1) + , meKeywordLocalization(KeywordLocalization::AllowEnglish) +{ + pFormatter = pFormatterP; + xNFC = css::i18n::NumberFormatMapper::create( pFormatter->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] = + pFormatter->GetCharClass()->uppercase( pFormatter->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] = + pFormatter->GetCharClass()->uppercase( pFormatter->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 + pFormatter->GetCompatibilityCurrency( pThis->sCurSymbol, pThis->sCurAbbrev ); + // currency symbol upper case + pThis->sCurString = pFormatter->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 = pFormatter->GetCharClass(); + const LocaleDataWrapper* pLocaleData = pFormatter->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& rLoadedLocale = pLocaleData->getLoadedLanguageTag(); + LanguageType eLang = rLoadedLocale.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, rLoadedLocale.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) +{ + maNullDate = Date(nDay, nMonth, nYear); + if (!maNullDate.IsValidDate()) + { + maNullDate.Normalize(); + SAL_WARN("svl.numbers","ImpSvNumberformatScan::ChangeNullDate - not valid" + " d: " << nDay << " m: " << nMonth << " y: " << nYear << " normalized to" + " d: " << maNullDate.GetDay() << " m: " << maNullDate.GetMonth() << " y: " << maNullDate.GetYear()); + } +} + +void ImpSvNumberformatScan::ChangeStandardPrec(sal_uInt16 nPrec) +{ + nStandardPrec = nPrec; +} + +const Color* ImpSvNumberformatScan::GetColor(OUString& sStr) const +{ + OUString sString = pFormatter->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 = pFormatter->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 = pFormatter->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 = pFormatter->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 = pFormatter->GetCharClass(); + const LocaleDataWrapper* pLoc = pFormatter->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( pFormatter->GetNumDecimalSep(), cToken) || + StringEqualsChar( pFormatter->GetNumThousandSep(), cToken) || + StringEqualsChar( pFormatter->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: + eState = SsStop; + sSymbolBuffer.append(OUStringChar(cToken)); + break; + 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 = pFormatter->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 = pFormatter->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; + } + 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] != pFormatter->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 ( pFormatter->GetDateSep() != sStrArray[i] && + pLoc->getTimeSep() != sStrArray[i] ) + { + return nPos; + } + } + break; + case SvNumFormatType::PERCENT: + switch (eNewType) + { + case SvNumFormatType::NUMBER: // Only number to percent + break; + default: + return nPos; + } + break; + case SvNumFormatType::SCIENTIFIC: + switch (eNewType) + { + case SvNumFormatType::NUMBER: // Only number to E + 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; + case SvNumFormatType::FRACTION: + switch (eNewType) + { + case SvNumFormatType::NUMBER: // Only number to fraction + break; + default: + 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; + for (size_t i = nStringsCnt; i > nPos; --i) + { + nTypeArray[i] = nTypeArray[i-1]; + sStrArray[i] = sStrArray[i-1]; + } + } + ++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 = pFormatter->GetLocaleData(); + + // save values for convert mode + OUString sOldDecSep = pFormatter->GetNumDecimalSep(); + OUString sOldThousandSep = pFormatter->GetNumThousandSep(); + OUString sOldDateSep = pFormatter->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) + { + pFormatter->ChangeIntl(eNewLnge); + //! pointer may have changed + pLoc = pFormatter->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 = pFormatter->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( pFormatter->GetNumThousandSep(), cHere)) + { + cSimplified = ','; + } + else if (StringEqualsChar( pFormatter->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 = pFormatter->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] = pFormatter->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] = pFormatter->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, pFormatter->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] = pFormatter->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: + 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_SYMBOLTYPE_STRING: + nPos = nPos + sStrArray[i].getLength(); + i++; + 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] = pFormatter->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( pFormatter->GetNumThousandSep(), c) || + StringEqualsChar( pFormatter->GetNumDecimalSep(), c) || + (c == ' ' && + (StringEqualsChar( pFormatter->GetNumThousandSep(), cNoBreakSpace) || + StringEqualsChar( pFormatter->GetNumThousandSep(), cNarrowNoBreakSpace))))) + { + rString += sStrArray[i]; + } + else if ((eScannedType & SvNumFormatType::DATE) && + StringEqualsChar( pFormatter->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; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |