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