c6c6126aa3
to split it into two constituent parts SvNFFormatData which is the data store for number formats it generally operates on. SvNFLanguageData for data around the current language in use. and then a SvNFEngine which implements the interaction between those parts SvNFEngine has two policies, the typical RW mode and a new RO mode where the SvNFFormatData doesn't change, all formats needed in this mode must already exist. Change-Id: I56b070ccd2e556a0cb1fe609a2fae28e18277c8c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/165146 Tested-by: Jenkins Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
3339 lines
130 KiB
C++
3339 lines
130 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
|
|
#include <stdlib.h>
|
|
#include <comphelper/string.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <tools/debug.hxx>
|
|
#include <i18nlangtag/mslangid.hxx>
|
|
#include <unotools/charclass.hxx>
|
|
#include <unotools/localedatawrapper.hxx>
|
|
#include <com/sun/star/i18n/NumberFormatCode.hpp>
|
|
#include <com/sun/star/i18n/NumberFormatMapper.hpp>
|
|
|
|
#include <svl/zforlist.hxx>
|
|
#include <svl/zformat.hxx>
|
|
#include <unotools/digitgroupingiterator.hxx>
|
|
|
|
#include "zforscan.hxx"
|
|
|
|
#include <svl/nfsymbol.hxx>
|
|
using namespace svt;
|
|
|
|
const sal_Unicode cNoBreakSpace = 0xA0;
|
|
const sal_Unicode cNarrowNoBreakSpace = 0x202F;
|
|
|
|
const int MaxCntPost = 20; //max dec places allow by rtl_math_round
|
|
|
|
const NfKeywordTable ImpSvNumberformatScan::sEnglishKeyword =
|
|
{ // Syntax keywords in English (USA)
|
|
//! All keywords MUST be UPPERCASE! In same order as NfKeywordIndex
|
|
"", // 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(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& 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)
|
|
{
|
|
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:
|
|
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 = 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:
|
|
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 = 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:
|
|
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] = 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: */
|